From ad01868a9ff3f17b27ca61533ada1bcafb4278a9 Mon Sep 17 00:00:00 2001 From: gcch Date: Wed, 5 Mar 2025 09:10:16 +0100 Subject: [PATCH] 2025-03-05 --- Dockerfile | 29 ----- bun.lock | 12 +- containers/Dockerfile | 53 +++++++++ {docker => containers/angie}/default.conf | 0 cspell.json | 3 +- docker-compose.yaml | 2 +- package.json | 8 +- src/components/TmdbSearchResults.vue | 6 +- src/components/dialogs/EditEntryDialog.vue | 107 ++++++++++++++---- src/components/loading/LoadingBox.vue | 4 + ...karen_page.sql => 0000_goofy_vanisher.sql} | 11 +- src/db/drizzle/meta/0000_snapshot.json | 68 ++++++++++- src/db/drizzle/meta/_journal.json | 4 +- src/db/schemas/constants.ts | 11 ++ src/db/schemas/entries.ts | 8 +- src/db/schemas/works.ts | 6 + src/libs/apis/tmdb/orders.ts | 4 +- src/libs/search/schemas.ts | 18 +-- src/pages/SearchPage.vue | 15 ++- src/services/images.ts | 20 +++- src/services/migrations.ts | 2 +- src/styles/themes/default.css | 4 + tsconfig.json | 30 ++++- 23 files changed, 328 insertions(+), 97 deletions(-) delete mode 100644 Dockerfile create mode 100644 containers/Dockerfile rename {docker => containers/angie}/default.conf (100%) create mode 100644 src/components/loading/LoadingBox.vue rename src/db/drizzle/{0000_unusual_karen_page.sql => 0000_goofy_vanisher.sql} (88%) diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 1b9c7ec..0000000 --- a/Dockerfile +++ /dev/null @@ -1,29 +0,0 @@ -FROM oven/bun:1 AS base -WORKDIR /usr/src/app - -# Installe les dépendances de développement. -FROM base AS install -RUN mkdir -p /temp/dev -COPY package.json bun.lock /temp/dev/ -RUN cd /temp/dev && bun install --frozen-lockfile -# Installe les dépendances de production. -RUN mkdir -p /temp/prod -COPY package.json bun.lock /temp/prod/ -RUN cd /temp/prod && bun install --frozen-lockfile --production - -# Récupère node_modules et les fichiers du projet. -FROM base AS prerelease -COPY --from=install /temp/dev/node_modules/ node_modules -COPY . . -# Compile le projet. -ENV NODE_ENV=production -RUN bun --bun vite build - -# Créé le nécessaire pour Angie, le proxy inversé servant l'application. -FROM docker.angie.software/angie:minimal AS release -COPY --from=prerelease /usr/src/app/dist/ /usr/share/angie/html/ -COPY ./docker/default.conf /etc/angie/http.d/default.conf - -# Démarre Angie. -EXPOSE 80 -CMD ["angie", "-g", "daemon off;"] diff --git a/bun.lock b/bun.lock index 6f92dbf..a448063 100644 --- a/bun.lock +++ b/bun.lock @@ -213,11 +213,11 @@ "@effect/experimental": ["@effect/experimental@0.41.4", "", { "dependencies": { "msgpackr": "^1.10.2", "uuid": "^11.0.3" }, "peerDependencies": { "@effect/platform": "^0.77.4", "@effect/platform-node": "^0.73.4", "effect": "^3.13.4", "ioredis": "^5", "lmdb": "^3", "ws": "^8" }, "optionalPeers": ["@effect/platform-node", "ioredis", "lmdb", "ws"] }, "sha512-celrEhl/K2Eis906d3/oOv/3/w+M1jcONvrChG5qQgWYo6CJjSJm4xXopcTbiBhxAhvkd06zABkB3tr/lptUCA=="], - "@effect/platform": ["@effect/platform@0.77.4", "", { "dependencies": { "find-my-way-ts": "^0.1.5", "multipasta": "^0.2.5" }, "peerDependencies": { "effect": "^3.13.4" } }, "sha512-3jBDn4bAWqRSKARmoMY2NmMxIMM5Py5BqJdUuYJqPyI6jbz7qkOMwgeaF62rWqiqhQLHLTgU4QL/cPwyZl7mVg=="], + "@effect/platform": ["@effect/platform@0.77.6", "", { "dependencies": { "find-my-way-ts": "^0.1.5", "multipasta": "^0.2.5" }, "peerDependencies": { "effect": "^3.13.6" } }, "sha512-ghhLNyj/UoQvmp2I29nqngMlAzQB72BhjUKcOA58cUPaUUwNy3K2jmUAzdU6SB3RHIObsX44CM/jXZiYfTv59A=="], "@effect/sql": ["@effect/sql@0.30.4", "", { "dependencies": { "@opentelemetry/semantic-conventions": "^1.25.1", "uuid": "^11.0.3" }, "peerDependencies": { "@effect/experimental": "^0.41.4", "@effect/platform": "^0.77.4", "effect": "^3.13.4" } }, "sha512-hyGOZsNRlw09yqBcXCTr1o2+vVIVxEnBMNZzi0ZX5JbS8JBT05O7xAu3OYEzSU6bC5i+uVpJJUgdx2zDlOLkeA=="], - "@effect/sql-drizzle": ["@effect/sql-drizzle@0.29.4", "", { "peerDependencies": { "@effect/sql": "^0.30.4", "drizzle-orm": "^0.31", "effect": "^3.13.4" } }, "sha512-Hz8RypUu5g/V3DVmD0WLK+Nqy9H/V/GA2ew41sSZhJ0fTmxaCoQ08it23qypeNN66CCJNF5uE9OWnHl10cfKGA=="], + "@effect/sql-drizzle": ["@effect/sql-drizzle@0.29.6", "", { "peerDependencies": { "@effect/sql": "^0.30.6", "drizzle-orm": "^0.31", "effect": "^3.13.6" } }, "sha512-ST3HEXQZfp9tUXKOKxpfh9bbTukJsomyXXzZLvPo/KvF8nPpRltovYNT55Dsk+0vN8JyOHri+mX8hKBG5JzXcA=="], "@esbuild-kit/core-utils": ["@esbuild-kit/core-utils@3.3.2", "", { "dependencies": { "esbuild": "~0.18.20", "source-map-support": "^0.5.21" } }, "sha512-sPRAnw9CdSsRmEtnsl2WXWdyquogVpB3yZ3dgwJfe8zrOzTsV7cJvmwrKVa+0ma5BoiGJ+BoqkMvawbayKUsqQ=="], @@ -623,7 +623,7 @@ "easy-table": ["easy-table@1.2.0", "", { "dependencies": { "ansi-regex": "^5.0.1" }, "optionalDependencies": { "wcwidth": "^1.0.1" } }, "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww=="], - "effect": ["effect@3.13.4", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-XZgCImyBpONuKdsBRk37JfV7242yxTu8r/TcL5ZELIyqRbMYa+Prr86cz1INxXi7iFXfU0havZkCJGyYp1BsiA=="], + "effect": ["effect@3.13.6", "", { "dependencies": { "@standard-schema/spec": "^1.0.0", "fast-check": "^3.23.1" } }, "sha512-NKmzyIuOb2UuHFPRz9EYScbhMBxXkzjPRuu+4axE+hMk1f0U7TZxzi2CP3TVVxA2kzvh00aBQEbyH7Opq4PnWg=="], "electron-to-chromium": ["electron-to-chromium@1.5.109", "", {}, "sha512-AidaH9JETVRr9DIPGfp1kAarm/W6hRJTPuCnkF+2MqhF4KaAgRIcBc8nvjk+YMXZhwfISof/7WG29eS4iGxQLQ=="], @@ -649,7 +649,7 @@ "eslint-plugin-perfectionist": ["eslint-plugin-perfectionist@4.9.0", "", { "dependencies": { "@typescript-eslint/types": "^8.24.0", "@typescript-eslint/utils": "^8.24.0", "natural-orderby": "^5.0.0" }, "peerDependencies": { "eslint": ">=8.0.0" } }, "sha512-76lDfJnonOcXGW3bEXuqhEGId0LrOlvIE1yLHvK/eKMMPOc0b43KchAIR2Bdbqlg+LPXU5/Q+UzuzkO+cWHT6w=="], - "eslint-plugin-vue": ["eslint-plugin-vue@9.32.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "globals": "^13.24.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "vue-eslint-parser": "^9.4.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, "sha512-b/Y05HYmnB/32wqVcjxjHZzNpwxj1onBOvqW89W+V+XNG1dRuaFbNd3vT9CLbr2LXjEoq+3vn8DanWf7XU22Ug=="], + "eslint-plugin-vue": ["eslint-plugin-vue@10.0.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", "natural-compare": "^1.4.0", "nth-check": "^2.1.1", "postcss-selector-parser": "^6.0.15", "semver": "^7.6.3", "xml-name-validator": "^4.0.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" } }, "sha512-XKckedtajqwmaX6u1VnECmZ6xJt+YvlmMzBPZd+/sI3ub2lpYZyFnsyWo7c3nMOQKJQudeyk1lw/JxdgeKT64w=="], "eslint-scope": ["eslint-scope@8.2.0", "", { "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" } }, "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A=="], @@ -1037,8 +1037,6 @@ "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "type-fest": ["type-fest@0.20.2", "", {}, "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="], - "typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], "typescript-eslint": ["typescript-eslint@8.25.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.25.0", "@typescript-eslint/parser": "8.25.0", "@typescript-eslint/utils": "8.25.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.8.0" } }, "sha512-TxRdQQLH4g7JkoFlYG3caW5v1S6kEkz8rqt80iQJZUYPq1zD1Ra7HfQBJJ88ABRaMvHAXnwRvRB4V+6sQ9xN5Q=="], @@ -1129,8 +1127,6 @@ "eslint/file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="], - "eslint-plugin-vue/globals": ["globals@13.24.0", "", { "dependencies": { "type-fest": "^0.20.2" } }, "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ=="], - "fast-glob/@nodelib/fs.walk": ["@nodelib/fs.walk@1.2.8", "", { "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" } }, "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg=="], "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="], diff --git a/containers/Dockerfile b/containers/Dockerfile new file mode 100644 index 0000000..073334f --- /dev/null +++ b/containers/Dockerfile @@ -0,0 +1,53 @@ +FROM oven/bun:1 AS base +WORKDIR /usr/src/app + +# Définis les variables d'environnement. +ARG VERSION="0.1" + +# Installe les dépendances de développement. +FROM base AS install +RUN mkdir -p /temp/dev +COPY package.json bun.lock /temp/dev/ +RUN cd /temp/dev && bun install --frozen-lockfile +# Installe les dépendances de production. +RUN mkdir -p /temp/prod +COPY package.json bun.lock /temp/prod/ +RUN cd /temp/prod && bun install --frozen-lockfile --production + +# Récupère node_modules et les fichiers du projet. +FROM base AS prerelease +COPY --from=install /temp/dev/node_modules/ node_modules +COPY . . +# Compile le projet. +ENV NODE_ENV=production +RUN bun --bun vite build + +# Créé le nécessaire pour Angie, le proxy inversé servant l'application. +FROM docker.angie.software/angie:minimal AS release +COPY --from=prerelease /usr/src/app/dist/ /usr/share/angie/html/ +COPY containers/angie/default.conf /etc/angie/http.d/default.conf + +# Met en place les métadonnées OCI. +LABEL org.opencontainers.image.title=journal-media-vue \ + org.opencontainers.image.description="A modern client-server application for the Soulseek file sharing network" \ + org.opencontainers.image.authors="gcch" \ + org.opencontainers.image.vendor="gcch" \ + org.opencontainers.image.licenses=AGPL-3.0 +# org.opencontainers.image.url=https://slskd.org \ +# org.opencontainers.image.source=https://github.com/slskd/slskd \ +# org.opencontainers.image.documentation=https://github.com/slskd/slskd \ +# org.opencontainers.image.version=$VERSION \ +# org.opencontainers.image.revision=$REVISION \ +# org.opencontainers.image.created=$BUILD_DATE + +# Démarre Angie. +EXPOSE 80 +CMD ["angie", "-g", "daemon off;"] + +# Met en place une vérification périodique de la bonne santé du conteneur. +HEALTHCHECK \ + --interval=60s \ + --timeout=3s \ + --start-period=5s \ + --retries=3 \ + CMD wget -q -O - http://localhost:80/ diff --git a/docker/default.conf b/containers/angie/default.conf similarity index 100% rename from docker/default.conf rename to containers/angie/default.conf diff --git a/cspell.json b/cspell.json index f8a4ff1..5d546ed 100644 --- a/cspell.json +++ b/cspell.json @@ -14,6 +14,7 @@ "quartary", "fieldset", "tabindex", - "currentcolor" + "currentcolor", + "labelledby" ] } diff --git a/docker-compose.yaml b/docker-compose.yaml index 3987542..91eb7f2 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,7 +2,7 @@ services: app: build: context: . - dockerfile: Dockerfile + dockerfile: containers/Dockerfile container_name: journal-media-vue ports: - 127.0.0.1:8080:8 diff --git a/package.json b/package.json index 0beb834..5cdc9fd 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,14 @@ "type": "module", "private": true, "dependencies": { - "@effect/platform": "^0.77.4", - "@effect/sql-drizzle": "^0.29.4", + "@effect/platform": "^0.77.6", + "@effect/sql-drizzle": "^0.29.6", "@thi.ng/color-palettes": "^1.4.36", "@thi.ng/pixel": "^7.3.19", "@thi.ng/pixel-dither": "^1.1.159", "a11y-dialog": "^8.1.1", "drizzle-orm": "^0.40.0", - "effect": "^3.13.4", + "effect": "^3.13.6", "pinia": "^3.0.1", "sqlocal": "^0.14.0", "vue": "^3.5.13", @@ -29,7 +29,7 @@ "drizzle-kit": "^0.30.5", "eslint": "^9.21.0", "eslint-plugin-perfectionist": "^4.9.0", - "eslint-plugin-vue": "^9.32.0", + "eslint-plugin-vue": "^10.0.0", "globals": "^16.0.0", "jiti": "^2.4.2", "knip": "^5.45.0", diff --git a/src/components/TmdbSearchResults.vue b/src/components/TmdbSearchResults.vue index 7cac717..fa7d84a 100644 --- a/src/components/TmdbSearchResults.vue +++ b/src/components/TmdbSearchResults.vue @@ -90,12 +90,12 @@ tabindex="0" @click="onRowClicked(result[0])" @keypress="onRowClicked(result[0])" > - {{ result[1].original_result_index }} + {{ result[1].originalResultIndex }} - {{ result[1].original_title }} + {{ result[1].originalTitle }} - {{ result[1].release_date }} + {{ result[1].releaseDate }} {{ result[1].popularity }} diff --git a/src/components/dialogs/EditEntryDialog.vue b/src/components/dialogs/EditEntryDialog.vue index e99dde0..6c9d12f 100644 --- a/src/components/dialogs/EditEntryDialog.vue +++ b/src/components/dialogs/EditEntryDialog.vue @@ -1,37 +1,62 @@ + + diff --git a/src/db/drizzle/0000_unusual_karen_page.sql b/src/db/drizzle/0000_goofy_vanisher.sql similarity index 88% rename from src/db/drizzle/0000_unusual_karen_page.sql rename to src/db/drizzle/0000_goofy_vanisher.sql index 6f5591f..8cc4796 100644 --- a/src/db/drizzle/0000_unusual_karen_page.sql +++ b/src/db/drizzle/0000_goofy_vanisher.sql @@ -1,7 +1,10 @@ CREATE TABLE `diary_entries` ( + `appreciation` text NOT NULL, `art_work_id` integer NOT NULL, + `commentary` text, `date_created` integer NOT NULL, `date_modified` integer NOT NULL, + `date_obtained` integer NOT NULL, `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, `state_id` integer NOT NULL, FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action, @@ -59,4 +62,10 @@ CREATE TABLE `media_types` ( ); --> statement-breakpoint CREATE UNIQUE INDEX `media_types_name_unique` ON `media_types` (`name`);--> statement-breakpoint -CREATE UNIQUE INDEX `media_types_slug_unique` ON `media_types` (`slug`); \ No newline at end of file +CREATE UNIQUE INDEX `media_types_slug_unique` ON `media_types` (`slug`);--> statement-breakpoint +CREATE TABLE `posters` ( + `art_work_id` integer NOT NULL, + `id` integer PRIMARY KEY AUTOINCREMENT NOT NULL, + `image` blob NOT NULL, + FOREIGN KEY (`art_work_id`) REFERENCES `art_works`(`id`) ON UPDATE no action ON DELETE no action +); diff --git a/src/db/drizzle/meta/0000_snapshot.json b/src/db/drizzle/meta/0000_snapshot.json index 0923fb6..d8392a3 100644 --- a/src/db/drizzle/meta/0000_snapshot.json +++ b/src/db/drizzle/meta/0000_snapshot.json @@ -1,12 +1,19 @@ { "version": "6", "dialect": "sqlite", - "id": "8b217318-7662-4f81-bc55-92d5c6ddf2b2", + "id": "01d72a44-6bdd-4f2a-b1dd-34546aa1b734", "prevId": "00000000-0000-0000-0000-000000000000", "tables": { "diary_entries": { "name": "diary_entries", "columns": { + "appreciation": { + "name": "appreciation", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, "art_work_id": { "name": "art_work_id", "type": "integer", @@ -14,6 +21,13 @@ "notNull": true, "autoincrement": false }, + "commentary": { + "name": "commentary", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, "date_created": { "name": "date_created", "type": "integer", @@ -28,6 +42,13 @@ "notNull": true, "autoincrement": false }, + "date_obtained": { + "name": "date_obtained", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, "id": { "name": "id", "type": "integer", @@ -404,6 +425,51 @@ "compositePrimaryKeys": {}, "uniqueConstraints": {}, "checkConstraints": {} + }, + "posters": { + "name": "posters", + "columns": { + "art_work_id": { + "name": "art_work_id", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": true + }, + "image": { + "name": "image", + "type": "blob", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": { + "posters_art_work_id_art_works_id_fk": { + "name": "posters_art_work_id_art_works_id_fk", + "tableFrom": "posters", + "tableTo": "art_works", + "columnsFrom": [ + "art_work_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} } }, "views": {}, diff --git a/src/db/drizzle/meta/_journal.json b/src/db/drizzle/meta/_journal.json index de36f89..207d6cd 100644 --- a/src/db/drizzle/meta/_journal.json +++ b/src/db/drizzle/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "6", - "when": 1740814587298, - "tag": "0000_unusual_karen_page", + "when": 1741093519331, + "tag": "0000_goofy_vanisher", "breakpoints": true } ] diff --git a/src/db/schemas/constants.ts b/src/db/schemas/constants.ts index a616ed8..40fe09c 100644 --- a/src/db/schemas/constants.ts +++ b/src/db/schemas/constants.ts @@ -20,3 +20,14 @@ export const DIARY_ENTRY_STATES = { /** Un média ayant été regardé au mois une fois. */ WATCHED: "watched", } as const; + +export const APPRECIATION_STATES = { + /** Oeuvre appréciée. */ + APPRECIATED: "appreciated", + /** Oeuvre non appréciée (détestée). */ + DISLIKED: "disliked", + /** Oeuvre laissant de marbre. */ + NEUTRAL: "neutral", + /** Appréciation inconnue. */ + UNKNOWN: "unknown", +} as const; diff --git a/src/db/schemas/entries.ts b/src/db/schemas/entries.ts index 3ae3683..f99fbd0 100644 --- a/src/db/schemas/entries.ts +++ b/src/db/schemas/entries.ts @@ -5,15 +5,18 @@ import * as t from "drizzle-orm/sqlite-core"; import { sqliteTable as table } from "drizzle-orm/sqlite-core"; import { Schema } from "effect"; -import { DIARY_ENTRY_STATES } from "./constants"; +import { APPRECIATION_STATES, DIARY_ENTRY_STATES } from "./constants"; import { ArtWorks, Genres } from "./works"; // Tables export const DiaryEntries = table("diary_entries", { + appreciation: t.text("appreciation").$type>().notNull(), artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id).notNull(), + commentary: t.text("commentary").notNull(), dateCreated: t.integer("date_created", { mode: "timestamp" }).notNull(), dateModified: t.integer("date_modified", { mode: "timestamp" }).notNull(), + dateObtained: t.integer("date_obtained", { mode: "timestamp" }).notNull(), id: t.integer("id").primaryKey({ autoIncrement: true }), stateId: t.integer("state_id").references((): AnySQLiteColumn => DiaryEntriesStates.id).notNull(), }); @@ -38,9 +41,12 @@ export const Viewings = table("viewings", { // Schémas export const DiaryEntrySchema = Schema.Struct({ + appreciation: Schema.Enums(APPRECIATION_STATES), artWorkId: Schema.NonNegativeInt, + commentary: Schema.String.pipe(Schema.optional), dateCreated: Schema.Number, dateModified: Schema.Number, + dateObtained: Schema.Number, id: Schema.NonNegativeInt, stateId: Schema.NonNegativeInt, }); diff --git a/src/db/schemas/works.ts b/src/db/schemas/works.ts index f08dcd5..fff8251 100644 --- a/src/db/schemas/works.ts +++ b/src/db/schemas/works.ts @@ -15,6 +15,12 @@ export const MediaTypes = table("media_types", { slug: t.text("slug").$type>().notNull().unique(), }); +export const Posters = table("posters", { + artWorkId: t.integer("art_work_id").references((): AnySQLiteColumn => ArtWorks.id).notNull(), + id: t.integer("id").primaryKey({ autoIncrement: true }), + image: t.blob("image", { mode: "buffer" }).notNull(), +}); + export const ArtWorks = table("art_works", { coverPath: t.text("cover_path").unique(), dateCreated: t.integer("date_created", { mode: "timestamp" }).notNull(), diff --git a/src/libs/apis/tmdb/orders.ts b/src/libs/apis/tmdb/orders.ts index 3cba5b4..25fb360 100644 --- a/src/libs/apis/tmdb/orders.ts +++ b/src/libs/apis/tmdb/orders.ts @@ -47,7 +47,7 @@ export const getTmdbSortFunction = (sortData: TmdbSortData) => export const byOriginalIndexAscending = Order.mapInput( Order.number, - (data: [number, MergedTmdbLocalData]) => data[1].original_result_index, + (data: [number, MergedTmdbLocalData]) => data[1].originalResultIndex, ); export const byPopularityAscending = Order.mapInput( Order.number, @@ -55,7 +55,7 @@ export const byPopularityAscending = Order.mapInput( ); export const byReleaseDateAscending = Order.mapInput( Order.string, - (data: [number, MergedTmdbLocalData]) => data[1].release_date, + (data: [number, MergedTmdbLocalData]) => data[1].releaseDate, ); export const byTitleAscending = Order.mapInput( Order.string, diff --git a/src/libs/search/schemas.ts b/src/libs/search/schemas.ts index aaef875..83522ae 100644 --- a/src/libs/search/schemas.ts +++ b/src/libs/search/schemas.ts @@ -1,4 +1,4 @@ -import { MEDIA_TYPES } from "@/db/schemas/constants"; +import { APPRECIATION_STATES, MEDIA_TYPES } from "@/db/schemas/constants"; import { Schema } from "effect"; export class SearchPageQueryParams extends Schema.Class("SearchPageQueryParams")({ @@ -8,20 +8,24 @@ export class SearchPageQueryParams extends Schema.Class(" }) {} export class MergedTmdbLocalData extends Schema.Class("MergedTmdbLocalData")({ - artWorkCoverPath: Schema.Union(Schema.String, Schema.Null), artWorkId: Schema.NonNegativeInt.pipe(Schema.optional), artWorkMediumTypeId: Schema.NonNegativeInt.pipe(Schema.optional), + entryAppreciation: Schema.Enums(APPRECIATION_STATES).pipe(Schema.optional), + entryCommentary: Schema.String.pipe(Schema.optional), entryDateCreated: Schema.Date.pipe(Schema.optional), entryDateModified: Schema.Date.pipe(Schema.optional), + entryDateObtained: Schema.Date.pipe(Schema.optional), entryId: Schema.NonNegativeInt.pipe(Schema.optional), entryStateId: Schema.NonNegativeInt.pipe(Schema.optional), - genre_ids: Schema.Array(Schema.NonNegativeInt), - original_language: Schema.String, - original_result_index: Schema.Int, - original_title: Schema.String, + genreIds: Schema.Array(Schema.NonNegativeInt), + originalLanguage: Schema.String, + originalResultIndex: Schema.Int, + originalTitle: Schema.String, overview: Schema.String, popularity: Schema.Number, - release_date: Schema.String, + posterBlob: Schema.Unknown.pipe(Schema.optional), + posterUrl: Schema.Union(Schema.String, Schema.Null), + releaseDate: Schema.String, title: Schema.String, tmdbId: Schema.NonNegativeInt, }) {} diff --git a/src/pages/SearchPage.vue b/src/pages/SearchPage.vue index 1c7bfa1..a3b45de 100644 --- a/src/pages/SearchPage.vue +++ b/src/pages/SearchPage.vue @@ -153,20 +153,23 @@ result.id, yield* Schema.decodeUnknown(MergedTmdbLocalData)( { - artWorkCoverPath: result.poster_path, artWorkId: artWork?.id, artWorkMediumTypeId: artWork?.mediumTypeId, + entryAppreciation: entry?.appreciation, + entryCommentary: entry?.commentary, entryDateCreated: entry?.dateCreated, entryDateModified: entry?.dateModified, + entryDateObtained: entry?.dateObtained, entryId: entry?.id, entryStateId: entry?.stateId, - genre_ids: result.genre_ids, - original_language: result.original_language, - original_result_index: index, - original_title: result.original_title, + genreIds: result.genre_ids, + originalLanguage: result.original_language, + originalResultIndex: index, + originalTitle: result.original_title, overview: result.overview, popularity: result.popularity, - release_date: result.release_date, + posterUrl: result.poster_path, + releaseDate: result.release_date, title: result.title, tmdbId: result.id, } satisfies MergedTmdbLocalData, diff --git a/src/services/images.ts b/src/services/images.ts index e695666..cdec2ef 100644 --- a/src/services/images.ts +++ b/src/services/images.ts @@ -1,5 +1,13 @@ import { asInt } from "@thi.ng/color-palettes"; -import { ARGB8888, canvasFromPixelBuffer, defIndexed, imageFromURL, intBufferFromImage } from "@thi.ng/pixel"; +import { + ARGB8888, + canvasFromPixelBuffer, + defIndexed, + imageFromURL, + IntBuffer, + intBufferFromImage, + type IntFormat, +} from "@thi.ng/pixel"; import { ATKINSON, ditherWith } from "@thi.ng/pixel-dither"; import { Data, Effect, pipe } from "effect"; @@ -10,12 +18,12 @@ export class Images extends Effect.Service()("Images", { return { ditherImage: (image: HTMLImageElement, parent?: HTMLElement) => Effect.gen(function*() { - const buf = intBufferFromImage(image, ARGB8888).scale(0.8, "cubic"); - const theme = defIndexed(asInt(["salmon", "black"])); - const ditheredBuf = ditherWith(ATKINSON, buf.copy(), {}).as(theme); + const buf: IntBuffer = intBufferFromImage(image, ARGB8888).scale(0.8, "cubic"); + const theme: IntFormat = defIndexed(asInt(["salmon", "black"])); + const ditheredBuffer: IntBuffer = ditherWith(ATKINSON, buf.copy(), {}).as(theme); - const canvas = canvasFromPixelBuffer(ditheredBuf, parent, { pixelated: true }); - return canvas; + const canvas = canvasFromPixelBuffer(ditheredBuffer, parent, { pixelated: true }); + return { buffer: ditheredBuffer, canvas: canvas }; }), imageFromUrl: (url: URL) => pipe( diff --git a/src/services/migrations.ts b/src/services/migrations.ts index 1a83034..360250d 100644 --- a/src/services/migrations.ts +++ b/src/services/migrations.ts @@ -1,6 +1,6 @@ import type { SQLocalDrizzle } from "sqlocal/drizzle"; -import v0000 from "@/db/drizzle/0000_unusual_karen_page.sql?raw"; +import v0000 from "@/db/drizzle/0000_goofy_vanisher.sql?raw"; import { Data, Effect } from "effect"; import { LocalSqlite } from "./db"; diff --git a/src/styles/themes/default.css b/src/styles/themes/default.css index 8172902..015b7ee 100644 --- a/src/styles/themes/default.css +++ b/src/styles/themes/default.css @@ -48,6 +48,10 @@ body { } } +strong { + font-weight: var(--brkly-font-weight-semibold); +} + .container { --layout-center-max-width: 100%; --layout-center-inline-padding: var(--s1); diff --git a/tsconfig.json b/tsconfig.json index 65acbc7..415fe18 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -16,7 +16,13 @@ "isolatedModules": true, "jsx": "preserve", "jsxImportSource": "vue", - "lib": ["ESNext", "DOM", "DOM.Iterable", "DOM.AsyncIterable", "WebWorker"], + "lib": [ + "ESNext", + "DOM", + "DOM.Iterable", + "DOM.AsyncIterable", + "WebWorker" + ], "module": "ESNext", "moduleDetection": "force", "moduleResolution": "bundler", @@ -32,8 +38,12 @@ "noUncheckedSideEffectImports": false, "noUnusedLocals": true, "noUnusedParameters": true, - "paths": { "@/*": ["./src/*"] }, - "plugins": [{ "name": "@vue/typescript-plugin" }], + "paths": { + "@/*": ["./src/*"] + }, + "plugins": [ + { "name": "@vue/typescript-plugin" } + ], "resolveJsonModule": true, "skipDefaultLibCheck": true, "skipLibCheck": true, @@ -43,10 +53,20 @@ "strictNullChecks": true, "strictPropertyInitialization": true, "target": "ESNext", + "tsBuildInfoFile": ".cache/tsbuildinfo", "useDefineForClassFields": true, "useUnknownInCatchVariables": true, "verbatimModuleSyntax": true }, - "exclude": ["node_modules", "dist/", ".cache"], - "include": ["src/router/typed-routes.d.ts", "src/env.d.ts", "**/*", "**/*.vue"] + "exclude": [ + ".cache", + "dist/", + "node_modules" + ], + "include": [ + "**/*", + "**/*.vue", + "src/vite-env.d.ts", + "src/router/typed-routes.d.ts" + ] }