aboutsummaryrefslogtreecommitdiffstats
path: root/apps/theming/src/components/UserAppMenuSection.vue
blob: 56abd357274360662986c1122b432ed4bdb15e13 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
<!--
  - SPDX-FileCopyrightText: 2023 Nextcloud GmbH and Nextcloud contributors
  - SPDX-License-Identifier: AGPL-3.0-or-later
-->
<template>
	<NcSettingsSection :name="t('theming', 'Navigation bar settings')">
		<p>
			{{ t('theming', 'You can configure the app order used for the navigation bar. The first entry will be the default app, opened after login or when clicking on the logo.') }}
		</p>
		<NcNoteCard v-if="enforcedDefaultApp" :id="elementIdEnforcedDefaultApp" type="info">
			{{ t('theming', 'The default app can not be changed because it was configured by the administrator.') }}
		</NcNoteCard>
		<NcNoteCard v-if="hasAppOrderChanged" :id="elementIdAppOrderChanged" type="info">
			{{ t('theming', 'The app order was changed, to see it in action you have to reload the page.') }}
		</NcNoteCard>

		<AppOrderSelector class="user-app-menu-order"
			:aria-details="ariaDetailsAppOrder"
			:value="appOrder"
			@update:value="updateAppOrder" />

		<NcButton data-test-id="btn-apporder-reset"
			:disabled="!hasCustomAppOrder"
			type="tertiary"
			@click="resetAppOrder">
			<template #icon>
				<IconUndo :size="20" />
			</template>
			{{ t('theming', 'Reset default app order') }}
		</NcButton>
	</NcSettingsSection>
</template>

<script lang="ts">
import type { IApp } from './AppOrderSelector.vue'
import type { INavigationEntry } from '../../../../core/src/types/navigation.d.ts'

import { showError } from '@nextcloud/dialogs'
import { loadState } from '@nextcloud/initial-state'
import { translate as t } from '@nextcloud/l10n'
import { generateOcsUrl } from '@nextcloud/router'
import { computed, defineComponent, ref } from 'vue'

import axios from '@nextcloud/axios'
import AppOrderSelector from './AppOrderSelector.vue'
import IconUndo from 'vue-material-design-icons/Undo.vue'
import NcButton from '@nextcloud/vue/dist/Components/NcButton.js'
import NcNoteCard from '@nextcloud/vue/dist/Components/NcNoteCard.js'
import NcSettingsSection from '@nextcloud/vue/dist/Components/NcSettingsSection.js'

/** The app order user setting */
type IAppOrder = Record<string, { order: number, app?: string }>

/** OCS responses */
interface IOCSResponse<T> {
	ocs: {
		meta: unknown
		data: T
	}
}

export default defineComponent({
	name: 'UserAppMenuSection',
	components: {
		AppOrderSelector,
		IconUndo,
		NcButton,
		NcNoteCard,
		NcSettingsSection,
	},
	setup() {
		const {
			/** The app order currently defined by the user */
			userAppOrder,
			/** The enforced default app set by the administrator (if any) */
			enforcedDefaultApp,
		} = loadState<{ userAppOrder: IAppOrder, enforcedDefaultApp: string }>('theming', 'navigationBar')

		/**
		 * Array of all available apps, it is set by a core controller for the app menu, so it is always available
		 */
		 const initialAppOrder = loadState<INavigationEntry[]>('core', 'apps')
			.filter(({ type }) => type === 'link')
			.map((app) => ({ ...app, label: app.name, default: app.default && app.app === enforcedDefaultApp }))

		/**
		 * Check if a custom app order is used or the default is shown
		 */
		const hasCustomAppOrder = ref(!Array.isArray(userAppOrder) || Object.values(userAppOrder).length > 0)

		/**
		 * Track if the app order has changed, so the user can be informed to reload
		 */
		const hasAppOrderChanged = computed(() => initialAppOrder.some(({ id }, index) => id !== appOrder.value[index].id))

		/** ID of the "app order has changed" NcNodeCard, used for the aria-details of the apporder */
		const elementIdAppOrderChanged = 'theming-apporder-changed-infocard'

		/** ID of the "you can not change the default app" NcNodeCard, used for the aria-details of the apporder */
		const elementIdEnforcedDefaultApp = 'theming-apporder-changed-infocard'

		/**
		 * The aria-details value of the app order selector
		 * contains the space separated list of element ids of NcNoteCards
		 */
		const ariaDetailsAppOrder = computed(() => (hasAppOrderChanged.value ? `${elementIdAppOrderChanged} ` : '') + (enforcedDefaultApp ? elementIdEnforcedDefaultApp : ''))

		/**
		 * The current apporder (sorted by user)
		 */
		const appOrder = ref([...initialAppOrder])

		/**
		 * Update the app order, called when the user sorts entries
		 * @param value The new app order value
		 */
		const updateAppOrder = (value: IApp[]) => {
			const order: IAppOrder = {}
			value.forEach(({ app, id }, index) => {
				order[id] = { order: index, app }
			})

			saveSetting('apporder', order)
				.then(() => {
					appOrder.value = value as never
					hasCustomAppOrder.value = true
				})
				.catch((error) => {
					console.warn('Could not set the app order', error)
					showError(t('theming', 'Could not set the app order'))
				})
		}

		/**
		 * Reset the app order to the default
		 */
		const resetAppOrder = async () => {
			try {
				await saveSetting('apporder', [])
				hasCustomAppOrder.value = false

				// Reset our app order list
				const { data } = await axios.get<IOCSResponse<INavigationEntry[]>>(generateOcsUrl('/core/navigation/apps'), {
					headers: {
						'OCS-APIRequest': 'true',
					},
				})
				appOrder.value = data.ocs.data.map((app) => ({ ...app, label: app.name, default: app.default && app.app === enforcedDefaultApp }))
			} catch (error) {
				console.warn(error)
				showError(t('theming', 'Could not reset the app order'))
			}
		}

		const saveSetting = async (key: string, value: unknown) => {
			const url = generateOcsUrl('apps/provisioning_api/api/v1/config/users/{appId}/{configKey}', {
				appId: 'core',
				configKey: key,
			})
			return await axios.post(url, {
				configValue: JSON.stringify(value),
			})
		}

		return {
			appOrder,
			updateAppOrder,
			resetAppOrder,

			enforcedDefaultApp,
			hasAppOrderChanged,
			hasCustomAppOrder,

			ariaDetailsAppOrder,
			elementIdAppOrderChanged,
			elementIdEnforcedDefaultApp,

			t,
		}
	},
})
</script>

<style scoped lang="scss">
.user-app-menu-order {
	margin-block: 12px;
}
</style>