diff --git a/package-lock.json b/package-lock.json index 77f00b6e..55d8966e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5334,8 +5334,9 @@ "license": "ISC" }, "node_modules/@vue/devtools-api": { - "version": "6.2.0", - "license": "MIT" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz", + "integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==" }, "node_modules/@vue/eslint-config-standard-with-typescript": { "version": "8.0.0", @@ -18257,6 +18258,53 @@ "node": ">=4" } }, + "node_modules/pinia": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.17.tgz", + "integrity": "sha512-AtwLwEWQgIjofjgeFT+nxbnK5lT2QwQjaHNEDqpsi2AiCwf/NY78uWTeHUyEhiiJy8+sBmw0ujgQMoQbWiZDfA==", + "dependencies": { + "@vue/devtools-api": "^6.2.1", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.2.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/pinia-logger": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/pinia-logger/-/pinia-logger-1.3.2.tgz", + "integrity": "sha512-fLgWJHmLk+NI+rLdMCbGn5z+q/sbUZYkbcme0g4WErD6YhssuEYgFaJen6JEg5GKC3QVRALAofTB1ivnUDuVUA==", + "dev": true, + "dependencies": { + "pinia": "^2.0.9" + } + }, + "node_modules/pinia-plugin-persistedstate": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-2.1.1.tgz", + "integrity": "sha512-HUgsU5IRtM75eAQiIqzT3p1oPEuYH1/B2ipTMU++yE+FV0LkHaBswdKXs0RMWYCmugO8s62oxLTh/N1dLNp+5A==", + "peerDependencies": { + "pinia": "^2.0.0" + }, + "peerDependenciesMeta": { + "pinia": { + "optional": true + } + } + }, "node_modules/pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -22065,7 +22113,7 @@ }, "node_modules/typescript": { "version": "4.7.4", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -22345,6 +22393,31 @@ "@vue/shared": "3.2.37" } }, + "node_modules/vue-demi": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.6.tgz", + "integrity": "sha512-02NYpxgyGE2kKGegRPYlNQSL1UWfA/+JqvzhGCOYjhfbLWXU5QQX0+9pAm/R2sCOPKr5NBxVIab7fvFU0B1RxQ==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vue-eslint-parser": { "version": "9.0.3", "dev": true, @@ -24162,7 +24235,7 @@ }, "packages/core": { "name": "enterprise-core", - "version": "2.8.0", + "version": "2.8.1", "dependencies": { "ajv": "^8.11.0", "bcryptjs": "^2.4.3", @@ -24241,7 +24314,7 @@ }, "packages/scraper": { "name": "enterprise-scraper", - "version": "2.8.0", + "version": "2.8.1", "dependencies": { "cheerio": "^1.0.0-rc.12", "cloudscraper": "^4.6.0", @@ -24352,7 +24425,7 @@ }, "packages/server": { "name": "enterprise-server", - "version": "2.8.0", + "version": "2.8.1", "dependencies": { "ajv-formats": "^2.1.1", "brotli": "^1.3.3", @@ -24474,7 +24547,7 @@ }, "packages/website": { "name": "enterprise-website", - "version": "2.8.0", + "version": "2.8.1", "dependencies": { "@fortawesome/fontawesome-free": "^6.1.2", "@fortawesome/fontawesome-svg-core": "^6.1.2", @@ -24485,6 +24558,8 @@ "d3-scale-chromatic": "^3.0.0", "jsonschema": "^1.4.1", "mitt": "^3.0.0", + "pinia": "^2.0.17", + "pinia-plugin-persistedstate": "^2.1.1", "primeicons": "^5.0.0", "primevue": "^3.15.0", "prismjs": "1.28.0", @@ -24516,6 +24591,7 @@ "babel-core": "6.26.3", "babel-loader": "^8.2.5", "babel-plugin-prismjs": "^2.1.0", + "pinia-logger": "^1.3.2", "typescript": "^4.7.4", "webpack": "^5.74.0" } @@ -28230,7 +28306,9 @@ } }, "@vue/devtools-api": { - "version": "6.2.0" + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.2.1.tgz", + "integrity": "sha512-OEgAMeQXvCoJ+1x8WyQuVZzFo0wcyCmUR3baRVLmKBo1LmYZWMlRiXlux5jd0fqVJu6PfDbOrZItVqUEzLobeQ==" }, "@vue/eslint-config-standard-with-typescript": { "version": "8.0.0", @@ -31143,6 +31221,9 @@ "d3-scale-chromatic": "^3.0.0", "jsonschema": "^1.4.1", "mitt": "^3.0.0", + "pinia": "^2.0.17", + "pinia-logger": "^1.3.2", + "pinia-plugin-persistedstate": "^2.1.1", "primeicons": "^5.0.0", "primevue": "^3.15.0", "prismjs": "1.28.0", @@ -37144,6 +37225,30 @@ "version": "3.0.0", "dev": true }, + "pinia": { + "version": "2.0.17", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.17.tgz", + "integrity": "sha512-AtwLwEWQgIjofjgeFT+nxbnK5lT2QwQjaHNEDqpsi2AiCwf/NY78uWTeHUyEhiiJy8+sBmw0ujgQMoQbWiZDfA==", + "requires": { + "@vue/devtools-api": "^6.2.1", + "vue-demi": "*" + } + }, + "pinia-logger": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/pinia-logger/-/pinia-logger-1.3.2.tgz", + "integrity": "sha512-fLgWJHmLk+NI+rLdMCbGn5z+q/sbUZYkbcme0g4WErD6YhssuEYgFaJen6JEg5GKC3QVRALAofTB1ivnUDuVUA==", + "dev": true, + "requires": { + "pinia": "^2.0.9" + } + }, + "pinia-plugin-persistedstate": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-2.1.1.tgz", + "integrity": "sha512-HUgsU5IRtM75eAQiIqzT3p1oPEuYH1/B2ipTMU++yE+FV0LkHaBswdKXs0RMWYCmugO8s62oxLTh/N1dLNp+5A==", + "requires": {} + }, "pirates": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", @@ -39640,7 +39745,7 @@ }, "typescript": { "version": "4.7.4", - "dev": true + "devOptional": true }, "uglify-js": { "version": "3.13.9", @@ -39826,6 +39931,12 @@ "@vue/shared": "3.2.37" } }, + "vue-demi": { + "version": "0.13.6", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.6.tgz", + "integrity": "sha512-02NYpxgyGE2kKGegRPYlNQSL1UWfA/+JqvzhGCOYjhfbLWXU5QQX0+9pAm/R2sCOPKr5NBxVIab7fvFU0B1RxQ==", + "requires": {} + }, "vue-eslint-parser": { "version": "9.0.3", "dev": true, diff --git a/packages/server/src/api/episode.ts b/packages/server/src/api/episode.ts index 2b991f40..4ea62c58 100644 --- a/packages/server/src/api/episode.ts +++ b/packages/server/src/api/episode.ts @@ -75,10 +75,10 @@ export const getDisplayReleases = createHandler( until, read, uuid, - ignored_lists: ignoredLists, - required_lists: requiredLists, - ignored_media: ignoredMedia, - required_media: requiredMedia, + ignore_lists: ignoredLists, + only_lists: requiredLists, + ignore_media: ignoredMedia, + only_media: requiredMedia, }: GetDisplayReleases = req.query as any; const latestDate = getDate(latest); diff --git a/packages/server/src/validation.ts b/packages/server/src/validation.ts index 70a1fcab..60f4716c 100644 --- a/packages/server/src/validation.ts +++ b/packages/server/src/validation.ts @@ -307,10 +307,10 @@ export interface GetDisplayReleases { latest: string; until?: string; read?: boolean; - ignored_lists?: Id[]; - required_lists?: Id[]; - ignored_media?: Id[]; - required_media?: Id[]; + ignore_lists?: Id[]; + only_lists?: Id[]; + ignore_media?: Id[]; + only_media?: Id[]; } export const getDisplayReleasesSchema: JSONSchemaType = { @@ -321,10 +321,10 @@ export const getDisplayReleasesSchema: JSONSchemaType = { latest: string(), until: { type: "string", nullable: true }, read: { type: "boolean", nullable: true }, - ignored_lists: { type: "array", items: id(), nullable: true }, - ignored_media: { type: "array", items: id(), nullable: true }, - required_lists: { type: "array", items: id(), nullable: true }, - required_media: { type: "array", items: id(), nullable: true }, + ignore_lists: { type: "array", items: id(), nullable: true }, + ignore_media: { type: "array", items: id(), nullable: true }, + only_lists: { type: "array", items: id(), nullable: true }, + only_media: { type: "array", items: id(), nullable: true }, }, required: ["uuid", "latest"], }; diff --git a/packages/website/package.json b/packages/website/package.json index e24c7317..5ebcdbab 100644 --- a/packages/website/package.json +++ b/packages/website/package.json @@ -22,6 +22,8 @@ "d3-scale-chromatic": "^3.0.0", "jsonschema": "^1.4.1", "mitt": "^3.0.0", + "pinia": "^2.0.17", + "pinia-plugin-persistedstate": "^2.1.1", "@popperjs/core": "^2.11.5", "primevue": "^3.15.0", "primeicons": "^5.0.0", @@ -33,9 +35,7 @@ "vue": "^3.2.37", "vue-observe-visibility": "^1.0.0", "vue-prism-editor": "^2.0.0-alpha.2", - "vue-router": "^4.1.3", - "vuex": "^4.0.2", - "vuex-persistedstate": "^4.1.0" + "vue-router": "^4.1.3" }, "devDependencies": { "@types/bootstrap": "^5.2.1", @@ -54,6 +54,7 @@ "babel-core": "6.26.3", "babel-loader": "^8.2.5", "babel-plugin-prismjs": "^2.1.0", + "pinia-logger": "^1.3.2", "typescript": "^4.7.4", "webpack": "^5.74.0" }, diff --git a/packages/website/src/App.vue b/packages/website/src/App.vue index 0296c921..837d51b0 100644 --- a/packages/website/src/App.vue +++ b/packages/website/src/App.vue @@ -15,14 +15,18 @@ import { emitBusEvent, onBusEvent } from "./bus"; import { HttpClient } from "./Httpclient"; import { defineComponent } from "vue"; import { optimizedResize } from "./init"; +import { useSettingsStore } from "./store/settings"; +import { mapStores } from "pinia"; +import { useUserStore } from "./store/store"; export default defineComponent({ components: { appHeader, }, computed: { + ...mapStores(useUserStore), loggedIn(): boolean { - return this.$store.getters.loggedIn; + return this.userStore.loggedIn; }, }, watch: { @@ -33,20 +37,19 @@ export default defineComponent({ this.loginState(); } }, - "$store.settings.notifications": { - handler() { - // FIXME: does not fire somehow on changes - if (this.$store.state.settings.notifications.newReleases.enabled) { - this.$store.dispatch("activateNewReleases"); + }, + mounted() { + const store = useSettingsStore(); + store.$subscribe( + () => { + if (store.notifications.newReleases.enabled) { + store.activateNewReleases(); } else { - this.$store.dispatch("deactivateNewReleases"); + store.deactivateNewReleases(); } }, - immediate: true, - deep: true, - }, - }, - mounted() { + { immediate: true }, + ); onBusEvent("refresh:externalUser", (data: string) => this.refreshExternalUser(data)); onBusEvent("reset:modal", () => this.closeModal()); @@ -56,7 +59,7 @@ export default defineComponent({ async created() { if (this.loggedIn) { - await this.$store.dispatch("load"); + await this.userStore.load(); } else { await this.loginState(); } @@ -64,12 +67,13 @@ export default defineComponent({ methods: { closeModal() { - this.$store.commit("resetModal", "login"); - this.$store.commit("resetModal", "register"); - this.$store.commit("resetModal", "addList"); - this.$store.commit("resetModal", "addMedium"); - this.$store.commit("resetModal", "error"); - this.$store.commit("resetModal", "settings"); + // TODO: modal thingis + // this.$store.commit("resetModal", "login"); + // this.$store.commit("resetModal", "register"); + // this.$store.commit("resetModal", "addList"); + // this.$store.commit("resetModal", "addMedium"); + // this.$store.commit("resetModal", "error"); + // this.$store.commit("resetModal", "settings"); }, async loginState() { @@ -82,10 +86,7 @@ export default defineComponent({ console.log(`Logged In: ${this.loggedIn} New User: `, newUser); if (!this.loggedIn && newUser) { - await this.$store.dispatch("changeUser", { - user: newUser, - modal: "login", - }); + await this.userStore.changeUser(newUser, "login"); } else { throw Error(); } diff --git a/packages/website/src/Httpclient.ts b/packages/website/src/Httpclient.ts index 19a09bf1..55ddb5a4 100644 --- a/packages/website/src/Httpclient.ts +++ b/packages/website/src/Httpclient.ts @@ -1,4 +1,3 @@ -import { store } from "./store/store"; import { ExternalUser, Medium, @@ -35,6 +34,7 @@ import { } from "enterprise-server/dist/validation"; import { CustomHook, Id, Notification, Nullable, Paginated, SimpleUser } from "enterprise-core/dist/types"; import qs from "qs"; +import { useUserStore } from "./store/store"; /** * Allowed Methods for the API. @@ -295,7 +295,7 @@ type Query = Omit; export const HttpClient = { get loggedIn(): boolean { - return store.getters.loggedIn; + return useUserStore().loggedIn; }, _checkLogin: null as null | Promise, @@ -628,7 +628,8 @@ export const HttpClient = { if (this._checkLogin) { await this._checkLogin; } - const uuid = store.state.uuid; + const store = useUserStore(); + const uuid = store.uuid; if (!uuid) { throw Error("cannot send user message if no user is logged in"); @@ -637,7 +638,7 @@ export const HttpClient = { query = {}; } query.uuid = uuid; - query.session = store.state.session; + query.session = store.session; } const init = { method, @@ -669,10 +670,11 @@ export const HttpClient = { if (sessionResponse.ok) { const sessionResult: Nullable = await sessionResponse.json(); + const store = useUserStore(); if (sessionResult) { - store.dispatch("changeUser", { user: sessionResult }); + store.changeUser(sessionResult); } else { - store.commit("immediateLogout"); + store.immediateLogout(); } } } diff --git a/packages/website/src/components/app-header.vue b/packages/website/src/components/app-header.vue index 0633cb83..2f7c45ac 100644 --- a/packages/website/src/components/app-header.vue +++ b/packages/website/src/components/app-header.vue @@ -5,7 +5,7 @@ @@ -30,7 +26,7 @@

{{ item.content }}

- Read + Read @@ -42,7 +38,6 @@ diff --git a/packages/website/src/main.ts b/packages/website/src/main.ts index 755e3769..9d4ce598 100644 --- a/packages/website/src/main.ts +++ b/packages/website/src/main.ts @@ -8,7 +8,7 @@ import Router from "./router"; import AppComponent from "./App.vue"; import "./registerServiceWorker"; import VueObserveVisibility from "vue-observe-visibility"; -import { store } from "./store/store"; +import { pinia } from "./store/pinia"; import PrimeVue from "primevue/config"; import ToastService from "primevue/toastservice"; import Toast from "primevue/toast"; @@ -43,10 +43,10 @@ const app = createApp(AppComponent); app.config.devtools = true; app.use(VueObserveVisibility); app.use(Router); -app.use(store); app.use(PrimeVue); app.use(ToastService); app.use(ConfirmationService); +app.use(pinia); app.component("Toast", Toast); app.component("PButton", Button); app.component("SelectButton", SelectButton); diff --git a/packages/website/src/siteTypes.ts b/packages/website/src/siteTypes.ts index 50f389b3..839fd73c 100644 --- a/packages/website/src/siteTypes.ts +++ b/packages/website/src/siteTypes.ts @@ -1,4 +1,4 @@ -import { CustomHook, ExternalList, Id, JobHistoryItem as ServerJobHistoryItem, List } from "enterprise-core/dist/types"; +import { CustomHook, ExternalList, JobHistoryItem as ServerJobHistoryItem, List } from "enterprise-core/dist/types"; export type ClickListener = (evt: MouseEvent) => void; export type KeyboardListener = (evt: KeyboardEvent) => void; @@ -33,7 +33,7 @@ export interface TransferList { } export interface SimpleMedium { - id?: number; + id: number; countryOfOrigin?: string; languageOfOrigin?: string; author?: string; @@ -364,13 +364,6 @@ export interface VuexStore { name: string; uuid: string; modals: Modals; - releases: ReleaseStore; - externalUser: ExternalUserStore; - media: MediaStore; - lists: ListsStore; - news: NewsStore; - hooks: CustomHookStore; - settings: SettingStore; } export interface DisplayReleaseItem { @@ -401,22 +394,12 @@ export interface ReleaseStore { fetching: boolean; } -export interface ExternalUserStore { - externalUser: ExternalUser[]; -} - export type StoreList = StoreInternalList | StoreExternalList; export interface ListsStore { lists: StoreInternalList[]; } -export interface MediaStore { - media: Record; - secondaryMedia: Record; - episodesOnly: boolean; -} - export interface NewsStore { news: News[]; } @@ -425,13 +408,6 @@ export interface CustomHookStore { hooks: Record; } -export interface SettingStore { - notifications: { - push: boolean; - newReleases: { push: boolean; enabled: boolean; allMedia: boolean; media: Id[] }; - }; -} - export interface ScraperHook { id: number; name: string; diff --git a/packages/website/src/store/externaluser.ts b/packages/website/src/store/externaluser.ts index 83898278..7bfd020e 100644 --- a/packages/website/src/store/externaluser.ts +++ b/packages/website/src/store/externaluser.ts @@ -1,38 +1,33 @@ -import { ExternalUser, ExternalUserStore, StoreExternalList, VuexStore } from "../siteTypes"; -import { Module } from "vuex"; +import { ExternalUser, StoreExternalList } from "../siteTypes"; import { HttpClient } from "../Httpclient"; import { Id } from "enterprise-core/dist/types"; +import { defineStore } from "pinia"; -const module: Module = { +export const useExternalUserStore = defineStore("externaluser", { + persist: true, state: () => ({ - externalUser: [], + externalUser: [] as ExternalUser[], }), - getters: { - getExternalList(state) { - return (id: Id) => - state.externalUser.map((user) => user.lists.find((list) => list.id === id)).find((value) => value); - }, - }, - mutations: { - userExternalUser(state, externalUser: ExternalUser[]) { - state.externalUser = [...externalUser]; + actions: { + getExternalList(id: Id) { + return this.externalUser.map((user) => user.lists.find((list) => list.id === id)).find((value) => value); }, - addExternalUser(state, externalUser: ExternalUser) { + addExternalUserLocal(externalUser: ExternalUser) { externalUser.lists.forEach((list) => { list.external = true; }); - state.externalUser.push(externalUser); + this.externalUser.push(externalUser); }, - deleteExternalUser(state, uuid: string) { - const index = state.externalUser.findIndex((value) => value.uuid === uuid); + deleteExternalUserLocal(uuid: string) { + const index = this.externalUser.findIndex((value) => value.uuid === uuid); if (index < 0) { return; } - state.externalUser.splice(index, 1); + this.externalUser.splice(index, 1); }, - updateExternalList(state, updateList: StoreExternalList) { + updateExternalListLocal(updateList: StoreExternalList) { let found = false; - for (const user of state.externalUser) { + for (const user of this.externalUser) { for (const list of user.lists) { if (list.id === updateList.id) { found = true; @@ -45,35 +40,33 @@ const module: Module = { console.error("Cannot find list to update for id:", updateList.id); } }, - }, - actions: { - async loadExternalUser({ commit }) { + async loadExternalUser() { try { const externalUser = await HttpClient.getExternalUser(); externalUser.forEach((user) => { user.lists.forEach((list) => (list.external = true)); }); - commit("userExternalUser", externalUser); + this.externalUser = externalUser; console.log("Finished loading ExternalUser", externalUser); } catch (error) { console.error(error); } }, - async addExternalUser({ commit }, data: { identifier: string; pwd: string }) { + async addExternalUser(data: { identifier: string; pwd: string }) { if (!data.identifier) { - commit("addExternalUserModalError", "Identifier is missing!"); + // TODO: commit("addExternalUserModalError", "Identifier is missing!"); } else if (!data.pwd) { - commit("addExternalUserModalError", "Password is missing!"); + // TODO: commit("addExternalUserModalError", "Password is missing!"); } else { try { const externalUser: ExternalUser = await HttpClient.addExternalUser(data); - commit("addExternalUser", externalUser); + this.addExternalUserLocal(externalUser); } catch (error) { - commit("addExternalUserModalError", String(error)); + // TODO: commit("addExternalUserModalError", String(error)); } } }, - async deleteExternalUser({ commit }, uuid: string) { + async deleteExternalUser(uuid: string) { if (!uuid) { console.error("cannot delete externalUser without data"); return; @@ -81,12 +74,11 @@ const module: Module = { try { await HttpClient.deleteExternalUser(uuid); - commit("deleteExternalUser", uuid); + this.deleteExternalUserLocal(uuid); } catch (error) { console.log(error); throw error; } }, }, -}; -export default module; +}); diff --git a/packages/website/src/store/hooks.ts b/packages/website/src/store/hooks.ts index 22e611d9..cbe0c25a 100644 --- a/packages/website/src/store/hooks.ts +++ b/packages/website/src/store/hooks.ts @@ -1,56 +1,45 @@ -import { CustomHookStore, VuexStore } from "../siteTypes"; -import { Module } from "vuex"; import { HttpClient } from "../Httpclient"; import { CustomHook } from "enterprise-core/dist/types"; +import { defineStore } from "pinia"; -const module: Module = { +export const useHookStore = defineStore("hooks", { + persist: true, state: () => ({ - hooks: {}, + hooks: {} as Record, }), - mutations: { - add(state, hook: CustomHook): void { - if (state.hooks[hook.id]) { + actions: { + delete(hook: CustomHook): void { + if (!this.hooks[hook.id]) { + throw Error("Hook does not exist already"); + } + delete this.hooks[hook.id]; + }, + async createHook(hook: CustomHook): Promise { + const result = await HttpClient.createCustomHook(hook); + if (this.hooks[hook.id]) { throw Error("Hook already exists"); } - state.hooks[hook.id] = hook; + this.hooks[hook.id] = hook; + return result; }, - update(state, hook: CustomHook): void { - if (!state.hooks[hook.id]) { + async updateHook(hook: CustomHook): Promise { + const result = await HttpClient.updateCustomHook(hook); + if (!this.hooks[hook.id]) { throw Error("Hook does not exist already"); } - state.hooks[hook.id] = hook; + this.hooks[hook.id] = hook; + return result; }, - setAll(state, hooks: CustomHook[]): void { + async loadHooks(): Promise { + const result = await HttpClient.getCustomHooks(); + const newHooks: Record = {}; - for (const hook of hooks) { + for (const hook of result) { newHooks[hook.id] = hook; } - state.hooks = newHooks; - }, - delete(state, hook: CustomHook): void { - if (!state.hooks[hook.id]) { - throw Error("Hook does not exist already"); - } - delete state.hooks[hook.id]; - }, - }, - actions: { - async createHook({ commit }, hook: CustomHook): Promise { - const result = await HttpClient.createCustomHook(hook); - commit("add", result); - return result; - }, - async updateHook({ commit }, hook: CustomHook): Promise { - const result = await HttpClient.updateCustomHook(hook); - commit("update", result); - return result; - }, - async loadHooks({ commit }): Promise { - const result = await HttpClient.getCustomHooks(); - commit("setAll", result); + this.hooks = newHooks; }, }, -}; -export default module; +}); diff --git a/packages/website/src/store/lists.ts b/packages/website/src/store/lists.ts index bf87cf4f..8d37b8c3 100644 --- a/packages/website/src/store/lists.ts +++ b/packages/website/src/store/lists.ts @@ -1,49 +1,52 @@ -import { ListsStore, StoreInternalList, StoreList, VuexStore } from "../siteTypes"; -import { Module } from "vuex"; +import { StoreInternalList, StoreList } from "../siteTypes"; import { HttpClient } from "../Httpclient"; import { List } from "enterprise-core/src/types"; +import { defineStore } from "pinia"; +import { useExternalUserStore } from "./externaluser"; -const module: Module = { +export const useListStore = defineStore("lists", { + persist: true, state: () => ({ - lists: [], + lists: [] as StoreInternalList[], }), getters: { - allLists(state, _getters, rootState): StoreList[] { - const externalLists = rootState.externalUser.externalUser.flatMap((value) => value.lists); - return [...state.lists, ...externalLists]; + allLists(): StoreList[] { + const externalLists = useExternalUserStore().externalUser.flatMap((value) => value.lists); + return [...this.lists, ...externalLists]; }, }, - mutations: { - userLists(state, lists: List[]): void { - state.lists = lists.map((list) => ({ ...list, external: false })); + + actions: { + userListsLocal(lists: List[]): void { + this.lists = lists.map((list) => ({ ...list, external: false })); }, - addList(state, list: StoreInternalList) { + addListLocal(list: StoreInternalList) { list.external = false; - state.lists.push(list); + this.lists.push(list); }, - deleteList(state, id: number) { - const index = state.lists.findIndex((value) => value.id === id); + deleteListLocal(id: number) { + const index = this.lists.findIndex((value) => value.id === id); if (index < 0) { throw Error("invalid listId"); } - state.lists.splice(index, 1); + this.lists.splice(index, 1); }, - removeListItem(state, payload: { listId: number; mediumId: number }) { - const list = state.lists.find((value) => value.id === payload.listId); + removeListItemLocal(payload: { listId: number; mediumId: number }) { + const list = this.lists.find((value) => value.id === payload.listId); if (!list) { throw Error("invalid listId"); } list.items = list.items.filter((id) => id !== payload.mediumId); }, - addListItem(state, payload: { listId: number; mediumId: number }) { - const list = state.lists.find((value) => value.id === payload.listId); + addListItemLocal(payload: { listId: number; mediumId: number }) { + const list = this.lists.find((value) => value.id === payload.listId); if (!list) { throw Error("invalid listId"); } list.items.push(payload.mediumId); }, - updateList(state, updateList: List) { - const list = state.lists.find((value: List) => value.id === updateList.id); + updateListLocal(updateList: List) { + const list = this.lists.find((value: List) => value.id === updateList.id); if (list) { Object.assign(list, updateList); @@ -51,38 +54,33 @@ const module: Module = { console.error("Cannot find list to update for id:", updateList.id); } }, - }, - actions: { - async loadLists({ commit }) { + async loadLists() { try { const lists = await HttpClient.getLists(); - commit("userLists", lists); + this.userListsLocal(lists); console.log("Finished loading Lists", lists); } catch (error) { console.error(error); } }, - async addList({ commit }, data: { name: string; type: number }) { + async addList(data: { name: string; type: number }) { if (!data.name) { - commit("addListModalError", "Missing name"); + // TODO: commit("addListModalError", "Missing name"); } else if (!data.type) { - commit("addListModalError", "Missing type"); + // TODO: commit("addListModalError", "Missing type"); } else { return HttpClient.createList(data).then((list) => { - commit("addList", list); - commit("resetModal", "addList"); + // @ts-expect-error + this.addListLocal(list); + // TODO: commit("resetModal", "addList"); }); } }, - deleteList({ commit }, id: number) { - HttpClient.deleteList(id) - .then(() => { - commit("deleteList", id); - }) - .catch((error) => console.log(error)); + async deleteList(id: number) { + await HttpClient.deleteList(id); + this.deleteListLocal(id); }, }, -}; -export default module; +}); diff --git a/packages/website/src/store/media.ts b/packages/website/src/store/media.ts index 0446a8ba..b4b766fe 100644 --- a/packages/website/src/store/media.ts +++ b/packages/website/src/store/media.ts @@ -1,67 +1,66 @@ -import { AddMedium, MediaStore, Medium, SecondaryMedium, SimpleMedium, StringKey, VuexStore } from "../siteTypes"; -import { Module, useStore } from "vuex"; +import { AddMedium, Medium, SecondaryMedium, SimpleMedium, StringKey } from "../siteTypes"; import { HttpClient } from "../Httpclient"; import { mergeMediaTocProp } from "../init"; +import { defineStore } from "pinia"; +import { useListStore } from "./lists"; -const module: Module = { - state: () => ({ +export interface MediaStore { + media: Record; + secondaryMedia: Record; + episodesOnly: boolean; +} + +export const useMediaStore = defineStore("media", { + persist: true, + state: (): MediaStore => ({ media: {}, secondaryMedia: {}, episodesOnly: false, }), getters: { - getMedium(state) { - return (id: number): SimpleMedium => state.media[id]; - }, - getMergedProp(state) { + getMergedProp() { return >(medium: Medium, prop: T): SimpleMedium[T] => { if (!medium.id) { throw Error("missing id on medium"); } - const secondMedium = state.secondaryMedia[medium.id]; + const secondMedium = this.secondaryMedia[medium.id]; return mergeMediaTocProp(medium, secondMedium?.tocs || [], prop); }; }, - media(state): SimpleMedium[] { - return Object.values(state.media); + mediaList(): Medium[] { + return Object.values(this.media); }, }, - mutations: { - userMedia(state, media: Record) { - state.media = media; - }, - userSecondaryMedia(state, media: Record) { - state.secondaryMedia = media; - }, - addMedium(state, medium: Medium | Medium[]) { + actions: { + addMediumLocal(medium: Medium | Medium[]) { if (Array.isArray(medium)) { medium.forEach((item) => { if (!item.id) { throw Error("missing id on medium"); } - state.media[item.id] = item; + this.media[item.id] = item; }); } else { if (!medium.id) { throw Error("missing id on medium"); } - state.media[medium.id] = medium; + this.media[medium.id] = medium; } }, - updateMedium(state, medium: SimpleMedium) { + updateMediumLocal(medium: SimpleMedium) { if (!medium.id) { throw Error("missing id on medium"); } - Object.assign(state.media[medium.id], medium); + Object.assign(this.media[medium.id], medium); }, - deleteMedium(state, id: number) { - if (!(id in state.media)) { + deleteMediumLocal(id: number) { + if (!(id in this.media)) { throw Error("invalid mediumId"); } - delete state.media[id]; + delete this.media[id]; - useStore().state.lists.forEach((value: { items: number[] }) => { + useListStore().lists.forEach((value: { items: number[] }) => { const listIndex = value.items.findIndex((itemId: number) => itemId === id); if (listIndex >= 0) { @@ -69,19 +68,14 @@ const module: Module = { } }); }, - deleteToc(state, data: { mediumId: number; link: string }) { - const medium = state.secondaryMedia[data.mediumId]; + deleteTocLocal(data: { mediumId: number; link: string }) { + const medium = this.secondaryMedia[data.mediumId]; if (!medium) { throw Error("invalid mediumId"); } medium.tocs = medium.tocs.filter((toc) => toc.link !== data.link); }, - episodesOnly(state, value: boolean) { - state.episodesOnly = value; - }, - }, - actions: { - async loadMedia({ commit }) { + async loadMedia() { try { const [data, secondaryData] = await Promise.all([HttpClient.getAllMedia(), HttpClient.getAllSecondaryMedia()]); const media: Record = {}; @@ -100,29 +94,33 @@ const module: Module = { } } - commit("userMedia", media); - commit("userSecondaryMedia", secondaryMedia); + // @ts-expect-error + this.media = media; + this.secondaryMedia = secondaryMedia; } catch (error) { console.error(error); } }, - addMedium({ commit }, data: AddMedium) { + addMedium(data: AddMedium) { if (!data.title) { - commit("addMediumModalError", "Missing title"); + // TODO: commit("addMediumModalError", "Missing title"); } else if (!data.medium) { - commit("addMediumModalError", "Missing type"); + // TODO: commit("addMediumModalError", "Missing type"); } else { HttpClient.createMedium(data) .then((medium) => { - commit("addMedium", medium); - commit("resetModal", "addMedium"); + // @ts-expect-error + this.addMediumLocal(medium); }) - .catch((error) => commit("addMediumModalError", String(error))); + .catch((error) => { + // TODO: notify error + console.error(error); + }); } // TODO implement addMedium }, - editMedium({ commit }, data: { id: number; prop: string }) { + editMedium(data: { id: number; prop: string }) { if (data.id == null || !data.prop) { // TODO handle this better throw Error(); @@ -132,17 +130,16 @@ const module: Module = { // TODO implement editMedium }, - deleteMedium({ commit }, id: number) { + deleteMedium(id: number) { if (id == null) { // TODO handle this better throw Error(); } else { HttpClient.deleteMedium(id) - .then(() => commit("deleteMedium", id)) + .then(() => this.deleteMediumLocal(id)) .catch((error) => console.log(error)); } // TODO implement deleteMedium }, }, -}; -export default module; +}); diff --git a/packages/website/src/store/modals.ts b/packages/website/src/store/modals.ts index 2dbf245c..ca93f6f2 100644 --- a/packages/website/src/store/modals.ts +++ b/packages/website/src/store/modals.ts @@ -1,5 +1,5 @@ -import { Modal, Modals, VuexStore } from "../siteTypes"; -import { Module } from "vuex"; +import { Modal, Modals } from "../siteTypes"; +import { defineStore } from "pinia"; function createModal(): Modal { return { @@ -8,7 +8,7 @@ function createModal(): Modal { }; } -const module: Module = { +export const useModalStore = defineStore("store", { state: () => ({ addList: createModal(), addMedium: createModal(), @@ -18,7 +18,7 @@ const module: Module = { settings: createModal(), error: createModal(), }), - mutations: { + actions: { // create error and show setter for modals ...(function () { const modals: Array = [ @@ -44,5 +44,4 @@ const module: Module = { state[modalKey].error = ""; }, }, -}; -export default module; +}); diff --git a/packages/website/src/store/news.ts b/packages/website/src/store/news.ts deleted file mode 100644 index 09885233..00000000 --- a/packages/website/src/store/news.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { News, NewsStore, VuexStore } from "../siteTypes"; -import { Module } from "vuex"; -import { HttpClient } from "../Httpclient"; - -const module: Module = { - state: () => ({ - news: [], - }), - mutations: { - addNews(state, news: News[]): void { - const ownNews = state.news; - news = news - .filter((value) => !ownNews.find((otherValue) => otherValue.id === value.id)) - .map((value) => (value.date = new Date(value.date)) && value); - - if (news.length) { - ownNews.push(...news); - } - }, - }, - actions: { - markReadNews({ commit }, newsId: number): void { - // if (this.readNews.indexOf(newsId) < 0) { - // this.readNews.push(newsId); - // this.newReadNews.push(newsId); - // } - }, - - loadNews({ commit }, data: { from: Date | undefined; to: Date | undefined }): void { - HttpClient.getNews(data.from, data.to) - .then((news) => commit("userNews", news)) - .catch(console.log); - }, - }, -}; -export default module; diff --git a/packages/website/src/store/pinia.ts b/packages/website/src/store/pinia.ts new file mode 100644 index 00000000..a668e64b --- /dev/null +++ b/packages/website/src/store/pinia.ts @@ -0,0 +1,14 @@ +import { createPinia } from "pinia"; +import { createPersistedState } from "pinia-plugin-persistedstate"; +import { PiniaLogger } from "pinia-logger"; + +const pinia = createPinia(); +pinia.use(createPersistedState()); +pinia.use( + PiniaLogger({ + expanded: false, + disabled: process.env.mode === "production", + }), +); + +export { pinia }; diff --git a/packages/website/src/store/releases.ts b/packages/website/src/store/releases.ts index dd551fc4..91d84827 100644 --- a/packages/website/src/store/releases.ts +++ b/packages/website/src/store/releases.ts @@ -1,8 +1,10 @@ -import { DisplayReleaseItem, ReleaseStore, VuexStore } from "../siteTypes"; -import { Module } from "vuex"; +import { DisplayReleaseItem, MediaType, SimpleMedium } from "../siteTypes"; import { formatDate, remove } from "../init"; import { HttpClient } from "../Httpclient"; -import { DisplayRelease, Id, MinMedium, SimpleMedium } from "enterprise-core/dist/types"; +import { DisplayRelease, Id, List, MinMedium } from "enterprise-core/dist/types"; +import { defineStore } from "pinia"; +import { useMediaStore } from "./media"; +import { useListStore } from "./lists"; let fetchingReleases = false; @@ -21,92 +23,103 @@ function defaultEarliest(): Date { return until; } -const module: Module = { - namespaced: true, +interface LastFetch { + args: string; + date: number; +} + +export const useReleaseStore = defineStore("releases", { + persist: { + afterRestore(context) { + // parse the stringified dates back into an object + context.store.$state.latest = new Date(context.store.$state.latest); + context.store.$state.until = new Date(context.store.$state.until); + }, + }, state: () => ({ - readFilter: undefined, - typeFilter: 0, - onlyMedia: [], - onlyLists: [], - ignoreMedia: [], - ignoreLists: [], + readFilter: false as boolean, + typeFilter: 0 as 0 | MediaType, + onlyMedia: [] as Id[], + onlyLists: [] as Id[], + ignoreMedia: [] as Id[], + ignoreLists: [] as Id[], latest: defaultLatest(), until: defaultEarliest(), - releases: [], - lastFetch: undefined, + releases: [] as DisplayReleaseItem[], + lastFetch: undefined as LastFetch | undefined, fetching: false, }), - mutations: { - readFilter(state: ReleaseStore, read?: boolean): void { - state.readFilter = read; - }, - typeFilter(state: ReleaseStore, type: number): void { - state.typeFilter = type; - }, - ignoreMedium(state, id: number) { - state.ignoreMedia.push(id); - }, - ignoreList(state, id: number) { - state.ignoreLists.push(id); + getters: { + getIgnoreMedia(): SimpleMedium[] { + const mediaStore = useMediaStore(); + return this.ignoreMedia.map((id) => { + return mediaStore.media[id] || { id, title: "Unknown" }; + }); }, - requireMedium(state, id: number) { - state.onlyMedia.push(id); + getIgnoreLists(): List[] { + const listStore = useListStore(); + return this.ignoreLists.map((id) => { + return ( + listStore.lists.find((list) => list.id === id) || { + id, + name: "Unknown", + external: false, + medium: 0, + userUuid: "", + items: [], + } + ); + }); }, - requireList(state, id: number) { - state.onlyLists.push(id); + }, + actions: { + ignoreMedium(id: number) { + this.ignoreMedia.push(id); }, - unignoreMedium(state, id: number) { - remove(state.ignoreMedia, id); + ignoreList(id: number) { + this.ignoreLists.push(id); }, - unignoreList(state, id: number) { - remove(state.ignoreLists, id); + requireMedium(id: number) { + this.onlyMedia.push(id); }, - unrequireMedium(state, id: number) { - remove(state.onlyMedia, id); + requireList(id: number) { + this.onlyLists.push(id); }, - unrequireList(state, id: number) { - remove(state.onlyLists, id); + unignoreMedium(id: number) { + remove(this.ignoreMedia, id); }, - latest(state, date: Date) { - state.latest = date; + unignoreList(id: number) { + remove(this.ignoreLists, id); }, - until(state, date: Date) { - state.until = date; + unrequireMedium(id: number) { + remove(this.onlyMedia, id); }, - resetDates(state) { - state.until = defaultEarliest(); - state.latest = defaultLatest(); + unrequireList(id: number) { + remove(this.onlyLists, id); }, - setFetching(state, fetching: boolean) { - state.fetching = fetching; + resetDates() { + this.until = defaultEarliest(); + this.latest = defaultLatest(); }, - updateProgress(state, update: { episodeId: Id; read: boolean }) { - state.releases.forEach((element: DisplayReleaseItem) => { + updateStoreProgress(update: { episodeId: Id; read: boolean }) { + this.releases.forEach((element: DisplayReleaseItem) => { if (update.episodeId === element.episodeId) { element.read = update.read; } }); }, - lastFetch(state, lastFetch: ReleaseStore["lastFetch"]) { - state.lastFetch = lastFetch; - }, - releases(state, releases: ReleaseStore["releases"]) { - state.releases = releases; - }, - }, - actions: { - async loadDisplayReleases({ state, commit, rootGetters }, force: boolean) { + async loadDisplayReleases(force: boolean) { if (fetchingReleases) { return; } const args: Parameters = [ - state.latest, - state.until, - state.readFilter, - state.onlyLists, - state.onlyMedia, - state.ignoreLists, - state.ignoreMedia, + this.latest, + this.until, + this.readFilter, + this.onlyLists, + this.onlyMedia, + this.ignoreLists, + this.ignoreMedia, ]; const currentFetch = JSON.stringify(args); @@ -115,15 +128,15 @@ const module: Module = { // do not try to get releases with the same args twice in less than a minute if ( !force && - state.lastFetch && - nowMillis - state.lastFetch.date < 1000 * 60 && - currentFetch === state.lastFetch.args + this.lastFetch && + nowMillis - this.lastFetch.date < 1000 * 60 && + currentFetch === this.lastFetch.args ) { return; } - commit("lastFetch", { args: currentFetch, date: nowMillis }); + this.lastFetch = { args: currentFetch, date: nowMillis }; fetchingReleases = true; - commit("setFetching", true); + this.fetching = true; try { const response = await HttpClient.getDisplayReleases(...args); @@ -133,6 +146,7 @@ const module: Module = { // should not happen because no two fetches should happen at the same time const mediumIdMap = new Map(); + const mediaStore = useMediaStore(); // insert fetched releases at the corresponding place releases.push( @@ -142,7 +156,7 @@ const module: Module = { } const key = item.episodeId + item.link; - let medium: SimpleMedium | undefined = rootGetters.getMedium(item.mediumId); + let medium: SimpleMedium | undefined = mediaStore.media[item.mediumId]; if (!medium) { // build map only if necessary and previously empty @@ -170,14 +184,13 @@ const module: Module = { }), ); releases.sort((a: DisplayReleaseItem, b: DisplayReleaseItem) => b.time - a.time); - commit("releases", releases); + this.releases = releases; } catch (error) { console.error(error); } finally { fetchingReleases = false; - commit("setFetching", false); + this.fetching = false; } }, }, -}; -export default module; +}); diff --git a/packages/website/src/store/settings.ts b/packages/website/src/store/settings.ts index fe071833..3ecfbaa2 100644 --- a/packages/website/src/store/settings.ts +++ b/packages/website/src/store/settings.ts @@ -1,83 +1,87 @@ -import { ActionTree, MutationTree } from "vuex"; +import { Id } from "enterprise-core/dist/types"; +import { defineStore } from "pinia"; import { notificationEnabled, notify } from "../notifications"; -import { SettingStore, VuexStore } from "../siteTypes"; +import { useReleaseStore } from "./releases"; -export const state = (): SettingStore => ({ - notifications: { - newReleases: { - enabled: false, +let intervalId: ReturnType | undefined; + +export const useSettingsStore = defineStore("settings", { + persist: true, + state: () => ({ + notifications: { + newReleases: { + enabled: false, + push: false, + allMedia: false, + media: [] as Id[], + alreadyNotified: [] as string[], + }, push: false, - allMedia: false, - media: [], }, - push: false, - }, -}); + }), + actions: { + activateNewReleases() { + if (intervalId) { + return; + } + let lastCheck = Date.now(); + const interval = 60000; + const releaseStore = useReleaseStore(); -export const mutations: MutationTree = { - updateNotificationSettings(state, settings: SettingStore["notifications"]) { - Object.assign(state.notifications, settings); - }, -}; + intervalId = setInterval(async () => { + if (!this.notifications.newReleases.enabled || !this.notifications.newReleases.push || !notificationEnabled()) { + console.info("ignoring checking for new releases"); + return; + } -let intervalId: ReturnType | undefined; + const start = Date.now(); + await releaseStore.loadDisplayReleases(false); -export const actions: ActionTree = { - activateNewReleases({ dispatch, state, rootState }) { - if (intervalId) { - return; - } - let lastCheck = Date.now(); - const interval = 60000; + // add the time the request itself has taken? + const allowedDifference = interval + (Date.now() - start); - intervalId = setInterval(async () => { - if (!state.notifications.newReleases.enabled || !state.notifications.newReleases.push || !notificationEnabled()) { - console.info("ignoring checking for new releases"); - return; - } + // if there are not media to be listened for, skip filtering + if (!this.notifications.newReleases.allMedia && !this.notifications.newReleases.media.length) { + console.info("ignoring checking for new releases: nothing to check for"); + return; + } - const start = Date.now(); - await dispatch("releases/loadDisplayReleases"); + const releasestoNotify = releaseStore.releases.filter( + (release) => + (this.notifications.newReleases.allMedia || + this.notifications.newReleases.media.includes(release.mediumId)) && + lastCheck - release.time <= allowedDifference && + // notify only once + !this.notifications.newReleases.alreadyNotified.includes(release.key), + ); - // add the time the request itself has taken? - const allowedDifference = interval + (Date.now() - start); + // TODO: clear this.notifications.newReleases.alreadyNotified sometimes + releasestoNotify.forEach((value) => this.notifications.newReleases.alreadyNotified.push(value.key)); - // if there are not media to be listened for, skip filtering - if (!state.notifications.newReleases.allMedia && !state.notifications.newReleases.media.length) { - console.info("ignoring checking for new releases: nothing to check for"); - return; - } - - const releasestoNotify = rootState.releases.releases.filter( - (release) => - (state.notifications.newReleases.allMedia || - state.notifications.newReleases.media.includes(release.mediumId)) && - lastCheck - release.time <= allowedDifference, - ); + if (releasestoNotify.length < 2) { + releasestoNotify.forEach((release) => { + notify({ title: "New Release for " + release.mediumTitle, content: release.title }); + }); + } else { + const release = releasestoNotify[0]; + const uniqueMedia = new Set(releasestoNotify.map((item) => item.mediumId)).size; - if (releasestoNotify.length < 2) { - releasestoNotify.forEach((release) => { - notify({ title: "New Release for " + release.mediumTitle, content: release.title }); - }); - } else { - const release = releasestoNotify[0]; - const uniqueMedia = new Set(releasestoNotify.map((item) => item.mediumId)).size; + notify({ + title: releasestoNotify.length + " new Releases", + content: release.mediumTitle + " and " + (uniqueMedia - 1) + " other titles", + }); + } + console.info("notified for %d new releases", releasestoNotify.length); + lastCheck = Date.now(); + }, interval); + }, - notify({ - title: releasestoNotify.length + " new Releases", - content: release.mediumTitle + " and " + (uniqueMedia - 1) + " other titles", - }); + deactivateNewReleases() { + if (!intervalId || this.notifications.newReleases.enabled) { + return; } - console.info("notified for %d new releases", releasestoNotify.length); - lastCheck = Date.now(); - }, interval); - }, - - deactivateNewReleases({ state }) { - if (!intervalId || state.notifications.newReleases.enabled) { - return; - } - clearInterval(intervalId); - intervalId = undefined; + clearInterval(intervalId); + intervalId = undefined; + }, }, -}; +}); diff --git a/packages/website/src/store/store.ts b/packages/website/src/store/store.ts index 01df55c7..e566f966 100644 --- a/packages/website/src/store/store.ts +++ b/packages/website/src/store/store.ts @@ -1,59 +1,34 @@ import { HttpClient } from "../Httpclient"; -import { User, VuexStore } from "../siteTypes"; +import { User } from "../siteTypes"; import router from "../router"; -import { Commit, createStore, createLogger } from "vuex"; -import persistedState from "vuex-persistedstate"; -import releaseStore from "./releases"; -import modalStore from "./modals"; -import listStore from "./lists"; -import mediumStore from "./media"; -import externalUserStore from "./externaluser"; -import newsStore from "./news"; -import hookStore from "./hooks"; +import { useListStore } from "./lists"; +import { useExternalUserStore } from "./externaluser"; +import { useHookStore } from "./hooks"; import { UserNotification } from "enterprise-core/dist/types"; -import { actions as settingsActions, mutations as settingsMutations, state as settingsState } from "./settings"; +import { useMediaStore } from "./media"; +import { defineStore } from "pinia"; -function userClear(commit: Commit) { - commit("userName", ""); - commit("userId", ""); - commit("userSession", ""); - commit("userLists", []); - commit("userMedia", []); - commit("userExternalUser", []); -} +type UserState = ReturnType["$state"]; -function setUser(commit: Commit, user: User) { - userClear(commit); - commit("userName", user.name); - commit("userId", user.uuid); - commit("userSession", user.session); +function userClear(state: UserState) { + state.name = ""; + state.uuid = ""; + state.session = ""; + useListStore().lists = []; + useMediaStore().media = {}; + useExternalUserStore().externalUser = []; } -const plugins = [persistedState()]; - -if (process.env.NODE_ENV !== "production") { - plugins.push(createLogger()); +function setUser(state: UserState, user: User) { + userClear(state); + state.name = user.name; + state.uuid = user.uuid; + state.session = user.session; } -export const store = createStore({ - strict: process.env.NODE_ENV !== "production", - plugins, - modules: { - releases: releaseStore, - modals: modalStore, - lists: listStore, - externalUser: externalUserStore, - media: mediumStore, - news: newsStore, - hooks: hookStore, - settings: { - actions: settingsActions, - state: settingsState, - mutations: settingsMutations, - }, - }, - // @ts-expect-error - state: (): VuexStore => ({ +export const useUserStore = defineStore("user", { + persist: true, + state: () => ({ user: { settings: {}, columns: [ @@ -72,42 +47,31 @@ export const store = createStore({ return !!state.uuid; }, }, - mutations: { - userName(state, name: string) { - state.name = name; - }, - userId(state, id: string) { - state.uuid = id; - }, - userSession(state, session: string) { - state.session = session; - }, - unreadNotificationCount(state, count: number) { - state.user.unreadNotificationsCount = count; - }, - readNotificationCount(state, count: number) { - state.user.readNotificationsCount = count; - }, - }, actions: { - async load({ dispatch }) { + async load() { + const mediaStore = useMediaStore(); + const listStore = useListStore(); + const hookStore = useHookStore(); + const externalUserStore = useExternalUserStore(); + await Promise.all([ - dispatch("loadMedia"), - dispatch("loadLists"), - dispatch("loadExternalUser"), - dispatch("loadHooks"), + mediaStore.loadMedia(), + listStore.loadLists(), + externalUserStore.loadExternalUser(), + hookStore.loadHooks(), ]); }, - async changeUser({ commit, dispatch, state }, { user, modal }: { user: User; modal: string }) { - const userChanged = user && state.uuid !== user.uuid; - setUser(commit, user); + async changeUser(user: User, modal?: string) { + const userChanged = user && this.uuid !== user.uuid; + this.$patch((state) => setUser(state, user)); if (modal) { - commit("resetModal", modal); + // TODO: commit("resetModal", modal); } if (userChanged) { - await dispatch("load"); + // TODO: load first and then change page, or reverse? + await this.load(); if (router.currentRoute.value.path === "/login") { // automatically navigate to view under home if successfully logged in @@ -116,66 +80,66 @@ export const store = createStore({ } } }, - async login({ commit, dispatch }, data: { user: string; pw: string }) { + async login(data: { user: string; pw: string }) { if (!data.user) { - commit("loginModalError", "Username is missing"); + // TODO: commit("loginModalError", "Username is missing"); return; } else if (!data.pw) { - commit("loginModalError", "Password is missing"); + // TODO: commit("loginModalError", "Password is missing"); return; } try { // FIXME modal does not close after successful login const newUser = await HttpClient.login(data.user, data.pw); - await dispatch("changeUser", { user: newUser, modal: "login" }); + this.changeUser(newUser, "login"); } catch (error) { - commit("loginModalError", String(error)); + // TODO: commit("loginModalError", String(error)); } }, - immediateLogout({ commit }) { - userClear(commit); + immediateLogout() { + this.$patch(userClear); }, - async logout({ commit }) { + async logout() { const loggedOut = await HttpClient.logout(); - userClear(commit); + this.$patch(userClear); if (!loggedOut) { throw Error("An error occurred while logging out"); } }, - async register({ commit, dispatch }, data: { user: string; pw: string; pwRepeat: string }) { + async register(data: { user: string; pw: string; pwRepeat: string }) { if (!data.user) { - commit("registerModalError", "Username is missing"); + // TODO: commit("registerModalError", "Username is missing"); } else if (!data.pw) { - commit("registerModalError", "Password is missing"); + // TODO: commit("registerModalError", "Password is missing"); } else if (!data.pwRepeat) { - commit("registerModalError", "Password was not repeated"); + // TODO: commit("registerModalError", "Password was not repeated"); } else if (data.pwRepeat !== data.pw) { - commit("registerModalError", "Repeated Password is not password"); + // TODO: commit("registerModalError", "Repeated Password is not password"); } else { try { const newUser = await HttpClient.register(data.user, data.pw, data.pwRepeat); - await dispatch("changeUser", { user: newUser, modal: "register" }); + await this.changeUser(newUser, "register"); } catch (error) { - commit("registerModalError", String(error)); + // TODO: commit("registerModalError", String(error)); } } }, - async checkNotificationCounts({ commit }) { + async checkNotificationCounts() { const [unreadCount, readCount] = await Promise.all([ HttpClient.getNotificationsCount(false), HttpClient.getNotificationsCount(true), ]); - commit("unreadNotificationCount", unreadCount); - commit("readNotificationCount", readCount); + this.user.unreadNotificationsCount = unreadCount; + this.user.readNotificationsCount = readCount; }, - async readNotification({ dispatch }, data: UserNotification) { + async readNotification(data: UserNotification) { await HttpClient.readNotification(data.id); - dispatch("checkNotificationCounts"); + this.checkNotificationCounts(); }, - async readAllNotifications({ dispatch }) { + async readAllNotifications() { await HttpClient.readAllNotifications(); - dispatch("checkNotificationCounts"); + this.checkNotificationCounts(); }, }, }); diff --git a/packages/website/src/views/AddExternal.vue b/packages/website/src/views/AddExternal.vue index 19ff3fb7..32f0b579 100644 --- a/packages/website/src/views/AddExternal.vue +++ b/packages/website/src/views/AddExternal.vue @@ -1,3 +1,4 @@ +import { useExternalUserStore } from "../store/externaluser";