Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] chore(config): re-design #1155

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 43 additions & 18 deletions src/app/AppConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,27 +78,19 @@ export type AppConfig = {

/**
* Whether to play a sound when a chat notification is received.
* Same as notifications.sound_notification initial state.
* - 'always': always play sound
* - 'respect-dnd': play sound only if user status is not Do-Not-Disturb [default]
* - 'never': disable notification sound
* Default: true, but ignored on Do-Not-Disturb.
*/
playSoundChat: 'always' | 'respect-dnd' | 'never'
playSoundChat: boolean
/**
* Whether to play a sound when a call notification is received.
* Same as notifications.sound_talk initial state.
* - 'always': always play sound
* - 'respect-dnd': play sound only if user status is not Do-Not-Disturb [default]
* - 'never': disable notification sound
* Default: true, but ignored on Do-Not-Disturb.
*/
playSoundCall: 'always' | 'respect-dnd' | 'never'
playSoundCall: boolean
/**
* Whether to show a popup when a call notification is received.
* - 'always': always show the popup
* - 'respect-dnd': show the popup only if user status is not Do-Not-Disturb [default]
* - 'never': disable the call popup
* Default: true, but ignored on Do-Not-Disturb.
*/
enableCallbox: 'always' | 'respect-dnd' | 'never'
enableCallbox: boolean
/**
* Whether to play ring sound on secondary speaker when a call notification is received.
*/
Expand All @@ -120,9 +112,9 @@ const defaultAppConfig: AppConfig = {
systemTitleBar: isLinux,
monochromeTrayIcon: isMac,
zoomFactor: 1,
playSoundChat: 'respect-dnd',
playSoundCall: 'respect-dnd',
enableCallbox: 'respect-dnd',
playSoundChat: true,
playSoundCall: true,
enableCallbox: true,
secondarySpeaker: false,
secondarySpeakerDevice: null,
}
Expand Down Expand Up @@ -167,11 +159,44 @@ async function writeAppConfigFile(config: Partial<AppConfig>) {
}
}

/**
* Validate the application config by removing unknown properties and properties with invalid values
* @param config - Config to validate
*/
function validateAppConfig(config: unknown): Partial<AppConfig> {
if (typeof config !== 'object' || config === null) {
return {}
}
// Remove unknown keys
for (const key in config) {
if (!(key in defaultAppConfig)) {
delete config[key as keyof typeof config]
}
}
// Validate values
const booleanProperties: AppConfigKey[] = ['launchAtStartup', 'systemTitleBar', 'monochromeTrayIcon', 'playSoundChat', 'playSoundCall', 'enableCallbox', 'secondarySpeaker']
for (const key of booleanProperties) {
if (typeof config[key as keyof typeof config] !== 'boolean') {
delete config[key as keyof typeof config]
}
}
if ('zoomFactor' in config && typeof config.zoomFactor !== 'number') {
delete config.zoomFactor
}
if ('theme' in config && !['default', 'dark', 'light'].includes(config.theme as AppConfig['theme'])) {
delete config.theme
}
if ('secondarySpeakerDevice' in config && typeof config.secondarySpeakerDevice !== 'string' && config.secondarySpeakerDevice !== null) {
delete config.secondarySpeakerDevice
}
return config
}

/**
* Load the application config into the application memory
*/
export async function loadAppConfig() {
const config = await readAppConfigFile()
const config = validateAppConfig(await readAppConfigFile())
Object.assign(appConfig, config)
initialized = true
}
Expand Down
164 changes: 94 additions & 70 deletions src/talk/renderer/Settings/DesktopSettingsSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,21 @@
import { t } from '@nextcloud/l10n'
import NcButton from '@nextcloud/vue/components/NcButton'
import NcCheckboxRadioSwitch from '@nextcloud/vue/components/NcCheckboxRadioSwitch'
import NcSelect from '@nextcloud/vue/components/NcSelect'
import IconMagnify from 'vue-material-design-icons/Magnify.vue'
import IconMinus from 'vue-material-design-icons/Minus.vue'
import IconPlus from 'vue-material-design-icons/Plus.vue'
import IconReload from 'vue-material-design-icons/Reload.vue'
import NcNoteCard from '@nextcloud/vue/components/NcNoteCard'
import NcTextField from '@nextcloud/vue/components/NcTextField'
import IconApplicationOutline from 'vue-material-design-icons/ApplicationOutline.vue'
import IconCardAccountPhoneOutline from 'vue-material-design-icons/CardAccountPhoneOutline.vue'
import IconPhoneRingOutline from 'vue-material-design-icons/PhoneRingOutline.vue'
import IconBellRingOutline from 'vue-material-design-icons/BellRingOutline.vue'
import IconVolumeHigh from 'vue-material-design-icons/VolumeHigh.vue'
import IconRestore from 'vue-material-design-icons/Restore.vue'
import IconRocketLaunch from 'vue-material-design-icons/RocketLaunch.vue'
import IconThemeLightDark from 'vue-material-design-icons/ThemeLightDark.vue'
import IconSpeakerMultiple from 'vue-material-design-icons/SpeakerMultiple.vue'
import SettingsSubsection from './components/SettingsSubsection.vue'
import SettingsSelect from './components/SettingsSelect.vue'
import SettingsFormGroup from './components/SettingsFormGroup.vue'
Expand All @@ -44,7 +48,7 @@
const themeOption = useNcSelectModel(theme, themeOptions)

const systemTitleBar = useAppConfigValue('systemTitleBar')
const monochromeTrayIcon = useAppConfigValue('monochromeTrayIcon')

Check failure on line 51 in src/talk/renderer/Settings/DesktopSettingsSection.vue

View workflow job for this annotation

GitHub Actions / NPM lint

'monochromeTrayIcon' is assigned a value but never used

const zoomFactorConfig = useAppConfigValue('zoomFactor')
const zoomFactor = computed({
Expand All @@ -66,21 +70,9 @@
resetKey: `<kbd>${ctrl} + 0</kbd>`,
}, undefined, { escape: false })

const generalNotificationOptions = [
{ label: t('talk_desktop', 'Always'), value: 'always' } as const,
{ label: t('talk_desktop', 'When not in "Do not disturb"'), value: 'respect-dnd' } as const,
{ label: t('talk_desktop', 'Never'), value: 'never' } as const,
]

const playSoundChat = useAppConfigValue('playSoundChat')
const playSoundChatOption = useNcSelectModel(playSoundChat, generalNotificationOptions)

const playSoundCall = useAppConfigValue('playSoundCall')
const playSoundCallOption = useNcSelectModel(playSoundCall, generalNotificationOptions)

const enableCallbox = useAppConfigValue('enableCallbox')
const enableCallboxOption = useNcSelectModel(enableCallbox, generalNotificationOptions)

const secondarySpeaker = useAppConfigValue('secondarySpeaker')

const EMPTY_DEVICE_OPTION = { value: null, label: t('talk_desktop', 'None') }
Expand Down Expand Up @@ -135,9 +127,14 @@
</NcNoteCard>

<SettingsSubsection v-if="!isLinux" :name="t('talk_desktop', 'General')">
<NcCheckboxRadioSwitch v-model="launchAtStartup" type="switch">
{{ t('talk_desktop', 'Launch at startup') }}
</NcCheckboxRadioSwitch>
<SettingsFormGroup :label="t('talk_desktop', 'Launch at startup')">
<template #icon="{ size }">
<IconRocketLaunch :size="size" />
</template>
<template #default="{ inputId }">
<NcCheckboxRadioSwitch :id="inputId" v-model="launchAtStartup" type="switch" />
</template>
</SettingsFormGroup>
</SettingsSubsection>

<SettingsSubsection :name="t('talk_desktop', 'Appearance')">
Expand All @@ -147,13 +144,14 @@
</template>
</SettingsSelect>

<NcCheckboxRadioSwitch v-model="monochromeTrayIcon" type="switch">
{{ t('talk_desktop', 'Use monochrome tray icon') }}
</NcCheckboxRadioSwitch>

<NcCheckboxRadioSwitch v-model="systemTitleBar" type="switch">
{{ t('talk_desktop', 'Use system title bar') }}
</NcCheckboxRadioSwitch>
<SettingsFormGroup :label="t('talk_desktop', 'Use system title bar')">
<template #icon="{ size }">
<IconApplicationOutline :size="size" />
</template>
<template #default="{ inputId }">
<NcCheckboxRadioSwitch :id="inputId" v-model="systemTitleBar" type="switch" />
</template>
</SettingsFormGroup>

<SettingsFormGroup :label="t('talk_desktop', 'Zoom')">
<template #icon="{ size }">
Expand All @@ -164,73 +162,89 @@
<span v-html="zoomHint" />
</template>
<template #default="{ inputId, descriptionId }">
<NcButton :aria-label="t('talk_desktop', 'Zoom out')" type="tertiary" @click="zoomFactor /= ZOOM_STEP">
<template #icon>
<IconMinus :size="20" />
</template>
</NcButton>
<NcTextField :id="inputId"
class="zoom-input"
:aria-describedby="descriptionId"
label-outside
inputmode="number"
:model-value="zoomFactorPercentage"
@change="zoomFactorPercentage = $event.target.value"
@blur="$event.target.value = zoomFactorPercentage" />
<NcButton :aria-label="t('talk_desktop', 'Zoom in')" type="tertiary" @click="zoomFactor *= ZOOM_STEP">
<template #icon>
<IconPlus :size="20" />
</template>
</NcButton>
<NcButton @click="zoomFactor = 1">
<template #icon>
<IconRestore :size="20" />
</template>
{{ t('talk_desktop', 'Reset') }}
</NcButton>
<div style="display: flex; gap: var(--default-grid-baseline);">
<NcButton :aria-label="t('talk_desktop', 'Zoom out')" type="tertiary" @click="zoomFactor /= ZOOM_STEP">
<template #icon>
<IconMinus :size="20" />
</template>
</NcButton>
<NcTextField :id="inputId"
class="zoom-input"
:aria-describedby="descriptionId"
label-outside
inputmode="number"
:model-value="zoomFactorPercentage"
@change="zoomFactorPercentage = $event.target.value"
@blur="$event.target.value = zoomFactorPercentage" />
<NcButton :aria-label="t('talk_desktop', 'Zoom in')" type="tertiary" @click="zoomFactor *= ZOOM_STEP">
<template #icon>
<IconPlus :size="20" />
</template>
</NcButton>
<NcButton @click="zoomFactor = 1">
<template #icon>
<IconRestore :size="20" />
</template>
{{ t('talk_desktop', 'Reset') }}
</NcButton>
</div>
</template>
</SettingsFormGroup>
</SettingsSubsection>

<SettingsSubsection :name="t('talk_desktop', 'Notifications and sounds')">
<SettingsSelect v-model="playSoundChatOption" :options="generalNotificationOptions" :label="t('talk_desktop', 'Play chat notification sound')">
<SettingsFormGroup :label="t('talk_desktop', 'Play chat notification sound')">
<template #icon="{ size }">
<IconBellRingOutline :size="size" />
</template>
</SettingsSelect>
<template #default="{ inputId }">
<NcCheckboxRadioSwitch :id="inputId" v-model="playSoundCall" type="switch" />
</template>
</SettingsFormGroup>

<SettingsSelect v-model="playSoundCallOption" :options="generalNotificationOptions" :label="t('talk_desktop', 'Play call notification sound')">
<SettingsFormGroup :label="t('talk_desktop', 'Play call notification sound')">
<template #icon="{ size }">
<IconPhoneRingOutline :size="size" />
</template>
</SettingsSelect>
<template #default="{ inputId }">
<NcCheckboxRadioSwitch :id="inputId" v-model="playSoundChat" type="switch" />
</template>
</SettingsFormGroup>

<SettingsSelect v-model="enableCallboxOption" :options="generalNotificationOptions" :label="t('talk_desktop', 'Show call notification popup')">
<SettingsFormGroup :label="t('talk_desktop', 'Show call notification popup')">
<template #icon="{ size }">
<IconCardAccountPhoneOutline :size="size" />
</template>
</SettingsSelect>

<NcCheckboxRadioSwitch v-model="secondarySpeaker" type="switch">
{{ t('talk_desktop', 'Also repeat call notification on a secondary speaker') }}
</NcCheckboxRadioSwitch>
<template #default="{ inputId }">
<NcCheckboxRadioSwitch :id="inputId" v-model="enableCallbox" type="switch" />
</template>
</SettingsFormGroup>

<SettingsSelect v-if="secondarySpeaker"
v-model="secondarySpeakerDeviceOption"
:options="secondarySpeakerOptions"
:disabled="secondarySpeakerOptions.length === 1"
:label="t('talk_desktop', 'Secondary speaker')">
<SettingsFormGroup :label="t('talk_desktop', 'Also repeat call notification on a secondary speaker')">
<template #icon="{ size }">
<IconVolumeHigh :size="size" />
<IconSpeakerMultiple :size="size" />
</template>
<template #action>
<NcButton type="tertiary" @click="initializeDevices">
<template #icon>
<IconRestore :size="20" />
</template>
</NcButton>
<template #default="{ inputId }">
<NcCheckboxRadioSwitch :id="inputId" v-model="secondarySpeaker" type="switch" />
</template>
</SettingsSelect>
<template v-if="secondarySpeaker" #subform>
<div style="display: flex; gap: var(--default-grid-baseline);">
<NcSelect v-model="secondarySpeakerDeviceOption"
:aria-label-combobox="t('talk_desktop', 'Secondary speaker device')"
style="flex: 1"
:options="secondarySpeakerOptions"
:disabled="secondarySpeakerOptions.length === 1"
:clearable="false"
:searchable="false"
label-outside />
<NcButton type="tertiary" @click="initializeDevices">
<template #icon>
<IconReload :size="20" />
</template>
</NcButton>
</div>
</template>
</SettingsFormGroup>
</SettingsSubsection>
</div>
</template>
Expand Down Expand Up @@ -258,4 +272,14 @@
.zoom-input {
width: 50px !important;
}

:deep(.checkbox-content) {
padding: var(--default-grid-baseline);
}

:deep(.checkbox-content__icon) {
height: 28px;
display: flex;
align-items: center;
}
</style>
8 changes: 2 additions & 6 deletions src/talk/renderer/Settings/appConfig.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,8 @@ export const useAppConfigStore = defineStore('appConfig', () => {

const userStatusStore = useUserStatusStore()
watchEffect(() => {
const playSoundChat = appConfig.value.playSoundChat === 'respect-dnd'
? userStatusStore.userStatus?.status !== 'dnd'
: appConfig.value.playSoundChat === 'always'
const playSoundCall = appConfig.value.playSoundCall === 'respect-dnd'
? userStatusStore.userStatus?.status !== 'dnd'
: appConfig.value.playSoundCall === 'always'
const playSoundChat = appConfig.value.playSoundChat && userStatusStore.userStatus?.status !== 'dnd'
const playSoundCall = appConfig.value.playSoundCall && userStatusStore.userStatus?.status !== 'dnd'
setInitialState('notifications', 'sound_notification', playSoundChat)
setInitialState('notifications', 'sound_talk', playSoundCall)
})
Expand Down
Loading
Loading