Skip to content

Commit 4346729

Browse files
committed
fix(talk/settings): new design
Signed-off-by: Grigorii K. Shartsev <[email protected]>
1 parent f661973 commit 4346729

11 files changed

+688
-399
lines changed

src/talk/renderer/Settings/DesktopSettingsSection.vue

Lines changed: 63 additions & 223 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,20 @@
44
-->
55

66
<script setup lang="ts">
7-
import type { NcSelectOption } from '../composables/useNcSelectModel.ts'
8-
97
import { t } from '@nextcloud/l10n'
108
import { storeToRefs } from 'pinia'
11-
import { computed, ref } from 'vue'
12-
import NcButton from '@nextcloud/vue/components/NcButton'
13-
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
14-
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
15-
import NcTextField from '@nextcloud/vue/components/NcTextField'
16-
import IconBellRingOutline from 'vue-material-design-icons/BellRingOutline.vue'
17-
import IconCardAccountPhoneOutline from 'vue-material-design-icons/CardAccountPhoneOutline.vue'
18-
import IconMagnify from 'vue-material-design-icons/Magnify.vue'
19-
import IconMinus from 'vue-material-design-icons/Minus.vue'
20-
import IconPhoneRingOutline from 'vue-material-design-icons/PhoneRingOutline.vue'
21-
import IconPlus from 'vue-material-design-icons/Plus.vue'
22-
import IconRestore from 'vue-material-design-icons/Restore.vue'
9+
import NcFormBox from '@nextcloud/vue/components/NcFormBox'
10+
import NcFormBoxSwitch from '@nextcloud/vue/components/NcFormBoxSwitch'
11+
import NcFormGroup from '@nextcloud/vue/components/NcFormGroup'
12+
import NcRadioGroup from '@nextcloud/vue/components/NcRadioGroup'
13+
import NcRadioGroupButton from '@nextcloud/vue/components/NcRadioGroupButton'
2314
import IconThemeLightDark from 'vue-material-design-icons/ThemeLightDark.vue'
24-
import IconVolumeHigh from 'vue-material-design-icons/VolumeHigh.vue'
25-
import SettingsFormGroup from './components/SettingsFormGroup.vue'
26-
import SettingsSelect from './components/SettingsSelect.vue'
27-
import SettingsSubsection from './components/SettingsSubsection.vue'
28-
import { ZOOM_MAX, ZOOM_MIN } from '../../../constants.js'
29-
import { useNcSelectModel } from '../composables/useNcSelectModel.ts'
15+
import IconWeatherNight from 'vue-material-design-icons/WeatherNight.vue'
16+
import IconWeatherSunny from 'vue-material-design-icons/WeatherSunny.vue'
17+
import DesktopSettingsSectionRelaunchNote from './components/DesktopSettingsSectionRelaunchNote.vue'
18+
import UiFormBoxAudioOutput from './components/UiFormBoxAudioOutput.vue'
19+
import UiFormBoxSelectNative from './components/UiFormBoxSelectNative.vue'
20+
import UiFormGroupZoom from './components/UiFormGroupZoom.vue'
3021
import { useAppConfigStore } from './appConfig.store.ts'
3122
import { useAppConfigValue } from './useAppConfigValue.ts'
3223
@@ -35,231 +26,80 @@ const isLinux = window.systemInfo.isLinux
3526
const { isRelaunchRequired } = storeToRefs(useAppConfigStore())
3627
3728
const launchAtStartup = useAppConfigValue('launchAtStartup')
38-
3929
const theme = useAppConfigValue('theme')
40-
const themeOptions = [
41-
{ label: t('talk_desktop', 'System default'), value: 'default' } as const,
42-
{ label: t('talk_desktop', 'Light'), value: 'light' } as const,
43-
{ label: t('talk_desktop', 'Dark'), value: 'dark' } as const,
44-
]
45-
const themeOption = useNcSelectModel(theme, themeOptions)
46-
4730
const systemTitleBar = useAppConfigValue('systemTitleBar')
4831
const monochromeTrayIcon = useAppConfigValue('monochromeTrayIcon')
49-
50-
const zoomFactorConfig = useAppConfigValue('zoomFactor')
51-
const zoomFactor = computed({
52-
get: () => zoomFactorConfig.value,
53-
set: (value: number) => {
54-
zoomFactorConfig.value = isFinite(value) ? Math.min(Math.max(value, ZOOM_MIN), ZOOM_MAX) : 1
55-
},
56-
})
57-
const zoomFactorPercentage = computed({
58-
get: () => Math.round(zoomFactor.value * 100).toString(),
59-
set: (value: string) => {
60-
zoomFactor.value = parseFloat(value) / 100
61-
},
62-
})
63-
const ZOOM_STEP = Math.sqrt(1.2)
64-
const ctrl = window.systemInfo.isMac ? 'Ctrl/Cmd' : 'Ctrl'
65-
const zoomHint = t('talk_desktop', 'Zoom can be also changed by {key} or mouse wheel. Reset by {resetKey}', {
66-
key: `<kbd>${ctrl} + ±</kbd>`,
67-
resetKey: `<kbd>${ctrl} + 0</kbd>`,
68-
}, undefined, { escape: false })
69-
70-
const generalNotificationOptions = [
71-
{ label: t('talk_desktop', 'Always'), value: 'always' } as const,
72-
{ label: t('talk_desktop', 'When not in "Do not disturb"'), value: 'respect-dnd' } as const,
73-
{ label: t('talk_desktop', 'Never'), value: 'never' } as const,
74-
]
32+
const zoomFactor = useAppConfigValue('zoomFactor')
7533
7634
const playSoundChat = useAppConfigValue('playSoundChat')
77-
const playSoundChatOption = useNcSelectModel(playSoundChat, generalNotificationOptions)
78-
7935
const playSoundCall = useAppConfigValue('playSoundCall')
80-
const playSoundCallOption = useNcSelectModel(playSoundCall, generalNotificationOptions)
81-
8236
const enableCallbox = useAppConfigValue('enableCallbox')
83-
const enableCallboxOption = useNcSelectModel(enableCallbox, generalNotificationOptions)
37+
const notificationLevelOptions = [
38+
{ label: t('talk_desktop', 'Always'), value: 'always' },
39+
{ label: t('talk_desktop', 'When not in "Do not disturb"'), value: 'respect-dnd' },
40+
{ label: t('talk_desktop', 'Never'), value: 'never' },
41+
]
8442
8543
const secondarySpeaker = useAppConfigValue('secondarySpeaker')
86-
87-
const EMPTY_DEVICE_OPTION = { value: null, label: t('talk_desktop', 'None') }
88-
const secondarySpeakerOptions = ref<NcSelectOption<string | null>[]>([])
89-
9044
const secondarySpeakerDevice = useAppConfigValue('secondarySpeakerDevice')
91-
const secondarySpeakerDeviceOption = useNcSelectModel(secondarySpeakerDevice, secondarySpeakerOptions, EMPTY_DEVICE_OPTION)
92-
93-
/**
94-
* Enumerate available media devices (audio output) in format of NcSelectOption
95-
*/
96-
async function initializeDevices() {
97-
let stream = null
98-
try {
99-
stream = await navigator.mediaDevices.getUserMedia({ audio: true })
100-
const deviceOptions = (await navigator.mediaDevices.enumerateDevices() ?? [])
101-
.filter((device) => device.kind === 'audiooutput')
102-
.map((device) => ({ value: device.deviceId, label: device.label }))
103-
104-
secondarySpeakerOptions.value = [EMPTY_DEVICE_OPTION, ...deviceOptions]
105-
} catch (error) {
106-
console.error('Error while requesting or initializing audio devices: ', error)
107-
secondarySpeakerOptions.value = [EMPTY_DEVICE_OPTION]
108-
} finally {
109-
if (stream) {
110-
stream.getTracks().forEach((track) => track.stop())
111-
}
112-
}
113-
}
114-
initializeDevices()
115-
116-
/**
117-
* Restart the app
118-
*/
119-
function relaunch() {
120-
window.TALK_DESKTOP.relaunchWindow()
121-
}
12245
</script>
12346

12447
<template>
125-
<div>
126-
<NcNoteCard v-if="isRelaunchRequired" type="info" class="relaunch-require-note-card">
127-
<div class="relaunch-require-note-card__content">
128-
<span>{{ t('talk_desktop', 'Some changes require a relaunch to take effect') }}</span>
129-
<NcButton
130-
variant="primary"
131-
size="small"
132-
class="relaunch-require-note-card__button"
133-
@click="relaunch">
134-
{{ t('talk_desktop', 'Restart') }}
135-
</NcButton>
136-
</div>
137-
</NcNoteCard>
48+
<div class="desktop-settings-section">
49+
<DesktopSettingsSectionRelaunchNote v-if="isRelaunchRequired" />
13850

139-
<SettingsSubsection v-if="!isLinux" :name="t('talk_desktop', 'General')">
140-
<NcCheckboxRadioSwitch v-model="launchAtStartup" type="switch">
141-
{{ t('talk_desktop', 'Launch at startup') }}
142-
</NcCheckboxRadioSwitch>
143-
</SettingsSubsection>
51+
<NcFormBox v-if="!isLinux">
52+
<NcFormBoxSwitch v-model="launchAtStartup" :label="t('talk_desktop', 'Launch at startup')" />
53+
</NcFormBox>
14454

145-
<SettingsSubsection :name="t('talk_desktop', 'Appearance')">
146-
<SettingsSelect v-model="themeOption" :options="themeOptions" :label="t('talk_desktop', 'Theme')">
147-
<template #icon="{ size }">
148-
<IconThemeLightDark :size="size" />
55+
<NcRadioGroup v-model="theme" :label="t('talk_desktop', 'Theme')">
56+
<NcRadioGroupButton :label="t('talk_desktop', 'System default')" value="default">
57+
<template #icon>
58+
<IconThemeLightDark :size="20" />
14959
</template>
150-
</SettingsSelect>
151-
152-
<NcCheckboxRadioSwitch v-model="monochromeTrayIcon" type="switch">
153-
{{ t('talk_desktop', 'Use monochrome tray icon') }}
154-
</NcCheckboxRadioSwitch>
155-
156-
<NcCheckboxRadioSwitch v-model="systemTitleBar" type="switch">
157-
{{ t('talk_desktop', 'Use system title bar') }}
158-
</NcCheckboxRadioSwitch>
159-
160-
<SettingsFormGroup :label="t('talk_desktop', 'Zoom')">
161-
<template #icon="{ size }">
162-
<IconMagnify :size="size" />
60+
</NcRadioGroupButton>
61+
<NcRadioGroupButton :label="t('talk_desktop', 'Light')" value="light">
62+
<template #icon>
63+
<IconWeatherSunny :size="20" />
16364
</template>
164-
<template #description>
165-
<!-- eslint-disable-next-line vue/no-v-html -->
166-
<span v-html="zoomHint" />
65+
</NcRadioGroupButton>
66+
<NcRadioGroupButton :label="t('talk_desktop', 'Dark')" value="dark">
67+
<template #icon>
68+
<IconWeatherNight :size="20" />
16769
</template>
168-
<template #default="{ inputId, descriptionId }">
169-
<NcButton :aria-label="t('talk_desktop', 'Zoom out')" variant="tertiary" @click="zoomFactor /= ZOOM_STEP">
170-
<template #icon>
171-
<IconMinus :size="20" />
172-
</template>
173-
</NcButton>
174-
<NcTextField
175-
:id="inputId"
176-
class="zoom-input"
177-
:aria-describedby="descriptionId"
178-
label-outside
179-
inputmode="number"
180-
:model-value="zoomFactorPercentage"
181-
@change="zoomFactorPercentage = $event.target.value"
182-
@blur="$event.target.value = zoomFactorPercentage" />
183-
<NcButton :aria-label="t('talk_desktop', 'Zoom in')" variant="tertiary" @click="zoomFactor *= ZOOM_STEP">
184-
<template #icon>
185-
<IconPlus :size="20" />
186-
</template>
187-
</NcButton>
188-
<NcButton @click="zoomFactor = 1">
189-
<template #icon>
190-
<IconRestore :size="20" />
191-
</template>
192-
{{ t('talk_desktop', 'Reset') }}
193-
</NcButton>
194-
</template>
195-
</SettingsFormGroup>
196-
</SettingsSubsection>
197-
198-
<SettingsSubsection :name="t('talk_desktop', 'Notifications and sounds')">
199-
<SettingsSelect v-model="playSoundChatOption" :options="generalNotificationOptions" :label="t('talk_desktop', 'Play chat notification sound')">
200-
<template #icon="{ size }">
201-
<IconBellRingOutline :size="size" />
202-
</template>
203-
</SettingsSelect>
204-
205-
<SettingsSelect v-model="playSoundCallOption" :options="generalNotificationOptions" :label="t('talk_desktop', 'Play call notification sound')">
206-
<template #icon="{ size }">
207-
<IconPhoneRingOutline :size="size" />
208-
</template>
209-
</SettingsSelect>
210-
211-
<SettingsSelect v-model="enableCallboxOption" :options="generalNotificationOptions" :label="t('talk_desktop', 'Show call notification popup')">
212-
<template #icon="{ size }">
213-
<IconCardAccountPhoneOutline :size="size" />
214-
</template>
215-
</SettingsSelect>
216-
217-
<NcCheckboxRadioSwitch v-model="secondarySpeaker" type="switch">
218-
{{ t('talk_desktop', 'Also repeat call notification on a secondary speaker') }}
219-
</NcCheckboxRadioSwitch>
220-
221-
<SettingsSelect
222-
v-if="secondarySpeaker"
223-
v-model="secondarySpeakerDeviceOption"
224-
:options="secondarySpeakerOptions"
225-
:disabled="secondarySpeakerOptions.length === 1"
226-
:label="t('talk_desktop', 'Secondary speaker')">
227-
<template #icon="{ size }">
228-
<IconVolumeHigh :size="size" />
229-
</template>
230-
<template #action>
231-
<NcButton variant="tertiary" @click="initializeDevices">
232-
<template #icon>
233-
<IconRestore :size="20" />
234-
</template>
235-
</NcButton>
236-
</template>
237-
</SettingsSelect>
238-
</SettingsSubsection>
70+
</NcRadioGroupButton>
71+
</NcRadioGroup>
72+
73+
<NcFormGroup :label="t('talk_desktop', 'Appearance')">
74+
<NcFormBox>
75+
<NcFormBoxSwitch v-model="monochromeTrayIcon" :label="t('talk_desktop', 'Use monochrome tray icon')" />
76+
<NcFormBoxSwitch v-model="systemTitleBar" :label="t('talk_desktop', 'Use system title bar')" />
77+
</NcFormBox>
78+
</NcFormGroup>
79+
80+
<UiFormGroupZoom v-model="zoomFactor" />
81+
82+
<NcFormGroup :label="t('talk_desktop', 'Notifications and sounds')">
83+
<NcFormBox>
84+
<UiFormBoxSelectNative v-model="playSoundChat" :label="t('talk_desktop', 'Play chat notification sound')" :options="notificationLevelOptions" />
85+
<UiFormBoxSelectNative v-model="playSoundCall" :label="t('talk_desktop', 'Play call notification sound')" :options="notificationLevelOptions" />
86+
<UiFormBoxSelectNative v-model="enableCallbox" :label="t('talk_desktop', 'Show call notification popup')" :options="notificationLevelOptions" />
87+
</NcFormBox>
88+
89+
<NcFormBox>
90+
<NcFormBoxSwitch v-model="secondarySpeaker" :label="t('talk_desktop', 'Also repeat call notification on a secondary speaker')" />
91+
</NcFormBox>
92+
93+
<UiFormBoxAudioOutput v-if="secondarySpeaker" v-model="secondarySpeakerDevice" :label="t('talk_desktop', 'Secondary speaker')" />
94+
</NcFormGroup>
23995
</div>
24096
</template>
24197

24298
<style scoped>
243-
.relaunch-require-note-card {
244-
margin-block-start: 0 !important;
245-
}
246-
247-
.relaunch-require-note-card > :deep(div) {
248-
flex: 1; /* TODO: fix in upstream */
249-
}
250-
251-
.relaunch-require-note-card__content {
99+
.desktop-settings-section {
252100
display: flex;
253-
gap: var(--default-grid-baseline);
254-
align-items: flex-start;
255-
}
256-
257-
.relaunch-require-note-card__button {
258-
margin-inline-start: auto;
259-
flex: 0 0 auto;
260-
}
261-
262-
.zoom-input {
263-
width: 50px !important;
101+
flex-direction: column;
102+
justify-content: stretch;
103+
gap: calc(6 * var(--default-grid-baseline));
264104
}
265105
</style>
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<!--
2+
- SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3+
- SPDX-License-Identifier: AGPL-3.0-or-later
4+
-->
5+
6+
<script setup lang="ts">
7+
import { t } from '@nextcloud/l10n'
8+
import NcButton from '@nextcloud/vue/components/NcButton'
9+
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
10+
11+
/**
12+
* Restart the app
13+
*/
14+
function relaunch() {
15+
window.TALK_DESKTOP.relaunchWindow()
16+
}
17+
</script>
18+
19+
<template>
20+
<NcNoteCard type="info" class="relaunch-require-note-card">
21+
<div class="relaunch-require-note-card__content">
22+
<span>{{ t('talk_desktop', 'Some changes require a relaunch to take effect') }}</span>
23+
<NcButton
24+
variant="primary"
25+
size="small"
26+
class="relaunch-require-note-card__button"
27+
@click="relaunch">
28+
{{ t('talk_desktop', 'Restart') }}
29+
</NcButton>
30+
</div>
31+
</NcNoteCard>
32+
</template>
33+
34+
<style scoped>
35+
.relaunch-require-note-card {
36+
margin-block-start: 0 !important;
37+
}
38+
39+
.relaunch-require-note-card > :deep(div) {
40+
flex: 1; /* TODO: fix in upstream */
41+
}
42+
43+
.relaunch-require-note-card__content {
44+
display: flex;
45+
gap: var(--default-grid-baseline);
46+
align-items: flex-start;
47+
}
48+
49+
.relaunch-require-note-card__button {
50+
margin-inline-start: auto;
51+
flex: 0 0 auto;
52+
}
53+
</style>

0 commit comments

Comments
 (0)