diff options
| -rw-r--r-- | public/index.html | 12 | ||||
| -rw-r--r-- | src/albums.js | 26 | ||||
| -rw-r--r-- | src/immich.js | 32 | ||||
| -rw-r--r-- | src/index.js | 13 | ||||
| -rw-r--r-- | src/pages.js | 9 | ||||
| -rw-r--r-- | src/style.css | 52 |
6 files changed, 122 insertions, 22 deletions
diff --git a/public/index.html b/public/index.html index 5868d5d..d122f33 100644 --- a/public/index.html +++ b/public/index.html @@ -32,11 +32,13 @@ <div id="albums" style="display:none"> <div id="albums-container"></div> <template id="album-template"> - <img class="album-thumb" /> - <span class="album-name"></span> - <div> - <span><span class="album-assets-count"></span> items</span> - <span class="album-shared">· Shared</span> + <div class="album"> + <img class="album-thumb" /> + <span class="album-name font-bold"></span> + <div class="album-info"> + <span><span class="album-assets-count"></span> items</span> + <span class="album-shared">• Shared</span> + </div> </div> </template> </div> 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; |
