From aac01c7a35836421c17882b4f77334fc14bfeaec Mon Sep 17 00:00:00 2001 From: Elscrux Date: Sun, 2 Jun 2024 08:15:53 +0000 Subject: [PATCH] feat: default view setting (#2306) This PR adds configuration of default project view in settings, which is used when the user has not visited the project and thus last view hasn't yet been saved in projects. Updates old settings and adds "First View" option with fallback. Resolves https://kolaente.dev/vikunja/vikunja/issues/2153 Co-authored-by: Elscrux Reviewed-on: https://kolaente.dev/vikunja/vikunja/pulls/2306 Reviewed-by: konrad Co-authored-by: Elscrux Co-committed-by: Elscrux --- frontend/src/i18n/lang/en.json | 4 ++++ frontend/src/modelTypes/IProjectView.ts | 6 +++++ frontend/src/modelTypes/IUserSettings.ts | 2 ++ frontend/src/models/userSettings.ts | 2 ++ frontend/src/views/project/ProjectView.vue | 25 +++++++++++++++----- frontend/src/views/user/settings/General.vue | 20 ++++++++++++++++ 6 files changed, 53 insertions(+), 6 deletions(-) diff --git a/frontend/src/i18n/lang/en.json b/frontend/src/i18n/lang/en.json index 2fe5e9361..ada85a81f 100644 --- a/frontend/src/i18n/lang/en.json +++ b/frontend/src/i18n/lang/en.json @@ -95,6 +95,7 @@ "weekStartMonday": "Monday", "language": "Language", "defaultProject": "Default Project", + "defaultView": "Default View", "timezone": "Time Zone", "overdueTasksRemindersTime": "Overdue tasks reminder email time", "filterUsedOnOverview": "Saved filter used on the overview page" @@ -314,6 +315,9 @@ "delete": "Delete" } }, + "first": { + "title": "First View" + }, "list": { "title": "List", "add": "Add", diff --git a/frontend/src/modelTypes/IProjectView.ts b/frontend/src/modelTypes/IProjectView.ts index 076f072af..4a0565729 100644 --- a/frontend/src/modelTypes/IProjectView.ts +++ b/frontend/src/modelTypes/IProjectView.ts @@ -9,6 +9,12 @@ export const PROJECT_VIEW_KINDS = { } as const export type ProjectViewKind = typeof PROJECT_VIEW_KINDS[keyof typeof PROJECT_VIEW_KINDS] +export const DEFAULT_PROJECT_VIEW_SETTINGS = { + FIRST: 'first', + ...PROJECT_VIEW_KINDS, +} as const +export type DefaultProjectViewKind = typeof DEFAULT_PROJECT_VIEW_SETTINGS[keyof typeof DEFAULT_PROJECT_VIEW_SETTINGS] + export const PROJECT_VIEW_BUCKET_CONFIGURATION_MODES = ['none', 'manual', 'filter'] export type ProjectViewBucketConfigurationMode = typeof PROJECT_VIEW_BUCKET_CONFIGURATION_MODES[number] diff --git a/frontend/src/modelTypes/IUserSettings.ts b/frontend/src/modelTypes/IUserSettings.ts index 5f20e3852..0ca266288 100644 --- a/frontend/src/modelTypes/IUserSettings.ts +++ b/frontend/src/modelTypes/IUserSettings.ts @@ -4,12 +4,14 @@ import type {IProject} from './IProject' import type {PrefixMode} from '@/modules/parseTaskText' import type {BasicColorSchema} from '@vueuse/core' import type {SupportedLocale} from '@/i18n' +import type {DefaultProjectViewKind} from '@/modelTypes/IProjectView' export interface IFrontendSettings { playSoundWhenDone: boolean quickAddMagicMode: PrefixMode colorSchema: BasicColorSchema filterIdUsedOnOverview: IProject['id'] | null + defaultView?: DefaultProjectViewKind } export interface IUserSettings extends IAbstract { diff --git a/frontend/src/models/userSettings.ts b/frontend/src/models/userSettings.ts index 397963dff..31ec7b745 100644 --- a/frontend/src/models/userSettings.ts +++ b/frontend/src/models/userSettings.ts @@ -3,6 +3,7 @@ import AbstractModel from './abstractModel' import type {IFrontendSettings, IUserSettings} from '@/modelTypes/IUserSettings' import {getBrowserLanguage} from '@/i18n' import {PrefixMode} from '@/modules/parseTaskText' +import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView' export default class UserSettingsModel extends AbstractModel implements IUserSettings { name = '' @@ -19,6 +20,7 @@ export default class UserSettingsModel extends AbstractModel impl playSoundWhenDone: true, quickAddMagicMode: PrefixMode.Default, colorSchema: 'auto', + defaultView: DEFAULT_PROJECT_VIEW_SETTINGS.FIRST, } constructor(data: Partial = {}) { diff --git a/frontend/src/views/project/ProjectView.vue b/frontend/src/views/project/ProjectView.vue index 86bdfa449..33a356245 100644 --- a/frontend/src/views/project/ProjectView.vue +++ b/frontend/src/views/project/ProjectView.vue @@ -8,6 +8,8 @@ import ProjectList from '@/components/project/views/ProjectList.vue' import ProjectGantt from '@/components/project/views/ProjectGantt.vue' import ProjectTable from '@/components/project/views/ProjectTable.vue' import ProjectKanban from '@/components/project/views/ProjectKanban.vue' +import {useAuthStore} from '@/stores/auth' +import {DEFAULT_PROJECT_VIEW_SETTINGS} from '@/modelTypes/IProjectView' const { projectId, @@ -19,6 +21,7 @@ const { const router = useRouter() const projectStore = useProjectStore() +const authStore = useAuthStore() const currentView = computed(() => { const project = projectStore.projects[projectId] @@ -26,17 +29,27 @@ const currentView = computed(() => { return project?.views.find(v => v.id === viewId) }) -function redirectToFirstViewIfNecessary() { +function redirectToDefaultViewIfNecessary() { if (viewId === 0 || !projectStore.projects[projectId]?.views.find(v => v.id === viewId)) { // Ideally, we would do that in the router redirect, but the projects (and therefore, the views) // are not always loaded then. - const firstViewId = projectStore.projects[projectId]?.views[0].id - if (firstViewId) { + + let view + if (authStore.settings.frontendSettings.defaultView !== DEFAULT_PROJECT_VIEW_SETTINGS.FIRST) { + view = projectStore.projects[projectId]?.views.find(v => v.viewKind === authStore.settings.frontendSettings.defaultView) + } + + // Use the first view as fallback if the default view is not available + if (view === undefined && projectStore.projects[projectId]?.views?.length > 0) { + view = projectStore.projects[projectId]?.views[0] + } + + if (view) { router.replace({ name: 'project.view', params: { projectId, - viewId: firstViewId, + viewId: view.id, }, }) } @@ -45,13 +58,13 @@ function redirectToFirstViewIfNecessary() { watch( () => viewId, - redirectToFirstViewIfNecessary, + redirectToDefaultViewIfNecessary, {immediate: true}, ) watch( () => projectStore.projects[projectId], - redirectToFirstViewIfNecessary, + redirectToDefaultViewIfNecessary, ) // using a watcher instead of beforeEnter because beforeEnter is not called when only the viewId changes diff --git a/frontend/src/views/user/settings/General.vue b/frontend/src/views/user/settings/General.vue index 64622d11b..97ec38023 100644 --- a/frontend/src/views/user/settings/General.vue +++ b/frontend/src/views/user/settings/General.vue @@ -26,6 +26,22 @@ +
+ +
+ +
+
`${t('user.settings.general.title')} - ${t('user.settings.title')}`) @@ -253,12 +270,15 @@ function useAvailableTimezones() { const availableTimezones = useAvailableTimezones() const authStore = useAuthStore() + 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, + // Add fallback for old settings that don't have the default view set + defaultView: authStore.settings.frontendSettings.defaultView ?? DEFAULT_PROJECT_VIEW_SETTINGS.FIRST, }, }) const id = ref(createRandomID())