From 77ee1bfc3efe864266fe11fb1fd94202371c798f Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 11 Jun 2023 17:31:04 +0200 Subject: [PATCH 01/12] feat(user): migrate pop sound setting to store in api --- src/modelTypes/IUserSettings.ts | 7 +++++++ src/models/userSettings.ts | 7 ++++++- src/views/user/settings/General.vue | 27 ++++++++++++++------------- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/modelTypes/IUserSettings.ts b/src/modelTypes/IUserSettings.ts index 8703c48af..017939dd1 100644 --- a/src/modelTypes/IUserSettings.ts +++ b/src/modelTypes/IUserSettings.ts @@ -1,6 +1,12 @@ import type {IAbstract} from './IAbstract' import type {IProject} from './IProject' +import type {PrefixMode} from '@/modules/parseTaskText' + +export interface IFrontendSettings { + playSoundWhenDone: boolean + quickAddMagicMode: PrefixMode +} export interface IUserSettings extends IAbstract { name: string @@ -13,4 +19,5 @@ export interface IUserSettings extends IAbstract { weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6 timezone: string language: string + frontendSettings: IFrontendSettings } \ No newline at end of file diff --git a/src/models/userSettings.ts b/src/models/userSettings.ts index e45e2041b..f3799d4b7 100644 --- a/src/models/userSettings.ts +++ b/src/models/userSettings.ts @@ -1,7 +1,8 @@ import AbstractModel from './abstractModel' -import type {IUserSettings} from '@/modelTypes/IUserSettings' +import type {IFrontendSettings, IUserSettings} from '@/modelTypes/IUserSettings' import {getCurrentLanguage} from '@/i18n' +import {PrefixMode} from '@/modules/parseTaskText' export default class UserSettingsModel extends AbstractModel implements IUserSettings { name = '' @@ -14,6 +15,10 @@ export default class UserSettingsModel extends AbstractModel impl weekStart = 0 as IUserSettings['weekStart'] timezone = '' language = getCurrentLanguage() + frontendSettings: IFrontendSettings = { + playSoundWhenDone: true, + quickAddMagicMode: PrefixMode.Default, + } constructor(data: Partial = {}) { super() diff --git a/src/views/user/settings/General.vue b/src/views/user/settings/General.vue index 87624daaf..e3872ed4a 100644 --- a/src/views/user/settings/General.vue +++ b/src/views/user/settings/General.vue @@ -57,7 +57,7 @@
@@ -170,6 +170,7 @@ import {useTitle} from '@/composables/useTitle' import {useProjectStore} from '@/stores/projects' import {useAuthStore} from '@/stores/auth' +import type {IUserSettings} from '@/modelTypes/IUserSettings' const {t} = useI18n({useScope: 'global'}) useTitle(() => `${t('user.settings.general.title')} - ${t('user.settings.title')}`) @@ -215,15 +216,15 @@ function useAvailableTimezones() { const availableTimezones = useAvailableTimezones() -function getPlaySoundWhenDoneSetting() { - return localStorage.getItem(playSoundWhenDoneKey) === 'true' || localStorage.getItem(playSoundWhenDoneKey) === null -} - -const playSoundWhenDone = ref(getPlaySoundWhenDoneSetting()) -const quickAddMagicMode = ref(getQuickAddMagicMode()) - const authStore = useAuthStore() -const settings = ref({...authStore.settings}) +const settings = ref({ + ...authStore.settings, + frontendSettings: { + // Sub objects get exported as read only as well, so we need to + // explicitly spread the object here to allow modification + ...authStore.settings.frontendSettings, + } +}) const id = ref(createRandomID()) const availableLanguageOptions = ref( Object.entries(SUPPORTED_LOCALES) @@ -252,10 +253,10 @@ const defaultProject = computed({ }) const loading = computed(() => authStore.isLoadingGeneralSettings) -watch( - playSoundWhenDone, - (play) => play && playPopSound(), -) +// watch( +// settings.value.frontendSettings.playSoundWhenDone, +// (play) => play && playPopSound(), +// ) async function updateSettings() { localStorage.setItem(playSoundWhenDoneKey, playSoundWhenDone.value ? 'true' : 'false') From d8ad934643559b42980abe72bc957fd2940a9cd8 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 11 Jun 2023 17:37:49 +0200 Subject: [PATCH 02/12] feat(user): save quick add magic mode in api --- .../quick-actions/quick-actions.vue | 7 ++--- src/components/tasks/add-task.vue | 4 +-- .../tasks/partials/quick-add-magic.vue | 6 +++-- src/helpers/parseSubtasksViaIndention.ts | 8 +++--- src/helpers/playPop.ts | 9 ------- src/helpers/quickAddMagicMode.ts | 21 --------------- src/modules/parseTaskText.ts | 27 +++++++------------ src/stores/tasks.ts | 11 ++++---- src/views/user/settings/General.vue | 6 +---- 9 files changed, 31 insertions(+), 68 deletions(-) delete mode 100644 src/helpers/quickAddMagicMode.ts diff --git a/src/components/quick-actions/quick-actions.vue b/src/components/quick-actions/quick-actions.vue index f5606a181..a6a5d47ce 100644 --- a/src/components/quick-actions/quick-actions.vue +++ b/src/components/quick-actions/quick-actions.vue @@ -71,10 +71,10 @@ import {useBaseStore} from '@/stores/base' import {useProjectStore} from '@/stores/projects' import {useLabelStore} from '@/stores/labels' import {useTaskStore} from '@/stores/tasks' +import {useAuthStore} from '@/stores/auth' import {getHistory} from '@/modules/projectHistory' import {parseTaskText, PrefixMode, PREFIXES} from '@/modules/parseTaskText' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' import {success} from '@/message' import type {ITeam} from '@/modelTypes/ITeam' @@ -88,6 +88,7 @@ const baseStore = useBaseStore() const projectStore = useProjectStore() const labelStore = useLabelStore() const taskStore = useTaskStore() +const authStore = useAuthStore() type DoAction = { type: ACTION_TYPE } & Type @@ -242,7 +243,7 @@ const hintText = computed(() => { } } const prefixes = - PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default] + PREFIXES[authStore.settings.frontendSettings.quickAddMagicMode] ?? PREFIXES[PrefixMode.Default] return t('quickActions.hint', prefixes) }) @@ -255,7 +256,7 @@ const availableCmds = computed(() => { return cmds }) -const parsedQuery = computed(() => parseTaskText(query.value, getQuickAddMagicMode())) +const parsedQuery = computed(() => parseTaskText(query.value, authStore.settings.frontendSettings.quickAddMagicMode)) const searchMode = computed(() => { if (query.value === '') { diff --git a/src/components/tasks/add-task.vue b/src/components/tasks/add-task.vue index 4c18f10b2..d377b20e8 100644 --- a/src/components/tasks/add-task.vue +++ b/src/components/tasks/add-task.vue @@ -116,12 +116,12 @@ async function addTask() { // This allows us to find the tasks with the title they had before being parsed // by quick add magic. const createdTasks: { [key: ITask['title']]: ITask } = {} - const tasksToCreate = parseSubtasksViaIndention(newTaskTitle.value) + const tasksToCreate = parseSubtasksViaIndention(newTaskTitle.value, authStore.settings.frontendSettings.quickAddMagicMode) // We ensure all labels exist prior to passing them down to the create task method // In the store it will only ever see one task at a time so there's no way to reliably // check if a new label was created before (because everything happens async). - const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title) ?? []) + const allLabels = tasksToCreate.map(({title}) => getLabelsFromPrefix(title, authStore.settings.frontendSettings.quickAddMagicMode) ?? []) await taskStore.ensureLabelsExist(allLabels.flat()) const newTasks = tasksToCreate.map(async ({title, project}) => { diff --git a/src/components/tasks/partials/quick-add-magic.vue b/src/components/tasks/partials/quick-add-magic.vue index 39582e0c3..30818a0ff 100644 --- a/src/components/tasks/partials/quick-add-magic.vue +++ b/src/components/tasks/partials/quick-add-magic.vue @@ -99,11 +99,13 @@ import {ref, computed} from 'vue' import BaseButton from '@/components/base/BaseButton.vue' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' import {PREFIXES} from '@/modules/parseTaskText' +import {useAuthStore} from '@/stores/auth' + +const authStore = useAuthStore() const visible = ref(false) -const mode = ref(getQuickAddMagicMode()) +const mode = computed(() => authStore.settings.frontendSettings.quickAddMagicMode) defineProps<{ highlightHintIcon: boolean, diff --git a/src/helpers/parseSubtasksViaIndention.ts b/src/helpers/parseSubtasksViaIndention.ts index 807301d5b..1956e671f 100644 --- a/src/helpers/parseSubtasksViaIndention.ts +++ b/src/helpers/parseSubtasksViaIndention.ts @@ -1,4 +1,4 @@ -import {getProjectFromPrefix} from '@/modules/parseTaskText' +import {getProjectFromPrefix, PrefixMode} from '@/modules/parseTaskText' export interface TaskWithParent { title: string, @@ -16,7 +16,7 @@ const spaceRegex = /^ */ * @param taskTitles should be multiple lines of task tiles with indention to declare their parent/subtask * relation between each other. */ -export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[] { +export function parseSubtasksViaIndention(taskTitles: string, prefixMode: PrefixMode): TaskWithParent[] { const titles = taskTitles.split(/[\r\n]+/) return titles.map((title, index) => { @@ -26,7 +26,7 @@ export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[] project: null, } - task.project = getProjectFromPrefix(task.title) + task.project = getProjectFromPrefix(task.title, prefixMode) if (index === 0) { return task @@ -49,7 +49,7 @@ export function parseSubtasksViaIndention(taskTitles: string): TaskWithParent[] task.parent = task.parent.replace(spaceRegex, '') if (task.project === null) { // This allows to specify a project once for the parent task and inherit it to all subtasks - task.project = getProjectFromPrefix(task.parent) + task.project = getProjectFromPrefix(task.parent, prefixMode) } } diff --git a/src/helpers/playPop.ts b/src/helpers/playPop.ts index 3f4e50c35..91b951e7f 100644 --- a/src/helpers/playPop.ts +++ b/src/helpers/playPop.ts @@ -2,15 +2,6 @@ import popSoundFile from '@/assets/audio/pop.mp3' export const playSoundWhenDoneKey = 'playSoundWhenTaskDone' -export function playPop() { - const enabled = localStorage.getItem(playSoundWhenDoneKey) === 'true' - if (!enabled) { - return - } - - playPopSound() -} - export function playPopSound() { const popSound = new Audio(popSoundFile) popSound.play() diff --git a/src/helpers/quickAddMagicMode.ts b/src/helpers/quickAddMagicMode.ts deleted file mode 100644 index 8ee23b270..000000000 --- a/src/helpers/quickAddMagicMode.ts +++ /dev/null @@ -1,21 +0,0 @@ -import {PrefixMode} from '@/modules/parseTaskText' - -const key = 'quickAddMagicMode' - -export const setQuickAddMagicMode = (mode: PrefixMode) => { - localStorage.setItem(key, mode) -} - -export const getQuickAddMagicMode = (): PrefixMode => { - const mode = localStorage.getItem(key) - - switch (mode) { - case null: - case PrefixMode.Default: - return PrefixMode.Default - case PrefixMode.Todoist: - return PrefixMode.Todoist - } - - return PrefixMode.Disabled -} diff --git a/src/modules/parseTaskText.ts b/src/modules/parseTaskText.ts index 0b4044d1c..e69ad58b2 100644 --- a/src/modules/parseTaskText.ts +++ b/src/modules/parseTaskText.ts @@ -1,7 +1,6 @@ import {parseDate} from '../helpers/time/parseDate' import {PRIORITIES} from '@/constants/priorities' import {REPEAT_TYPES, type IRepeatAfter, type IRepeatType} from '@/types/IRepeatAfter' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' const VIKUNJA_PREFIXES: Prefixes = { label: '*', @@ -72,10 +71,10 @@ export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMod return result } - result.labels = getLabelsFromPrefix(text, prefixes.label) ?? [] + result.labels = getLabelsFromPrefix(text, prefixesMode) ?? [] result.text = cleanupItemText(result.text, result.labels, prefixes.label) - result.project = getProjectFromPrefix(result.text, prefixes.project) + result.project = getProjectFromPrefix(result.text, prefixesMode) result.text = result.project !== null ? cleanupItemText(result.text, [result.project], prefixes.project) : result.text result.priority = getPriority(result.text, prefixes.priority) @@ -131,25 +130,19 @@ const getItemsFromPrefix = (text: string, prefix: string): string[] => { return Array.from(new Set(items)) } -export const getProjectFromPrefix = (text: string, projectPrefix: string | null = null): string | null => { - if (projectPrefix === null) { - const prefixes = PREFIXES[getQuickAddMagicMode()] - if (prefixes === undefined) { - return null - } - projectPrefix = prefixes.project +export const getProjectFromPrefix = (text: string, prefixMode: PrefixMode): string | null => { + const projectPrefix = PREFIXES[prefixMode]?.project + if(typeof projectPrefix === 'undefined') { + return null } const projects: string[] = getItemsFromPrefix(text, projectPrefix) return projects.length > 0 ? projects[0] : null } -export const getLabelsFromPrefix = (text: string, projectPrefix: string | null = null): string[] | null => { - if (projectPrefix === null) { - const prefixes = PREFIXES[getQuickAddMagicMode()] - if (prefixes === undefined) { - return null - } - projectPrefix = prefixes.label +export const getLabelsFromPrefix = (text: string, prefixMode: PrefixMode): string[] | null => { + const projectPrefix = PREFIXES[prefixMode]?.project + if(typeof projectPrefix === 'undefined') { + return null } return getItemsFromPrefix(text, projectPrefix) } diff --git a/src/stores/tasks.ts b/src/stores/tasks.ts index 40e7f1bd1..3166e353f 100644 --- a/src/stores/tasks.ts +++ b/src/stores/tasks.ts @@ -6,8 +6,7 @@ import TaskService from '@/services/task' import TaskAssigneeService from '@/services/taskAssignee' import LabelTaskService from '@/services/labelTask' -import {playPop} from '@/helpers/playPop' -import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode' +import {playPopSound} from '@/helpers/playPop' import {cleanupItemText, parseTaskText, PREFIXES} from '@/modules/parseTaskText' import TaskAssigneeModel from '@/models/taskAssignee' @@ -29,6 +28,7 @@ import {useAttachmentStore} from '@/stores/attachments' import {useKanbanStore} from '@/stores/kanban' import {useBaseStore} from '@/stores/base' import ProjectUserService from '@/services/projectUsers' +import {useAuthStore} from '@/stores/auth' interface MatchedAssignee extends IUser { match: string, @@ -106,6 +106,7 @@ export const useTaskStore = defineStore('task', () => { const attachmentStore = useAttachmentStore() const labelStore = useLabelStore() const projectStore = useProjectStore() + const authStore = useAuthStore() const tasks = ref<{ [id: ITask['id']]: ITask }>({}) // TODO: or is this ITask[] const isLoading = ref(false) @@ -142,8 +143,8 @@ export const useTaskStore = defineStore('task', () => { try { const updatedTask = await taskService.update(task) kanbanStore.setTaskInBucket(updatedTask) - if (task.done) { - playPop() + if (task.done && useAuthStore().settings.frontendSettings.playSoundWhenDone) { + playPopSound() } return updatedTask } finally { @@ -398,7 +399,7 @@ export const useTaskStore = defineStore('task', () => { Partial, ) { const cancel = setModuleLoading(setIsLoading) - const quickAddMagicMode = getQuickAddMagicMode() + const quickAddMagicMode = authStore.settings.frontendSettings.quickAddMagicMode const parsedTask = parseTaskText(title, quickAddMagicMode) const foundProjectId = await findProjectId({ diff --git a/src/views/user/settings/General.vue b/src/views/user/settings/General.vue index e3872ed4a..ce026d34b 100644 --- a/src/views/user/settings/General.vue +++ b/src/views/user/settings/General.vue @@ -97,7 +97,7 @@ {{ $t('user.settings.quickAddMagic.title') }}
- @@ -160,7 +160,6 @@ import ProjectSearch from '@/components/tasks/partials/projectSearch.vue' import {SUPPORTED_LOCALES} from '@/i18n' import {playSoundWhenDoneKey, playPopSound} from '@/helpers/playPop' -import {getQuickAddMagicMode, setQuickAddMagicMode} from '@/helpers/quickAddMagicMode' import {createRandomID} from '@/helpers/randomId' import {success} from '@/message' import {AuthenticatedHTTPFactory} from '@/helpers/fetcher' @@ -259,9 +258,6 @@ const loading = computed(() => authStore.isLoadingGeneralSettings) // ) async function updateSettings() { - localStorage.setItem(playSoundWhenDoneKey, playSoundWhenDone.value ? 'true' : 'false') - setQuickAddMagicMode(quickAddMagicMode.value) - await authStore.saveUserSettings({ settings: {...settings.value}, }) From 4b0022664a6e6edfb00972229afd3048d2087322 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sun, 11 Jun 2023 17:43:55 +0200 Subject: [PATCH 03/12] feat(user): set default settings when loading persisted --- src/stores/auth.ts | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/stores/auth.ts b/src/stores/auth.ts index c56adb953..b08a1a649 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -1,10 +1,10 @@ import {computed, readonly, ref} from 'vue' -import {defineStore, acceptHMRUpdate} from 'pinia' +import {acceptHMRUpdate, defineStore} from 'pinia' -import {HTTPFactory, AuthenticatedHTTPFactory} from '@/helpers/fetcher' -import {i18n, getCurrentLanguage, saveLanguage, setLanguage} from '@/i18n' +import {AuthenticatedHTTPFactory, HTTPFactory} from '@/helpers/fetcher' +import {getCurrentLanguage, i18n, saveLanguage, setLanguage} from '@/i18n' import {objectToSnakeCase} from '@/helpers/case' -import UserModel, { getAvatarUrl, getDisplayName } from '@/models/user' +import UserModel, {getAvatarUrl, getDisplayName} from '@/models/user' import UserSettingsService from '@/services/userSettings' import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth' import {setModuleLoading} from '@/stores/helper' @@ -16,6 +16,7 @@ import router from '@/router' import {useConfigStore} from '@/stores/config' import UserSettingsModel from '@/models/userSettings' import {MILLISECONDS_A_SECOND} from '@/constants/date' +import {PrefixMode} from '@/modules/parseTaskText' function redirectToProviderIfNothingElseIsEnabled() { const {auth} = useConfigStore() @@ -74,7 +75,7 @@ export const useAuthStore = defineStore('auth', () => { reloadAvatar() if (newUser.settings) { - settings.value = new UserSettingsModel(newUser.settings) + loadSettings(newUser.settings) } isLinkShareAuth.value = newUser.id < 0 @@ -82,12 +83,24 @@ export const useAuthStore = defineStore('auth', () => { } function setUserSettings(newSettings: IUserSettings) { - settings.value = new UserSettingsModel(newSettings) + loadSettings(newSettings) info.value = new UserModel({ ...info.value !== null ? info.value : {}, name: newSettings.name, }) } + + function loadSettings(newSettings: IUserSettings) { + settings.value = new UserSettingsModel({ + ...newSettings, + frontendSettings: { + ...newSettings.frontendSettings, + // Need to set default settings here in case the user does not have any saved in the api already + quickAddMagicMode: PrefixMode.Default, + } + }) + console.log(settings.value.frontendSettings) + } function setAuthenticated(newAuthenticated: boolean) { authenticated.value = newAuthenticated From 5325f6d7d96e25b447dad12e065fc3bab01379fc Mon Sep 17 00:00:00 2001 From: kolaente Date: Mon, 12 Jun 2023 15:57:18 +0200 Subject: [PATCH 04/12] feat(user): migrate color scheme settings to persistance in db --- src/composables/useColorScheme.ts | 6 +++-- src/modelTypes/IUserSettings.ts | 2 ++ src/models/userSettings.ts | 1 + src/stores/auth.ts | 6 +++-- src/views/user/settings/General.vue | 35 ++++++----------------------- 5 files changed, 18 insertions(+), 32 deletions(-) diff --git a/src/composables/useColorScheme.ts b/src/composables/useColorScheme.ts index e15948dd3..43fad38df 100644 --- a/src/composables/useColorScheme.ts +++ b/src/composables/useColorScheme.ts @@ -1,6 +1,7 @@ import {computed, watch, readonly} from 'vue' -import {useStorage, createSharedComposable, usePreferredColorScheme, tryOnMounted} from '@vueuse/core' +import {createSharedComposable, usePreferredColorScheme, tryOnMounted} from '@vueuse/core' import type {BasicColorSchema} from '@vueuse/core' +import {useAuthStore} from '@/stores/auth' const STORAGE_KEY = 'color-scheme' @@ -17,7 +18,8 @@ const CLASS_LIGHT = 'light' // - value is synced via `createSharedComposable` // https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts export const useColorScheme = createSharedComposable(() => { - const store = useStorage(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING) + const authStore = useAuthStore() + const store = computed(() => authStore.settings.frontendSettings.colorSchema) const preferredColorScheme = usePreferredColorScheme() diff --git a/src/modelTypes/IUserSettings.ts b/src/modelTypes/IUserSettings.ts index 017939dd1..b1c120799 100644 --- a/src/modelTypes/IUserSettings.ts +++ b/src/modelTypes/IUserSettings.ts @@ -2,10 +2,12 @@ import type {IAbstract} from './IAbstract' import type {IProject} from './IProject' import type {PrefixMode} from '@/modules/parseTaskText' +import type {BasicColorSchema} from '@vueuse/core' export interface IFrontendSettings { playSoundWhenDone: boolean quickAddMagicMode: PrefixMode + colorSchema: BasicColorSchema } export interface IUserSettings extends IAbstract { diff --git a/src/models/userSettings.ts b/src/models/userSettings.ts index f3799d4b7..dd11e5987 100644 --- a/src/models/userSettings.ts +++ b/src/models/userSettings.ts @@ -18,6 +18,7 @@ export default class UserSettingsModel extends AbstractModel impl frontendSettings: IFrontendSettings = { playSoundWhenDone: true, quickAddMagicMode: PrefixMode.Default, + colorSchema: 'auto' } constructor(data: Partial = {}) { diff --git a/src/stores/auth.ts b/src/stores/auth.ts index b08a1a649..e8a7d371e 100644 --- a/src/stores/auth.ts +++ b/src/stores/auth.ts @@ -94,12 +94,14 @@ export const useAuthStore = defineStore('auth', () => { settings.value = new UserSettingsModel({ ...newSettings, frontendSettings: { - ...newSettings.frontendSettings, // Need to set default settings here in case the user does not have any saved in the api already + playSoundWhenDone: true, quickAddMagicMode: PrefixMode.Default, + colorSchema: 'auto', + ...newSettings.frontendSettings, } }) - console.log(settings.value.frontendSettings) + console.log('settings from auth store', {...settings.value.frontendSettings}) } function setAuthenticated(newAuthenticated: boolean) { diff --git a/src/views/user/settings/General.vue b/src/views/user/settings/General.vue index ce026d34b..44460adb5 100644 --- a/src/views/user/settings/General.vue +++ b/src/views/user/settings/General.vue @@ -111,7 +111,7 @@ {{ $t('user.settings.appearance.title') }}
-