summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTim Keller <tjk@tjkeller.xyz>2025-05-05 22:56:56 -0500
committerTim Keller <tjk@tjkeller.xyz>2025-05-05 22:56:56 -0500
commitf86d11c3ce1f04ee89da235d78447aed6d6d7130 (patch)
treef253642b156ba296c9a5f9211ae95b62da3ccfb5 /src
parentbae5fe5501117df1b16da1aeb3355056d6882648 (diff)
downloadimmich-frame-f86d11c3ce1f04ee89da235d78447aed6d6d7130.tar.xz
immich-frame-f86d11c3ce1f04ee89da235d78447aed6d6d7130.zip
albums page and a bunch of stuff
Diffstat (limited to 'src')
-rw-r--r--src/albums.js26
-rw-r--r--src/immich.js32
-rw-r--r--src/index.js13
-rw-r--r--src/pages.js9
-rw-r--r--src/style.css52
5 files changed, 115 insertions, 17 deletions
diff --git a/src/albums.js b/src/albums.js
new file mode 100644
index 0000000..5628e6d
--- /dev/null
+++ b/src/albums.js
@@ -0,0 +1,26 @@
+import immichConnector from "./immich.js"
+
+export default async function initAlbums(albumsPageContainer) {
+ // TODO empty cells animation
+
+ const albumsContainer = albumsPageContainer.querySelector("#albums-container")
+ const albumTemplate = albumsPageContainer.querySelector("#album-template")
+ async function createAlbum(res) {
+ console.log(res.albumName, res.id, res.startDate, res.endDate, res.assetCount, res.shared)
+ const albumClone = albumTemplate.content.cloneNode(true)
+ albumClone.querySelector(".album-thumb").src = await immichConnector.fetchImageSrc(res.albumThumbnailAssetId)
+ albumClone.querySelector(".album-name").textContent = res.albumName
+ albumClone.querySelector(".album-assets-count").textContent = res.assetCount.toLocaleString()
+ if (!res.shared)
+ albumClone.querySelector(".album-shared").remove()
+
+ albumsContainer.appendChild(albumClone)
+ }
+
+ const albumsResponse = await immichConnector.fetchAlbums()
+
+ for (const res of albumsResponse)
+ createAlbum(res)
+
+ return true
+}
diff --git a/src/immich.js b/src/immich.js
index d911007..d938fea 100644
--- a/src/immich.js
+++ b/src/immich.js
@@ -1,4 +1,4 @@
-export default class ImmichConnector {
+class ImmichConnector {
constructor(url, apiKey) {
this.url = url
this.apiKey = apiKey
@@ -8,21 +8,41 @@ export default class ImmichConnector {
return this.fetch("/albums")
}
- fetch(endpoint) {
+ #fetch(endpoint) {
return fetch(this.url + "/api" + endpoint, {
headers: { "x-api-key": this.apiKey }
})
+ }
+
+ fetch(endpoint) {
+ return this.#fetch(endpoint)
.then(response => {
- if (!response.ok) {
+ if (!response.ok)
throw new Error(`HTTP error! Status: ${response.status}`)
- }
return response.json()
})
.then(data => {
- console.log('Fetched data:', data)
+ return data
})
.catch(error => {
- console.error('Fetch error:', error)
+ console.error("Fetch error:", error)
+ })
+ }
+
+ fetchImageSrc(key, size) {
+ const url = `/assets/${key}/thumbnail` + (this.size ? `?size=${this.size}` : "")
+ return this.#fetch(url)
+ .then(response => {
+ if (!response.ok)
+ throw new Error(`HTTP error! Status: ${response.status}`)
+ return response.blob()
+ })
+ .then(blob => {
+ return URL.createObjectURL(blob)
})
}
}
+
+const immichConnector = new ImmichConnector("http://192.168.1.13", "m5nqOoBc4uhAba21gZdCP3z8D3JT4GPxDXL2psd52EA")
+document.immichConnector = immichConnector // FIXME TEMP
+export default immichConnector
diff --git a/src/index.js b/src/index.js
index 4324bb5..a6d2130 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,17 +1,16 @@
import "@fontsource/overpass"
import "@fontsource/overpass/500.css"
+import "@fontsource/overpass/700.css"
import "./style.css"
-import ImmichConnector from "./immich.js"
+import immichConnector from "./immich.js"
import Page from "./pages.js"
import "./icons.js"
import initSlides from "./slides.js"
+import initAlbums from "./albums.js"
-const immichConnector = new ImmichConnector("http://192.168.1.13", "m5nqOoBc4uhAba21gZdCP3z8D3JT4GPxDXL2psd52EA")
-document.immichConnector = immichConnector // FIXME TEMP
-
-const slideshow = new Page(document.querySelector("#slideshow"), "/slideshow", initSlides)
-const albums = new Page(document.querySelector("#albums"), "/albums")
-const settings = new Page(document.querySelector("#settings"), "/settings")
+const slideshow = new Page(document.querySelector("#slideshow"), ["", "/slideshow"], initSlides)
+const albums = new Page(document.querySelector("#albums"), ["/albums"], initAlbums)
+const settings = new Page(document.querySelector("#settings"), ["/settings"])
window.addEventListener("popstate", Page.pathnameCallback)
Page.pathnameCallback() /* run after all pages are registered */
diff --git a/src/pages.js b/src/pages.js
index 3a02454..0576684 100644
--- a/src/pages.js
+++ b/src/pages.js
@@ -21,11 +21,12 @@ export default class Page {
Page.pathnameCallback()
}
- constructor(pageContainer, endpoint, f_initialize) {
- Page.pages[endpoint] = this
+ constructor(pageContainer, endpoints, f_initialize) {
+ for (const endpoint of endpoints)
+ Page.pages[endpoint] = this
this.pageContainer = pageContainer
- this.endpoint = endpoint
+ this.endpoints = endpoints
this.initialize = f_initialize
this.visible = false
this.initialized = false
@@ -35,6 +36,6 @@ export default class Page {
this.pageContainer.style.display = visible ? null : "none"
this.visible = visible
if (visible && !this.initialized && this.initialize)
- this.initialized = this.initialize()
+ this.initialized = this.initialize(this.pageContainer)
}
}
diff --git a/src/style.css b/src/style.css
index 98d3fda..607e6ec 100644
--- a/src/style.css
+++ b/src/style.css
@@ -1,3 +1,9 @@
+:root {
+ --immich-dark-primary: rgb(172 203 250);
+}
+
+.font-bold { font-weight: 700 }
+
body {
background: black;
color: white;
@@ -21,6 +27,7 @@ svg {
height: 100%;
}
+/* slideshow */
#slideshow-carousel {
height: 100%
}
@@ -77,6 +84,44 @@ svg {
}
}
+/* albums */
+#albums {
+ width: 100%;
+ height: 100%;
+ overflow: scroll;
+}
+#albums-container {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+ gap: 2.5rem;
+ margin: auto;
+ padding: 2rem;
+
+ .album-thumb {
+ width: 100%;
+ aspect-ratio: 1/1;
+ object-fit: cover;
+ border-radius: 1rem;
+ margin-bottom: 1rem;
+ }
+
+}
+
+@media (max-width: 512px) {
+ #albums-container {
+ grid-template-columns: 1fr 1fr;
+ gap: 1.5rem;
+ .album-info { font-size-adjust: .4; }
+ }
+}
+
+@media (max-width: 768px) {
+ .carousel-cell {
+ width: 70vw;
+ margin: 12px;
+ }
+}
+
footer {
width: 100%;
}
@@ -95,6 +140,13 @@ footer {
text-decoration: none;
width: 100%;
gap: 1rem;
+
+ &:hover {
+ color: var(--immich-dark-primary);
+
+ svg{ fill: var(--immich-dark-primary) }
+ }
+
svg { height: 24px; width: 24px }
span {
font-size: .875rem;