diff --git a/.gitignore b/.gitignore index 12ebec1c..d375bae0 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,3 @@ sw.* .DS_STORE .idea/* tailwind.compiled.css -tailwind.config.js diff --git a/Dockerfile b/Dockerfile index 816bdd3c..4e110a61 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,34 @@ -ARG NUSQLITE3_DIR="/usr/local/lib/nusqlite3" -ARG NUSQLITE3_PATH="${NUSQLITE3_DIR}/libnusqlite3.so" - ### STAGE 0: Build client ### -FROM node:20-alpine AS build-client - +FROM node:20-alpine AS build WORKDIR /client COPY /client /client RUN npm ci && npm cache clean --force RUN npm run generate ### STAGE 1: Build server ### -FROM node:20-alpine AS build-server - -ARG NUSQLITE3_DIR -ARG TARGETPLATFORM +FROM node:20-alpine ENV NODE_ENV=production -RUN apk add --no-cache --update \ +RUN apk update && \ + apk add --no-cache --update \ curl \ + tzdata \ + ffmpeg \ make \ python3 \ g++ \ + tini \ unzip -WORKDIR /server -COPY index.js package* /server -COPY /server /server/server +COPY --from=build /client/dist /client/dist +COPY index.js package* / +COPY server server + +ARG TARGETPLATFORM + +ENV NUSQLITE3_DIR="/usr/local/lib/nusqlite3" +ENV NUSQLITE3_PATH="${NUSQLITE3_DIR}/libnusqlite3.so" RUN case "$TARGETPLATFORM" in \ "linux/amd64") \ @@ -40,34 +42,14 @@ RUN case "$TARGETPLATFORM" in \ RUN npm ci --only=production -### STAGE 2: Create minimal runtime image ### -FROM node:20-alpine - -ARG NUSQLITE3_DIR -ARG NUSQLITE3_PATH - -# Install only runtime dependencies -RUN apk add --no-cache --update \ - tzdata \ - ffmpeg \ - tini - -WORKDIR /app - -# Copy compiled frontend and server from build stages -COPY --from=build-client /client/dist /app/client/dist -COPY --from=build-server /server /app -COPY --from=build-server ${NUSQLITE3_PATH} ${NUSQLITE3_PATH} +RUN apk del make python3 g++ EXPOSE 80 ENV PORT=80 -ENV NODE_ENV=production ENV CONFIG_PATH="/config" ENV METADATA_PATH="/metadata" ENV SOURCE="docker" -ENV NUSQLITE3_DIR=${NUSQLITE3_DIR} -ENV NUSQLITE3_PATH=${NUSQLITE3_PATH} ENTRYPOINT ["tini", "--"] CMD ["node", "index.js"] diff --git a/client/components/app/BookShelfCategorized.vue b/client/components/app/BookShelfCategorized.vue index 4bf8cfbb..8c680462 100644 --- a/client/components/app/BookShelfCategorized.vue +++ b/client/components/app/BookShelfCategorized.vue @@ -217,16 +217,6 @@ export default { }) } - if (this.results.episodes?.length) { - shelves.push({ - id: 'episodes', - label: 'Episodes', - labelStringKey: 'LabelEpisodes', - type: 'episode', - entities: this.results.episodes.map((res) => res.libraryItem) - }) - } - if (this.results.series?.length) { shelves.push({ id: 'series', diff --git a/client/components/app/BookShelfToolbar.vue b/client/components/app/BookShelfToolbar.vue index 95e7c378..01ab4fa7 100644 --- a/client/components/app/BookShelfToolbar.vue +++ b/client/components/app/BookShelfToolbar.vue @@ -274,10 +274,15 @@ export default { isAuthorsPage() { return this.page === 'authors' }, + isAlbumsPage() { + return this.page === 'albums' + }, numShowing() { return this.totalEntities }, entityName() { + if (this.isAlbumsPage) return 'Albums' + if (this.isPodcastLibrary) return this.$strings.LabelPodcasts if (!this.page) return this.$strings.LabelBooks if (this.isSeriesPage) return this.$strings.LabelSeries diff --git a/client/components/app/ConfigSideNav.vue b/client/components/app/ConfigSideNav.vue index 32e7e694..50fa7a06 100644 --- a/client/components/app/ConfigSideNav.vue +++ b/client/components/app/ConfigSideNav.vue @@ -70,11 +70,6 @@ export default { title: this.$strings.HeaderUsers, path: '/config/users' }, - { - id: 'config-api-keys', - title: this.$strings.HeaderApiKeys, - path: '/config/api-keys' - }, { id: 'config-sessions', title: this.$strings.HeaderListeningSessions, diff --git a/client/components/app/LazyBookshelf.vue b/client/components/app/LazyBookshelf.vue index 854b61b2..61331fb9 100644 --- a/client/components/app/LazyBookshelf.vue +++ b/client/components/app/LazyBookshelf.vue @@ -778,6 +778,10 @@ export default { windowResize() { this.executeRebuild() }, + socketInit() { + // Server settings are set on socket init + this.executeRebuild() + }, initListeners() { window.addEventListener('resize', this.windowResize) @@ -790,6 +794,7 @@ export default { }) this.$eventBus.$on('bookshelf_clear_selection', this.clearSelectedEntities) + this.$eventBus.$on('socket_init', this.socketInit) this.$eventBus.$on('user-settings', this.settingsUpdated) if (this.$root.socket) { @@ -821,6 +826,7 @@ export default { } this.$eventBus.$off('bookshelf_clear_selection', this.clearSelectedEntities) + this.$eventBus.$off('socket_init', this.socketInit) this.$eventBus.$off('user-settings', this.settingsUpdated) if (this.$root.socket) { diff --git a/client/components/app/SideRail.vue b/client/components/app/SideRail.vue index 5f364201..2b05ef36 100644 --- a/client/components/app/SideRail.vue +++ b/client/components/app/SideRail.vue @@ -116,7 +116,7 @@
-

v{{ $config.version }}

+

v{{ $config.version }}

Update

{{ Source }}

diff --git a/client/components/cards/AuthorCard.vue b/client/components/cards/AuthorCard.vue index 05347393..82645c57 100644 --- a/client/components/cards/AuthorCard.vue +++ b/client/components/cards/AuthorCard.vue @@ -71,6 +71,9 @@ export default { coverHeight() { return this.cardHeight }, + userToken() { + return this.store.getters['user/getToken'] + }, _author() { return this.author || {} }, diff --git a/client/components/cards/BookMatchCard.vue b/client/components/cards/BookMatchCard.vue index 09b963c5..87aa0a71 100644 --- a/client/components/cards/BookMatchCard.vue +++ b/client/components/cards/BookMatchCard.vue @@ -13,17 +13,9 @@

{{ book.publishedYear }}

- -
-
-

{{ $getString('LabelByAuthor', [book.author]) }}

-

{{ $strings.LabelNarrators }}: {{ book.narrator }}

-

{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}

-
-
-
{{ $strings.LabelMatchConfidence }}: {{ (book.matchConfidence * 100).toFixed(0) }}%
-
- +

{{ $getString('LabelByAuthor', [book.author]) }}

+

{{ $strings.LabelNarrators }}: {{ book.narrator }}

+

{{ $strings.LabelDuration }}: {{ $elapsedPrettyExtended(bookDuration, false) }} {{ bookDurationComparison }}

diff --git a/client/components/cards/EpisodeSearchCard.vue b/client/components/cards/EpisodeSearchCard.vue index 8be6a3a3..e69de29b 100644 --- a/client/components/cards/EpisodeSearchCard.vue +++ b/client/components/cards/EpisodeSearchCard.vue @@ -1,60 +0,0 @@ - - - - - diff --git a/client/components/cards/LazyAlbumCard.vue b/client/components/cards/LazyAlbumCard.vue new file mode 100644 index 00000000..9b722795 --- /dev/null +++ b/client/components/cards/LazyAlbumCard.vue @@ -0,0 +1,142 @@ + + + diff --git a/client/components/cards/LazyBookCard.vue b/client/components/cards/LazyBookCard.vue index 955e18d9..35c959fa 100644 --- a/client/components/cards/LazyBookCard.vue +++ b/client/components/cards/LazyBookCard.vue @@ -101,8 +101,7 @@

- Episode - #{{ recentEpisodeNumber }} + Episode #{{ recentEpisodeNumber }}

@@ -199,10 +198,7 @@ export default { return this.store.getters['user/getSizeMultiplier'] }, dateFormat() { - return this.store.getters['getServerSetting']('dateFormat') - }, - timeFormat() { - return this.store.getters['getServerSetting']('timeFormat') + return this.store.state.serverSettings.dateFormat }, _libraryItem() { return this.libraryItem || {} @@ -349,10 +345,6 @@ export default { if (this.mediaMetadata.publishedYear) return this.$getString('LabelPublishedDate', [this.mediaMetadata.publishedYear]) return '\u00A0' } - if (this.orderBy === 'progress') { - if (!this.userProgressLastUpdated) return '\u00A0' - return this.$getString('LabelLastProgressDate', [this.$formatDatetime(this.userProgressLastUpdated, this.dateFormat, this.timeFormat)]) - } return null }, episodeProgress() { @@ -385,10 +377,6 @@ export default { let progressPercent = this.itemIsFinished ? 1 : this.booksInSeries ? this.seriesProgressPercent : this.useEBookProgress ? this.userProgress?.ebookProgress || 0 : this.userProgress?.progress || 0 return Math.max(Math.min(1, progressPercent), 0) }, - userProgressLastUpdated() { - if (!this.userProgress) return null - return this.userProgress.lastUpdate - }, itemIsFinished() { if (this.booksInSeries) return this.seriesIsFinished return this.userProgress ? !!this.userProgress.isFinished : false diff --git a/client/components/cards/LazySeriesCard.vue b/client/components/cards/LazySeriesCard.vue index 34cea7e2..3532095b 100644 --- a/client/components/cards/LazySeriesCard.vue +++ b/client/components/cards/LazySeriesCard.vue @@ -71,7 +71,7 @@ export default { return this.height * this.sizeMultiplier }, dateFormat() { - return this.store.getters['getServerSetting']('dateFormat') + return this.store.state.serverSettings.dateFormat }, labelFontSize() { if (this.width < 160) return 0.75 diff --git a/client/components/controls/GlobalSearch.vue b/client/components/controls/GlobalSearch.vue index 6f3a819b..bc9a2368 100644 --- a/client/components/controls/GlobalSearch.vue +++ b/client/components/controls/GlobalSearch.vue @@ -39,15 +39,6 @@ -

{{ $strings.LabelEpisodes }}

- -

{{ $strings.LabelAuthors }}