Compare commits
No commits in common. "becd5d5e82de20e5b0334b9b761d84bd521bdb41" and "8c4911809ce5f346037343f1a7f1029a7b912569" have entirely different histories.
becd5d5e82
...
8c4911809c
@ -1,8 +1,10 @@
|
|||||||
import { useContext, useEffect, useMemo, useState } from 'react'
|
import { useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import { Flex, Space } from 'antd'
|
import { Descriptions, Flex, Select, Space, Spin, Typography } from 'antd'
|
||||||
|
import { LoadingOutlined, SettingOutlined } from '@ant-design/icons'
|
||||||
import { useThemeContext } from '../context/ThemeContext'
|
import { useThemeContext } from '../context/ThemeContext'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
import { ApiServerContext } from '../context/ApiServerContext'
|
||||||
import { ElectronContext } from '../context/ElectronContext'
|
import { ElectronContext } from '../context/ElectronContext'
|
||||||
|
import { AuthContext } from '../context/AuthContext'
|
||||||
import {
|
import {
|
||||||
normalizeAppUpdateEngine,
|
normalizeAppUpdateEngine,
|
||||||
useAppUpdateContext
|
useAppUpdateContext
|
||||||
@ -10,29 +12,20 @@ import {
|
|||||||
import { useMessageContext } from '../context/MessageContext'
|
import { useMessageContext } from '../context/MessageContext'
|
||||||
import useCollapseState from '../hooks/useCollapseState'
|
import useCollapseState from '../hooks/useCollapseState'
|
||||||
import InfoCollapse from '../common/InfoCollapse'
|
import InfoCollapse from '../common/InfoCollapse'
|
||||||
import ObjectInfo from '../common/ObjectInfo'
|
|
||||||
import ViewButton from '../common/ViewButton'
|
import ViewButton from '../common/ViewButton'
|
||||||
import EditButtons from '../common/EditButtons'
|
import EditButtons from '../common/EditButtons'
|
||||||
import ScrollBox from '../common/ScrollBox'
|
|
||||||
import settingsSchema, {
|
|
||||||
APP_SETTING_NAMES,
|
|
||||||
USER_SETTING_NAMES,
|
|
||||||
areSettingsValid,
|
|
||||||
getEffectiveAppearance,
|
|
||||||
getSettingsDefaults,
|
|
||||||
getVisibleSettingsSections,
|
|
||||||
pickSettings
|
|
||||||
} from '../../../database/Settings'
|
|
||||||
|
|
||||||
|
const { Text } = Typography
|
||||||
|
const { Option } = Select
|
||||||
const DEFAULT_UPDATE_BRANCH = 'main'
|
const DEFAULT_UPDATE_BRANCH = 'main'
|
||||||
const DEFAULT_UPDATE_ENGINE = 'native'
|
const DEFAULT_UPDATE_ENGINE = 'native'
|
||||||
const LEGACY_ELECTRON_USER_KEYS = [
|
|
||||||
'theme',
|
const engineLabel = (engine) => {
|
||||||
'density',
|
const normalized = normalizeAppUpdateEngine(engine)
|
||||||
'showNavigationLabels',
|
if (normalized === 'chromium') return 'Chromium'
|
||||||
'dateTimeFormat',
|
if (normalized === 'native') return 'Native'
|
||||||
'timezone'
|
return 'Not configured'
|
||||||
]
|
}
|
||||||
|
|
||||||
const Settings = () => {
|
const Settings = () => {
|
||||||
const {
|
const {
|
||||||
@ -44,183 +37,145 @@ const Settings = () => {
|
|||||||
showNavigationLabels,
|
showNavigationLabels,
|
||||||
setShowNavigationLabels
|
setShowNavigationLabels
|
||||||
} = useThemeContext()
|
} = useThemeContext()
|
||||||
const {
|
const { fetchAppUpdateBranches } = useContext(ApiServerContext)
|
||||||
connected,
|
|
||||||
fetchAppUpdateBranches,
|
|
||||||
updateUserSettings,
|
|
||||||
userSettings,
|
|
||||||
userSettingsLoaded
|
|
||||||
} = useContext(ApiServerContext)
|
|
||||||
const { isElectron, getAppSettings, setAppSettings, getAppEngine } =
|
const { isElectron, getAppSettings, setAppSettings, getAppEngine } =
|
||||||
useContext(ElectronContext)
|
useContext(ElectronContext)
|
||||||
const { recheckForUpdates } = useAppUpdateContext()
|
const { recheckForUpdates } = useAppUpdateContext()
|
||||||
|
const { userProfile, setUserProfile } = useContext(AuthContext)
|
||||||
const { showSuccess, showError } = useMessageContext()
|
const { showSuccess, showError } = useMessageContext()
|
||||||
const visibleSections = useMemo(
|
const [collapseState, updateCollapseState] = useCollapseState('Settings', {
|
||||||
() => getVisibleSettingsSections({ isElectron }),
|
appearance: true,
|
||||||
[isElectron]
|
appUpdates: true
|
||||||
)
|
})
|
||||||
const [collapseState, updateCollapseState] = useCollapseState(
|
|
||||||
'Settings',
|
|
||||||
Object.fromEntries(
|
|
||||||
settingsSchema.sections.map((section) => [section.key, true])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const [isEditing, setIsEditing] = useState(false)
|
const [isEditing, setIsEditing] = useState(false)
|
||||||
const [settingsLoading, setSettingsLoading] = useState(true)
|
const [settingsLoading, setSettingsLoading] = useState(true)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [savedSettings, setSavedSettings] = useState({})
|
const [appSettings, setAppSettingsState] = useState({})
|
||||||
const [draftSettings, setDraftSettings] = useState({})
|
const [draftSettings, setDraftSettings] = useState({})
|
||||||
const [electronSettings, setElectronSettings] = useState({})
|
|
||||||
const [branches, setBranches] = useState([])
|
const [branches, setBranches] = useState([])
|
||||||
|
const [branchLoading, setBranchLoading] = useState(false)
|
||||||
const currentThemeValue = isSystem ? 'system' : isDarkMode ? 'dark' : 'light'
|
const [runningEngine, setRunningEngine] = useState(DEFAULT_UPDATE_ENGINE)
|
||||||
const currentDensityValue = isCompact ? 'compact' : 'comfortable'
|
|
||||||
const userAppearance = userSettings?.appearance || {}
|
|
||||||
|
|
||||||
const resolvedSettings = useMemo(() => {
|
|
||||||
const defaults = getSettingsDefaults({ isElectron })
|
|
||||||
const stored = isEditing ? draftSettings : savedSettings
|
|
||||||
const defaultBranch = branches.includes(DEFAULT_UPDATE_BRANCH)
|
|
||||||
? DEFAULT_UPDATE_BRANCH
|
|
||||||
: branches[0]
|
|
||||||
|
|
||||||
return {
|
|
||||||
...defaults,
|
|
||||||
theme: currentThemeValue,
|
|
||||||
density: currentDensityValue,
|
|
||||||
showNavigationLabels: showNavigationLabels ?? false,
|
|
||||||
...(isElectron
|
|
||||||
? {
|
|
||||||
appTheme: stored.appTheme || currentThemeValue,
|
|
||||||
appShowNavigationLabels:
|
|
||||||
stored.appShowNavigationLabels ?? showNavigationLabels ?? false,
|
|
||||||
appUpdateBranch: defaultBranch,
|
|
||||||
appUpdateEngine:
|
|
||||||
normalizeAppUpdateEngine(stored.appUpdateEngine) ||
|
|
||||||
DEFAULT_UPDATE_ENGINE
|
|
||||||
}
|
|
||||||
: {}),
|
|
||||||
...stored
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
branches,
|
|
||||||
currentDensityValue,
|
|
||||||
currentThemeValue,
|
|
||||||
draftSettings,
|
|
||||||
isEditing,
|
|
||||||
isElectron,
|
|
||||||
savedSettings,
|
|
||||||
showNavigationLabels
|
|
||||||
])
|
|
||||||
|
|
||||||
const applyAppearance = (nextSettings, nextElectronSettings = electronSettings) => {
|
|
||||||
const effective = getEffectiveAppearance({
|
|
||||||
isElectron,
|
|
||||||
userAppearance: nextSettings,
|
|
||||||
electronSettings: {
|
|
||||||
...nextElectronSettings,
|
|
||||||
appTheme: nextSettings.appTheme,
|
|
||||||
appShowNavigationLabels: nextSettings.appShowNavigationLabels,
|
|
||||||
overrideTheme: nextSettings.theme === 'app'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
if (effective.theme) setThemeMode(effective.theme)
|
|
||||||
if (effective.density) setDensityMode(effective.density)
|
|
||||||
if (effective.showNavigationLabels !== undefined) {
|
|
||||||
setShowNavigationLabels(effective.showNavigationLabels)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userSettingsLoaded || isEditing) return
|
|
||||||
|
|
||||||
const loadSettings = async () => {
|
const loadSettings = async () => {
|
||||||
setSettingsLoading(true)
|
setSettingsLoading(true)
|
||||||
const [storedElectronSettings, detectedEngine] = await Promise.all([
|
const [storedSettings, detectedEngine] = await Promise.all([
|
||||||
isElectron ? getAppSettings() : Promise.resolve({}),
|
isElectron ? getAppSettings() : Promise.resolve(userProfile?.settings || {}),
|
||||||
isElectron ? getAppEngine() : Promise.resolve(DEFAULT_UPDATE_ENGINE)
|
isElectron ? getAppEngine() : Promise.resolve(DEFAULT_UPDATE_ENGINE)
|
||||||
])
|
])
|
||||||
const nextEngine =
|
const nextEngine =
|
||||||
normalizeAppUpdateEngine(detectedEngine) || DEFAULT_UPDATE_ENGINE
|
normalizeAppUpdateEngine(detectedEngine) || DEFAULT_UPDATE_ENGINE
|
||||||
const nextElectronSettings = { ...(storedElectronSettings || {}) }
|
setRunningEngine(nextEngine)
|
||||||
if (
|
|
||||||
isElectron &&
|
const nextSettings = { ...(storedSettings || {}) }
|
||||||
!normalizeAppUpdateEngine(nextElectronSettings.appUpdateEngine)
|
if (isElectron && !normalizeAppUpdateEngine(nextSettings.appUpdateEngine)) {
|
||||||
) {
|
nextSettings.appUpdateEngine = nextEngine
|
||||||
nextElectronSettings.appUpdateEngine = nextEngine
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nextSettings = {
|
setAppSettingsState(nextSettings)
|
||||||
...getSettingsDefaults({ isElectron }),
|
|
||||||
...pickSettings(userAppearance, USER_SETTING_NAMES),
|
|
||||||
...(isElectron
|
|
||||||
? {
|
|
||||||
...pickSettings(nextElectronSettings, APP_SETTING_NAMES),
|
|
||||||
appTheme:
|
|
||||||
nextElectronSettings.appTheme || nextElectronSettings.theme,
|
|
||||||
appShowNavigationLabels:
|
|
||||||
nextElectronSettings.appShowNavigationLabels ??
|
|
||||||
nextElectronSettings.showNavigationLabels,
|
|
||||||
theme: nextElectronSettings.overrideTheme
|
|
||||||
? 'app'
|
|
||||||
: userAppearance.theme
|
|
||||||
}
|
|
||||||
: {})
|
|
||||||
}
|
|
||||||
|
|
||||||
setElectronSettings(nextElectronSettings)
|
|
||||||
setSavedSettings(nextSettings)
|
|
||||||
setDraftSettings(nextSettings)
|
setDraftSettings(nextSettings)
|
||||||
applyAppearance(nextSettings, nextElectronSettings)
|
|
||||||
setSettingsLoading(false)
|
setSettingsLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadSettings()
|
loadSettings()
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps -- reload when stored appearance/app settings change
|
}, [getAppEngine, getAppSettings, isElectron, userProfile?.settings])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (settingsLoading || isEditing) return
|
||||||
|
if (appSettings.theme) setThemeMode(appSettings.theme)
|
||||||
|
if (appSettings.density) setDensityMode(appSettings.density)
|
||||||
|
if (appSettings.showNavigationLabels !== undefined) {
|
||||||
|
setShowNavigationLabels(appSettings.showNavigationLabels)
|
||||||
|
}
|
||||||
}, [
|
}, [
|
||||||
getAppEngine,
|
appSettings.density,
|
||||||
getAppSettings,
|
appSettings.showNavigationLabels,
|
||||||
|
appSettings.theme,
|
||||||
isEditing,
|
isEditing,
|
||||||
isElectron,
|
setDensityMode,
|
||||||
userAppearance.dateTimeFormat,
|
setShowNavigationLabels,
|
||||||
userAppearance.density,
|
setThemeMode,
|
||||||
userAppearance.showNavigationLabels,
|
settingsLoading
|
||||||
userAppearance.theme,
|
|
||||||
userAppearance.timezone,
|
|
||||||
userSettingsLoaded
|
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isElectron) {
|
if (!isElectron) {
|
||||||
setBranches([])
|
setBranches([])
|
||||||
|
setBranchLoading(false)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const loadBranches = async () => {
|
const loadBranches = async () => {
|
||||||
|
setBranchLoading(true)
|
||||||
const availableBranches = await fetchAppUpdateBranches()
|
const availableBranches = await fetchAppUpdateBranches()
|
||||||
setBranches(availableBranches)
|
setBranches(availableBranches)
|
||||||
|
|
||||||
setDraftSettings((previous) => {
|
setDraftSettings((previous) => {
|
||||||
if (previous.appUpdateBranch) return previous
|
if (previous.appUpdateBranch) return previous
|
||||||
|
|
||||||
const defaultBranch = availableBranches.includes(DEFAULT_UPDATE_BRANCH)
|
const defaultBranch = availableBranches.includes(DEFAULT_UPDATE_BRANCH)
|
||||||
? DEFAULT_UPDATE_BRANCH
|
? DEFAULT_UPDATE_BRANCH
|
||||||
: availableBranches[0]
|
: availableBranches[0]
|
||||||
|
|
||||||
return defaultBranch
|
return defaultBranch
|
||||||
? { ...previous, appUpdateBranch: defaultBranch }
|
? { ...previous, appUpdateBranch: defaultBranch }
|
||||||
: previous
|
: previous
|
||||||
})
|
})
|
||||||
|
|
||||||
|
setBranchLoading(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
loadBranches()
|
loadBranches()
|
||||||
}, [fetchAppUpdateBranches, isElectron])
|
}, [fetchAppUpdateBranches, isElectron])
|
||||||
|
|
||||||
|
const branchOptions = useMemo(
|
||||||
|
() =>
|
||||||
|
branches.map((branch) => (
|
||||||
|
<Option key={branch} value={branch}>
|
||||||
|
{branch}
|
||||||
|
</Option>
|
||||||
|
)),
|
||||||
|
[branches]
|
||||||
|
)
|
||||||
|
const viewItems = [
|
||||||
|
{ key: 'appearance', label: 'Appearance Settings' },
|
||||||
|
...(isElectron ? [{ key: 'appUpdates', label: 'App Update Settings' }] : [])
|
||||||
|
]
|
||||||
|
|
||||||
|
const getCurrentThemeValue = () => {
|
||||||
|
if (isSystem) return 'system'
|
||||||
|
return isDarkMode ? 'dark' : 'light'
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentThemeValue = getCurrentThemeValue()
|
||||||
|
const currentDensityValue = isCompact ? 'compact' : 'comfortable'
|
||||||
|
const currentShowNavigationLabels =
|
||||||
|
appSettings.showNavigationLabels ?? showNavigationLabels ?? false
|
||||||
|
const currentBranch =
|
||||||
|
appSettings.appUpdateBranch ||
|
||||||
|
(branches.includes(DEFAULT_UPDATE_BRANCH) ? DEFAULT_UPDATE_BRANCH : null) ||
|
||||||
|
branches[0] ||
|
||||||
|
'Not configured'
|
||||||
|
const currentEngine =
|
||||||
|
normalizeAppUpdateEngine(appSettings.appUpdateEngine) ||
|
||||||
|
normalizeAppUpdateEngine(runningEngine) ||
|
||||||
|
DEFAULT_UPDATE_ENGINE
|
||||||
|
|
||||||
const startEditing = () => {
|
const startEditing = () => {
|
||||||
setDraftSettings(resolvedSettings)
|
setDraftSettings({
|
||||||
|
...appSettings,
|
||||||
|
appUpdateBranch:
|
||||||
|
currentBranch === 'Not configured' ? undefined : currentBranch,
|
||||||
|
appUpdateEngine: currentEngine,
|
||||||
|
theme: currentThemeValue,
|
||||||
|
density: currentDensityValue,
|
||||||
|
showNavigationLabels: currentShowNavigationLabels
|
||||||
|
})
|
||||||
setIsEditing(true)
|
setIsEditing(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const cancelEditing = () => {
|
const cancelEditing = () => {
|
||||||
setDraftSettings(savedSettings)
|
setDraftSettings(appSettings)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,67 +183,55 @@ const Settings = () => {
|
|||||||
setSaving(true)
|
setSaving(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const useAppTheme = isElectron && draftSettings.theme === 'app'
|
|
||||||
const nextEngine =
|
const nextEngine =
|
||||||
normalizeAppUpdateEngine(draftSettings.appUpdateEngine) ||
|
normalizeAppUpdateEngine(draftSettings.appUpdateEngine) ||
|
||||||
DEFAULT_UPDATE_ENGINE
|
currentEngine
|
||||||
const nextSettings = {
|
const nextSettings = {
|
||||||
...getSettingsDefaults({ isElectron }),
|
...appSettings,
|
||||||
...savedSettings,
|
theme: draftSettings.theme,
|
||||||
...draftSettings,
|
density: draftSettings.density,
|
||||||
showNavigationLabels: Boolean(draftSettings.showNavigationLabels),
|
showNavigationLabels: Boolean(draftSettings.showNavigationLabels),
|
||||||
...(isElectron
|
...(isElectron
|
||||||
? {
|
? {
|
||||||
appShowNavigationLabels: Boolean(
|
|
||||||
draftSettings.appShowNavigationLabels
|
|
||||||
),
|
|
||||||
appUpdateBranch: draftSettings.appUpdateBranch,
|
appUpdateBranch: draftSettings.appUpdateBranch,
|
||||||
appUpdateEngine: nextEngine
|
appUpdateEngine: nextEngine
|
||||||
}
|
}
|
||||||
: {})
|
: {})
|
||||||
}
|
}
|
||||||
|
const saved = isElectron
|
||||||
|
? await setAppSettings(nextSettings)
|
||||||
|
: Boolean(userProfile)
|
||||||
|
|
||||||
const userUpdates = USER_SETTING_NAMES.filter(
|
|
||||||
(name) => !(name === 'theme' && useAppTheme)
|
|
||||||
).map((name) => updateUserSettings('appearance', name, nextSettings[name]))
|
|
||||||
|
|
||||||
const userResults = await Promise.all(userUpdates)
|
|
||||||
if (connected && userResults.some((result) => result == null)) {
|
|
||||||
showError('Unable to save settings.')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let nextElectronSettings = electronSettings
|
|
||||||
if (isElectron) {
|
|
||||||
nextElectronSettings = { ...electronSettings }
|
|
||||||
for (const key of LEGACY_ELECTRON_USER_KEYS) {
|
|
||||||
delete nextElectronSettings[key]
|
|
||||||
}
|
|
||||||
nextElectronSettings = {
|
|
||||||
...nextElectronSettings,
|
|
||||||
...pickSettings(nextSettings, APP_SETTING_NAMES),
|
|
||||||
overrideTheme: useAppTheme
|
|
||||||
}
|
|
||||||
|
|
||||||
const saved = await setAppSettings(nextElectronSettings)
|
|
||||||
if (!saved) {
|
if (!saved) {
|
||||||
showError('Unable to save settings.')
|
showError('Unable to save settings.')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isElectron) {
|
||||||
|
setUserProfile((previous) => ({
|
||||||
|
...previous,
|
||||||
|
settings: {
|
||||||
|
...(previous?.settings || {}),
|
||||||
|
theme: draftSettings.theme,
|
||||||
|
density: draftSettings.density,
|
||||||
|
showNavigationLabels: Boolean(draftSettings.showNavigationLabels)
|
||||||
|
}
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
applyAppearance(nextSettings, nextElectronSettings)
|
setThemeMode(draftSettings.theme)
|
||||||
setElectronSettings(nextElectronSettings)
|
setDensityMode(draftSettings.density)
|
||||||
setSavedSettings(nextSettings)
|
setShowNavigationLabels(draftSettings.showNavigationLabels)
|
||||||
|
setAppSettingsState(nextSettings)
|
||||||
setDraftSettings(nextSettings)
|
setDraftSettings(nextSettings)
|
||||||
setIsEditing(false)
|
setIsEditing(false)
|
||||||
showSuccess('Settings saved.')
|
showSuccess('Settings saved.')
|
||||||
|
|
||||||
const appUpdateSettingsChanged =
|
const appUpdateSettingsChanged =
|
||||||
isElectron &&
|
isElectron &&
|
||||||
(nextSettings.appUpdateBranch !== savedSettings.appUpdateBranch ||
|
(nextSettings.appUpdateBranch !== appSettings.appUpdateBranch ||
|
||||||
nextSettings.appUpdateEngine !==
|
nextSettings.appUpdateEngine !==
|
||||||
normalizeAppUpdateEngine(savedSettings.appUpdateEngine))
|
normalizeAppUpdateEngine(appSettings.appUpdateEngine))
|
||||||
|
|
||||||
if (appUpdateSettingsChanged) {
|
if (appUpdateSettingsChanged) {
|
||||||
void recheckForUpdates()
|
void recheckForUpdates()
|
||||||
@ -304,10 +247,7 @@ const Settings = () => {
|
|||||||
<Space size='small'>
|
<Space size='small'>
|
||||||
<ViewButton
|
<ViewButton
|
||||||
disabled={settingsLoading}
|
disabled={settingsLoading}
|
||||||
items={visibleSections.map((section) => ({
|
items={viewItems}
|
||||||
key: section.key,
|
|
||||||
label: section.name
|
|
||||||
}))}
|
|
||||||
visibleState={collapseState}
|
visibleState={collapseState}
|
||||||
updateVisibleState={updateCollapseState}
|
updateVisibleState={updateCollapseState}
|
||||||
/>
|
/>
|
||||||
@ -317,51 +257,163 @@ const Settings = () => {
|
|||||||
handleUpdate={handleSave}
|
handleUpdate={handleSave}
|
||||||
cancelEditing={cancelEditing}
|
cancelEditing={cancelEditing}
|
||||||
startEditing={startEditing}
|
startEditing={startEditing}
|
||||||
formValid={areSettingsValid(draftSettings, { isElectron })}
|
formValid={
|
||||||
disabled={settingsLoading || !userSettingsLoaded}
|
Boolean(draftSettings.theme && draftSettings.density) &&
|
||||||
|
(!isElectron ||
|
||||||
|
(Boolean(draftSettings.appUpdateBranch) &&
|
||||||
|
Boolean(normalizeAppUpdateEngine(draftSettings.appUpdateEngine))))
|
||||||
|
}
|
||||||
|
disabled={settingsLoading || (!isElectron && !userProfile)}
|
||||||
loading={saving}
|
loading={saving}
|
||||||
/>
|
/>
|
||||||
</Flex>
|
</Flex>
|
||||||
<ScrollBox>
|
<div style={{ height: '100%', minHeight: 0, overflowY: 'auto' }}>
|
||||||
|
<Spin spinning={settingsLoading} indicator={<LoadingOutlined />}>
|
||||||
<Flex vertical gap='large'>
|
<Flex vertical gap='large'>
|
||||||
{visibleSections.map((section) => {
|
|
||||||
const Icon = section.icon
|
|
||||||
return (
|
|
||||||
<InfoCollapse
|
<InfoCollapse
|
||||||
key={section.key}
|
title='Appearance Settings'
|
||||||
title={section.name}
|
icon={<SettingOutlined />}
|
||||||
icon={<Icon />}
|
active={collapseState.appearance}
|
||||||
active={collapseState[section.key]}
|
|
||||||
onToggle={(expanded) =>
|
onToggle={(expanded) =>
|
||||||
updateCollapseState(section.key, expanded)
|
updateCollapseState('appearance', expanded)
|
||||||
}
|
}
|
||||||
collapseKey={section.key}
|
collapseKey='appearance'
|
||||||
>
|
>
|
||||||
<ObjectInfo
|
<Descriptions
|
||||||
loading={settingsLoading}
|
bordered
|
||||||
isEditing={isEditing}
|
column={{
|
||||||
propertyDefinitions={section.properties}
|
xs: 1,
|
||||||
objectData={resolvedSettings}
|
sm: 1,
|
||||||
parentData={{ branches, isElectron }}
|
md: 1,
|
||||||
onPropertyChange={
|
lg: 2,
|
||||||
isEditing
|
xl: 2,
|
||||||
? (name, value) =>
|
xxl: 2
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Descriptions.Item label='Theme'>
|
||||||
|
{isEditing ? (
|
||||||
|
<Select
|
||||||
|
value={draftSettings.theme}
|
||||||
|
onChange={(value) =>
|
||||||
setDraftSettings((previous) => ({
|
setDraftSettings((previous) => ({
|
||||||
...previous,
|
...previous,
|
||||||
[name]:
|
theme: value
|
||||||
value?.target && typeof value.target === 'object'
|
|
||||||
? value.target.value
|
|
||||||
: value
|
|
||||||
}))
|
}))
|
||||||
: undefined
|
|
||||||
}
|
}
|
||||||
{...(section.column ? { column: section.column } : {})}
|
style={{ width: '100%' }}
|
||||||
/>
|
>
|
||||||
|
<Option value='light'>Light</Option>
|
||||||
|
<Option value='dark'>Dark</Option>
|
||||||
|
<Option value='system'>System</Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Text>{currentThemeValue}</Text>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='UI Density'>
|
||||||
|
{isEditing ? (
|
||||||
|
<Select
|
||||||
|
value={draftSettings.density}
|
||||||
|
onChange={(value) =>
|
||||||
|
setDraftSettings((previous) => ({
|
||||||
|
...previous,
|
||||||
|
density: value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Option value='comfortable'>Comfortable</Option>
|
||||||
|
<Option value='compact'>Compact</Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Text>{isCompact ? 'Compact' : 'Comfortable'}</Text>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Navigation Labels'>
|
||||||
|
{isEditing ? (
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
draftSettings.showNavigationLabels ? 'show' : 'hide'
|
||||||
|
}
|
||||||
|
onChange={(value) =>
|
||||||
|
setDraftSettings((previous) => ({
|
||||||
|
...previous,
|
||||||
|
showNavigationLabels: value === 'show'
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
>
|
||||||
|
<Option value='show'>Show</Option>
|
||||||
|
<Option value='hide'>Hide</Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Text>
|
||||||
|
{currentShowNavigationLabels ? 'Show' : 'Hide'}
|
||||||
|
</Text>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
</InfoCollapse>
|
</InfoCollapse>
|
||||||
)
|
{isElectron && (
|
||||||
})}
|
<InfoCollapse
|
||||||
|
title='App Update Settings'
|
||||||
|
icon={<SettingOutlined />}
|
||||||
|
active={collapseState.appUpdates}
|
||||||
|
onToggle={(expanded) =>
|
||||||
|
updateCollapseState('appUpdates', expanded)
|
||||||
|
}
|
||||||
|
collapseKey='appUpdates'
|
||||||
|
>
|
||||||
|
<Descriptions bordered column={1}>
|
||||||
|
<Descriptions.Item label='Branch'>
|
||||||
|
{isEditing ? (
|
||||||
|
<Select
|
||||||
|
value={draftSettings.appUpdateBranch}
|
||||||
|
onChange={(value) =>
|
||||||
|
setDraftSettings((previous) => ({
|
||||||
|
...previous,
|
||||||
|
appUpdateBranch: value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
loading={branchLoading}
|
||||||
|
placeholder='Select a branch'
|
||||||
|
>
|
||||||
|
{branchOptions}
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Text>{currentBranch}</Text>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
<Descriptions.Item label='Engine'>
|
||||||
|
{isEditing ? (
|
||||||
|
<Select
|
||||||
|
value={
|
||||||
|
normalizeAppUpdateEngine(draftSettings.appUpdateEngine) ||
|
||||||
|
currentEngine
|
||||||
|
}
|
||||||
|
onChange={(value) =>
|
||||||
|
setDraftSettings((previous) => ({
|
||||||
|
...previous,
|
||||||
|
appUpdateEngine: value
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder='Select an engine'
|
||||||
|
>
|
||||||
|
<Option value='native'>Native</Option>
|
||||||
|
<Option value='chromium'>Chromium</Option>
|
||||||
|
</Select>
|
||||||
|
) : (
|
||||||
|
<Text>{engineLabel(currentEngine)}</Text>
|
||||||
|
)}
|
||||||
|
</Descriptions.Item>
|
||||||
|
</Descriptions>
|
||||||
|
</InfoCollapse>
|
||||||
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
</ScrollBox>
|
</Spin>
|
||||||
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,22 +49,15 @@ import {
|
|||||||
|
|
||||||
import { useAppUpdateContext } from '../context/AppUpdateContext'
|
import { useAppUpdateContext } from '../context/AppUpdateContext'
|
||||||
import { useThemeContext } from '../context/ThemeContext'
|
import { useThemeContext } from '../context/ThemeContext'
|
||||||
import { getEffectiveAppearance } from '../../../database/Settings'
|
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
const DashboardNavigation = () => {
|
const DashboardNavigation = () => {
|
||||||
const { userProfile } = useContext(AuthContext)
|
const { userProfile } = useContext(AuthContext)
|
||||||
const { showSpotlight } = useContext(SpotlightContext)
|
const { showSpotlight } = useContext(SpotlightContext)
|
||||||
const { connecting, connected, userSettings, userSettingsLoaded } =
|
const { connecting, connected } = useContext(ApiServerContext)
|
||||||
useContext(ApiServerContext)
|
|
||||||
const { authenticated } = useContext(AuthContext)
|
const { authenticated } = useContext(AuthContext)
|
||||||
const {
|
const { showNavigationLabels, setShowNavigationLabels } = useThemeContext()
|
||||||
showNavigationLabels,
|
|
||||||
setShowNavigationLabels,
|
|
||||||
setThemeMode,
|
|
||||||
setDensityMode
|
|
||||||
} = useThemeContext()
|
|
||||||
const { toggleNotificationCenter, unreadCount } =
|
const { toggleNotificationCenter, unreadCount } =
|
||||||
useContext(NotificationContext)
|
useContext(NotificationContext)
|
||||||
const [apiServerState, setApiServerState] = useState('disconnected')
|
const [apiServerState, setApiServerState] = useState('disconnected')
|
||||||
@ -88,31 +81,21 @@ const DashboardNavigation = () => {
|
|||||||
const { availableUpdate, checkForUpdates } = useAppUpdateContext()
|
const { availableUpdate, checkForUpdates } = useAppUpdateContext()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!userSettingsLoaded) return
|
const hydrateNavigationLabels = async () => {
|
||||||
|
const settings = isElectron
|
||||||
const hydrateAppearance = async () => {
|
? await getAppSettings()
|
||||||
const electronSettings = isElectron ? await getAppSettings() : {}
|
: userProfile?.settings || {}
|
||||||
const effective = getEffectiveAppearance({
|
if (settings?.showNavigationLabels !== undefined) {
|
||||||
isElectron,
|
setShowNavigationLabels(settings.showNavigationLabels)
|
||||||
userAppearance: userSettings?.appearance || {},
|
|
||||||
electronSettings
|
|
||||||
})
|
|
||||||
if (effective.theme) setThemeMode(effective.theme)
|
|
||||||
if (effective.density) setDensityMode(effective.density)
|
|
||||||
if (effective.showNavigationLabels !== undefined) {
|
|
||||||
setShowNavigationLabels(effective.showNavigationLabels)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void hydrateAppearance()
|
void hydrateNavigationLabels()
|
||||||
}, [
|
}, [
|
||||||
getAppSettings,
|
getAppSettings,
|
||||||
isElectron,
|
isElectron,
|
||||||
setDensityMode,
|
|
||||||
setShowNavigationLabels,
|
setShowNavigationLabels,
|
||||||
setThemeMode,
|
userProfile?.settings
|
||||||
userSettings?.appearance,
|
|
||||||
userSettingsLoaded
|
|
||||||
])
|
])
|
||||||
|
|
||||||
const includeDev = import.meta.env.DEV
|
const includeDev = import.meta.env.DEV
|
||||||
|
|||||||
@ -770,13 +770,9 @@ const ObjectForm = forwardRef(
|
|||||||
isEditingRef.current = true
|
isEditingRef.current = true
|
||||||
setIsEditing(true)
|
setIsEditing(true)
|
||||||
|
|
||||||
// Prefer the fetched snapshot over a stale objectData closure
|
const computedEntries = calculateComputedValues(objectData, model)
|
||||||
// (?action=edit can start before fetch finishes).
|
|
||||||
const baseData = serverObjectData.current
|
|
||||||
if (baseData) {
|
|
||||||
const computedEntries = calculateComputedValues(baseData, model)
|
|
||||||
const nextObjectData = {
|
const nextObjectData = {
|
||||||
...applyComputedEntries(baseData, computedEntries),
|
...applyComputedEntries(objectData, computedEntries),
|
||||||
_isEditing: true
|
_isEditing: true
|
||||||
}
|
}
|
||||||
setObjectData(nextObjectData)
|
setObjectData(nextObjectData)
|
||||||
@ -785,9 +781,6 @@ const ObjectForm = forwardRef(
|
|||||||
objectData: nextObjectData,
|
objectData: nextObjectData,
|
||||||
editDisabled: getEditDisabled(model, nextObjectData, userProfile)
|
editDisabled: getEditDisabled(model, nextObjectData, userProfile)
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
onStateChangeRef.current({ isEditing: true })
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
showError(
|
showError(
|
||||||
|
|||||||
@ -1,12 +1,8 @@
|
|||||||
import { useContext, useState, useEffect } from 'react'
|
// PrinterSelect.js
|
||||||
|
import { useState, useEffect } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import { Flex, Typography, Tag } from 'antd'
|
import { Flex, Typography, Tag } from 'antd'
|
||||||
import dayjs from 'dayjs'
|
import dayjs from 'dayjs'
|
||||||
import { ApiServerContext } from '../context/ApiServerContext'
|
|
||||||
import {
|
|
||||||
DEFAULT_DATE_TIME_FORMAT,
|
|
||||||
splitDateTimeFormat
|
|
||||||
} from '../../../database/Settings'
|
|
||||||
|
|
||||||
const { Text } = Typography
|
const { Text } = Typography
|
||||||
|
|
||||||
@ -84,11 +80,7 @@ const TimeDisplay = ({
|
|||||||
showSince = false,
|
showSince = false,
|
||||||
type = 'primary'
|
type = 'primary'
|
||||||
}) => {
|
}) => {
|
||||||
const { userSettings } = useContext(ApiServerContext) || {}
|
|
||||||
const [timeAgo, setTimeAgo] = useState(formatTimeDifference(dateTime))
|
const [timeAgo, setTimeAgo] = useState(formatTimeDifference(dateTime))
|
||||||
const dateTimeFormat =
|
|
||||||
userSettings?.appearance?.dateTimeFormat || DEFAULT_DATE_TIME_FORMAT
|
|
||||||
const { dateFormat, timeFormat } = splitDateTimeFormat(dateTimeFormat)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showSince) {
|
if (showSince) {
|
||||||
@ -104,18 +96,15 @@ const TimeDisplay = ({
|
|||||||
return <Text type='secondary'>n/a</Text>
|
return <Text type='secondary'>n/a</Text>
|
||||||
}
|
}
|
||||||
|
|
||||||
let displayFormat = ''
|
var dateFormat = ''
|
||||||
if (showDate && showTime) {
|
if (showDate == true) {
|
||||||
displayFormat = dateTimeFormat
|
dateFormat += 'YYYY-MM-DD '
|
||||||
} else if (showDate) {
|
}
|
||||||
displayFormat = dateFormat
|
if (showTime == true) {
|
||||||
} else if (showTime) {
|
dateFormat += 'HH:mm:ss '
|
||||||
displayFormat = timeFormat
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const formattedDate = displayFormat
|
const formattedDate = dayjs(dateTime).format(dateFormat)
|
||||||
? dayjs(dateTime).format(displayFormat)
|
|
||||||
: ''
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex align={'center'} gap={'small'} wrap>
|
<Flex align={'center'} gap={'small'} wrap>
|
||||||
|
|||||||
@ -38,8 +38,7 @@ const createEmptyUserSettings = () => ({
|
|||||||
sortSidebarVisibility: {},
|
sortSidebarVisibility: {},
|
||||||
columnVisibility: {},
|
columnVisibility: {},
|
||||||
collapseState: {},
|
collapseState: {},
|
||||||
pageLayout: {},
|
pageLayout: {}
|
||||||
appearance: {}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const normalizeUserSettingsCategory = (category) =>
|
const normalizeUserSettingsCategory = (category) =>
|
||||||
@ -77,8 +76,7 @@ const normalizeUserSettings = (settings = {}) => ({
|
|||||||
),
|
),
|
||||||
columnVisibility: normalizeUserSettingsCategory(settings?.columnVisibility),
|
columnVisibility: normalizeUserSettingsCategory(settings?.columnVisibility),
|
||||||
collapseState: normalizeUserSettingsCategory(settings?.collapseState),
|
collapseState: normalizeUserSettingsCategory(settings?.collapseState),
|
||||||
pageLayout: normalizeUserSettingsCategory(settings?.pageLayout),
|
pageLayout: normalizeUserSettingsCategory(settings?.pageLayout)
|
||||||
appearance: normalizeUserSettingsCategory(settings?.appearance)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const emitWithAcknowledgement = (socket, eventName, data) =>
|
const emitWithAcknowledgement = (socket, eventName, data) =>
|
||||||
|
|||||||
@ -1,306 +0,0 @@
|
|||||||
import PersonIcon from '../components/Icons/PersonIcon'
|
|
||||||
import OpenAppIcon from '../components/Icons/OpenAppIcon'
|
|
||||||
import SoftwareUpdateIcon from '../components/Icons/SoftwareUpdateIcon'
|
|
||||||
|
|
||||||
export const DEFAULT_DATE_TIME_FORMAT = 'MM/DD/YYYY HH:mm:ss'
|
|
||||||
|
|
||||||
const DATE_FORMAT_TOKENS = [
|
|
||||||
'YYYY',
|
|
||||||
'YY',
|
|
||||||
'MMMM',
|
|
||||||
'MMM',
|
|
||||||
'MM',
|
|
||||||
'M',
|
|
||||||
'dddd',
|
|
||||||
'ddd',
|
|
||||||
'dd',
|
|
||||||
'd',
|
|
||||||
'DD',
|
|
||||||
'Do',
|
|
||||||
'D',
|
|
||||||
'WW',
|
|
||||||
'W',
|
|
||||||
'ww',
|
|
||||||
'w',
|
|
||||||
'Q'
|
|
||||||
]
|
|
||||||
const TIME_FORMAT_TOKENS = [
|
|
||||||
'HH',
|
|
||||||
'H',
|
|
||||||
'hh',
|
|
||||||
'h',
|
|
||||||
'mm',
|
|
||||||
'm',
|
|
||||||
'ss',
|
|
||||||
's',
|
|
||||||
'SSS',
|
|
||||||
'SS',
|
|
||||||
'S',
|
|
||||||
'A',
|
|
||||||
'a',
|
|
||||||
'ZZ',
|
|
||||||
'Z',
|
|
||||||
'X',
|
|
||||||
'x'
|
|
||||||
]
|
|
||||||
const FORMAT_TOKEN_PATTERN = new RegExp(
|
|
||||||
`\\[[^\\]]*\\]|${[...DATE_FORMAT_TOKENS, ...TIME_FORMAT_TOKENS]
|
|
||||||
.sort((left, right) => right.length - left.length)
|
|
||||||
.join('|')}`,
|
|
||||||
'g'
|
|
||||||
)
|
|
||||||
const DATE_FORMAT_TOKEN_SET = new Set(DATE_FORMAT_TOKENS)
|
|
||||||
const TIME_FORMAT_TOKEN_SET = new Set(TIME_FORMAT_TOKENS)
|
|
||||||
|
|
||||||
const lastPartIndex = (parts, type) => {
|
|
||||||
for (let index = parts.length - 1; index >= 0; index -= 1) {
|
|
||||||
if (parts[index].type === type) return index
|
|
||||||
}
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
|
|
||||||
const extractFormatPart = (parts, type) => {
|
|
||||||
const first = parts.findIndex((part) => part.type === type)
|
|
||||||
const last = lastPartIndex(parts, type)
|
|
||||||
if (first === -1) return ''
|
|
||||||
return parts
|
|
||||||
.slice(first, last + 1)
|
|
||||||
.map((part) => part.value)
|
|
||||||
.join('')
|
|
||||||
.trim()
|
|
||||||
}
|
|
||||||
|
|
||||||
export const splitDateTimeFormat = (
|
|
||||||
format = DEFAULT_DATE_TIME_FORMAT
|
|
||||||
) => {
|
|
||||||
const source =
|
|
||||||
typeof format === 'string' && format.trim()
|
|
||||||
? format
|
|
||||||
: DEFAULT_DATE_TIME_FORMAT
|
|
||||||
const parts = []
|
|
||||||
let cursor = 0
|
|
||||||
|
|
||||||
for (const match of source.matchAll(FORMAT_TOKEN_PATTERN)) {
|
|
||||||
if (match.index > cursor) {
|
|
||||||
parts.push({ type: 'sep', value: source.slice(cursor, match.index) })
|
|
||||||
}
|
|
||||||
|
|
||||||
const token = match[0]
|
|
||||||
if (token.startsWith('[')) {
|
|
||||||
parts.push({ type: 'sep', value: token })
|
|
||||||
} else if (DATE_FORMAT_TOKEN_SET.has(token)) {
|
|
||||||
parts.push({ type: 'date', value: token })
|
|
||||||
} else if (TIME_FORMAT_TOKEN_SET.has(token)) {
|
|
||||||
parts.push({ type: 'time', value: token })
|
|
||||||
} else {
|
|
||||||
parts.push({ type: 'sep', value: token })
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor = match.index + token.length
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cursor < source.length) {
|
|
||||||
parts.push({ type: 'sep', value: source.slice(cursor) })
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
dateFormat: extractFormatPart(parts, 'date') || 'MM/DD/YYYY',
|
|
||||||
timeFormat: extractFormatPart(parts, 'time') || 'HH:mm:ss'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const USER_SETTING_NAMES = [
|
|
||||||
'theme',
|
|
||||||
'density',
|
|
||||||
'showNavigationLabels',
|
|
||||||
'dateTimeFormat',
|
|
||||||
'timezone'
|
|
||||||
]
|
|
||||||
|
|
||||||
export const APP_SETTING_NAMES = [
|
|
||||||
'appTheme',
|
|
||||||
'appShowNavigationLabels',
|
|
||||||
'appUpdateBranch',
|
|
||||||
'appUpdateEngine'
|
|
||||||
]
|
|
||||||
|
|
||||||
const themeOptions = [
|
|
||||||
{ label: 'Light', value: 'light' },
|
|
||||||
{ label: 'Dark', value: 'dark' },
|
|
||||||
{ label: 'System', value: 'system' }
|
|
||||||
]
|
|
||||||
|
|
||||||
const settings = {
|
|
||||||
sections: [
|
|
||||||
{
|
|
||||||
key: 'appearance',
|
|
||||||
name: 'User Settings',
|
|
||||||
visible: true,
|
|
||||||
icon: PersonIcon,
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: 'theme',
|
|
||||||
label: 'Theme',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: (_objectData, parentData) => [
|
|
||||||
...themeOptions,
|
|
||||||
...(parentData?.isElectron ? [{ label: 'App', value: 'app' }] : [])
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'density',
|
|
||||||
label: 'UI Density',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Comfortable', value: 'comfortable' },
|
|
||||||
{ label: 'Compact', value: 'compact' }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'showNavigationLabels',
|
|
||||||
label: 'Navigation Labels',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
options: [
|
|
||||||
{ label: 'Show', value: true },
|
|
||||||
{ label: 'Hide', value: false }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'dateTimeFormat',
|
|
||||||
label: 'Date and Time Format',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
defaultValue: DEFAULT_DATE_TIME_FORMAT
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'timezone',
|
|
||||||
label: 'Timezone',
|
|
||||||
type: 'text',
|
|
||||||
required: true,
|
|
||||||
defaultValue: 'UTC'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'app',
|
|
||||||
name: 'App Settings',
|
|
||||||
visible: ({ isElectron } = {}) => Boolean(isElectron),
|
|
||||||
icon: OpenAppIcon,
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: 'appTheme',
|
|
||||||
label: 'Theme',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
defaultValue: 'system',
|
|
||||||
options: themeOptions
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'appShowNavigationLabels',
|
|
||||||
label: 'Navigation Labels',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
defaultValue: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Show', value: true },
|
|
||||||
{ label: 'Hide', value: false }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: 'appUpdates',
|
|
||||||
name: 'App Update Settings',
|
|
||||||
visible: ({ isElectron } = {}) => Boolean(isElectron),
|
|
||||||
icon: SoftwareUpdateIcon,
|
|
||||||
column: 1,
|
|
||||||
properties: [
|
|
||||||
{
|
|
||||||
name: 'appUpdateBranch',
|
|
||||||
label: 'Branch',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
defaultValue: 'main',
|
|
||||||
options: (_objectData, parentData) =>
|
|
||||||
(parentData?.branches || []).map((branch) => ({
|
|
||||||
label: branch,
|
|
||||||
value: branch
|
|
||||||
}))
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'appUpdateEngine',
|
|
||||||
label: 'Engine',
|
|
||||||
type: 'select',
|
|
||||||
required: true,
|
|
||||||
defaultValue: 'native',
|
|
||||||
options: [
|
|
||||||
{ label: 'Native', value: 'native' },
|
|
||||||
{ label: 'Chromium', value: 'chromium' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getVisibleSettingsSections = (context = {}) =>
|
|
||||||
settings.sections.filter((section) => {
|
|
||||||
if (typeof section.visible === 'function') {
|
|
||||||
return section.visible(context)
|
|
||||||
}
|
|
||||||
return section.visible !== false
|
|
||||||
})
|
|
||||||
|
|
||||||
export const getSettingsDefaults = (context = {}) => {
|
|
||||||
const defaults = {}
|
|
||||||
for (const section of getVisibleSettingsSections(context)) {
|
|
||||||
for (const property of section.properties || []) {
|
|
||||||
if (property.defaultValue !== undefined) {
|
|
||||||
defaults[property.name] = property.defaultValue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return defaults
|
|
||||||
}
|
|
||||||
|
|
||||||
export const areSettingsValid = (values = {}, context = {}) =>
|
|
||||||
getVisibleSettingsSections(context).every((section) =>
|
|
||||||
(section.properties || []).every((property) => {
|
|
||||||
if (!property.required) return true
|
|
||||||
const value = values[property.name]
|
|
||||||
return value !== undefined && value !== null && value !== ''
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
export const pickSettings = (values = {}, names = []) =>
|
|
||||||
names.reduce((picked, name) => {
|
|
||||||
if (values[name] !== undefined) {
|
|
||||||
picked[name] = values[name]
|
|
||||||
}
|
|
||||||
return picked
|
|
||||||
}, {})
|
|
||||||
|
|
||||||
export const getEffectiveAppearance = ({
|
|
||||||
isElectron = false,
|
|
||||||
userAppearance = {},
|
|
||||||
electronSettings = {}
|
|
||||||
} = {}) => {
|
|
||||||
const overrideTheme = Boolean(isElectron && electronSettings.overrideTheme)
|
|
||||||
const appTheme = electronSettings.appTheme || electronSettings.theme
|
|
||||||
const appShowNavigationLabels =
|
|
||||||
electronSettings.appShowNavigationLabels ??
|
|
||||||
electronSettings.showNavigationLabels
|
|
||||||
|
|
||||||
return {
|
|
||||||
theme: overrideTheme ? appTheme : userAppearance.theme,
|
|
||||||
showNavigationLabels: isElectron
|
|
||||||
? (appShowNavigationLabels ?? userAppearance.showNavigationLabels)
|
|
||||||
: userAppearance.showNavigationLabels,
|
|
||||||
density: userAppearance.density
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default settings
|
|
||||||
@ -1,12 +1,10 @@
|
|||||||
import { createElement, lazy } from 'react'
|
import { createElement, lazy } from 'react'
|
||||||
|
|
||||||
const FilamentSkuInfo = lazy(
|
const FilamentSkuInfo = lazy(
|
||||||
() =>
|
() => import('../../components/Dashboard/Management/FilamentSkus/FilamentSkuInfo')
|
||||||
import('../../components/Dashboard/Management/FilamentSkus/FilamentSkuInfo')
|
|
||||||
)
|
)
|
||||||
const NewFilamentSku = lazy(
|
const NewFilamentSku = lazy(
|
||||||
() =>
|
() => import('../../components/Dashboard/Management/FilamentSkus/NewFilamentSku')
|
||||||
import('../../components/Dashboard/Management/FilamentSkus/NewFilamentSku')
|
|
||||||
)
|
)
|
||||||
const DeleteObject = lazy(
|
const DeleteObject = lazy(
|
||||||
() => import('../../components/Dashboard/common/DeleteObject')
|
() => import('../../components/Dashboard/common/DeleteObject')
|
||||||
@ -36,11 +34,7 @@ export const FilamentSku = {
|
|||||||
label: 'New Filament SKU',
|
label: 'New Filament SKU',
|
||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
content: (objectData, { onOk } = {}) => {
|
content: (objectData, { onOk } = {}) => {
|
||||||
return createElement(NewFilamentSku, {
|
return createElement(NewFilamentSku, { defaultValues: objectData, onOk, reset: true })
|
||||||
defaultValues: objectData,
|
|
||||||
onOk,
|
|
||||||
reset: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -129,7 +123,6 @@ export const FilamentSku = {
|
|||||||
'name',
|
'name',
|
||||||
'color',
|
'color',
|
||||||
'cost',
|
'cost',
|
||||||
'overrideCost',
|
|
||||||
'costWithTax',
|
'costWithTax',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt',
|
'updatedAt',
|
||||||
@ -142,7 +135,6 @@ export const FilamentSku = {
|
|||||||
'color',
|
'color',
|
||||||
'cost',
|
'cost',
|
||||||
'costWithTax',
|
'costWithTax',
|
||||||
'overrideCost',
|
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
],
|
],
|
||||||
@ -224,7 +216,7 @@ export const FilamentSku = {
|
|||||||
label: 'Override Cost',
|
label: 'Override Cost',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
columnWidth: 162
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cost',
|
name: 'cost',
|
||||||
|
|||||||
@ -34,11 +34,7 @@ export const PartSku = {
|
|||||||
label: 'New Part SKU',
|
label: 'New Part SKU',
|
||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
content: (objectData, { onOk } = {}) => {
|
content: (objectData, { onOk } = {}) => {
|
||||||
return createElement(NewPartSku, {
|
return createElement(NewPartSku, { defaultValues: objectData, onOk, reset: true })
|
||||||
defaultValues: objectData,
|
|
||||||
onOk,
|
|
||||||
reset: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -130,8 +126,6 @@ export const PartSku = {
|
|||||||
'cost',
|
'cost',
|
||||||
'costWithTax',
|
'costWithTax',
|
||||||
'price',
|
'price',
|
||||||
'overridePrice',
|
|
||||||
'overrideCost',
|
|
||||||
'priceWithTax',
|
'priceWithTax',
|
||||||
'margin',
|
'margin',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
@ -143,10 +137,8 @@ export const PartSku = {
|
|||||||
'part',
|
'part',
|
||||||
'name',
|
'name',
|
||||||
'cost',
|
'cost',
|
||||||
'overrideCost',
|
|
||||||
'costWithTax',
|
'costWithTax',
|
||||||
'price',
|
'price',
|
||||||
'overridePrice',
|
|
||||||
'priceWithTax',
|
'priceWithTax',
|
||||||
'margin',
|
'margin',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
@ -222,7 +214,7 @@ export const PartSku = {
|
|||||||
label: 'Override Cost',
|
label: 'Override Cost',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
columnWidth: 162
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -303,7 +295,7 @@ export const PartSku = {
|
|||||||
: objectData?.part?.priceMode
|
: objectData?.part?.priceMode
|
||||||
},
|
},
|
||||||
type: 'priceMode',
|
type: 'priceMode',
|
||||||
columnWidth: 165
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'price',
|
name: 'price',
|
||||||
|
|||||||
@ -1,12 +1,10 @@
|
|||||||
import { createElement, lazy } from 'react'
|
import { createElement, lazy } from 'react'
|
||||||
|
|
||||||
const ProductSkuInfo = lazy(
|
const ProductSkuInfo = lazy(
|
||||||
() =>
|
() => import('../../components/Dashboard/Management/ProductSkus/ProductSkuInfo')
|
||||||
import('../../components/Dashboard/Management/ProductSkus/ProductSkuInfo')
|
|
||||||
)
|
)
|
||||||
const NewProductSku = lazy(
|
const NewProductSku = lazy(
|
||||||
() =>
|
() => import('../../components/Dashboard/Management/ProductSkus/NewProductSku')
|
||||||
import('../../components/Dashboard/Management/ProductSkus/NewProductSku')
|
|
||||||
)
|
)
|
||||||
const DeleteObject = lazy(
|
const DeleteObject = lazy(
|
||||||
() => import('../../components/Dashboard/common/DeleteObject')
|
() => import('../../components/Dashboard/common/DeleteObject')
|
||||||
@ -36,11 +34,7 @@ export const ProductSku = {
|
|||||||
label: 'New Product SKU',
|
label: 'New Product SKU',
|
||||||
icon: PlusIcon,
|
icon: PlusIcon,
|
||||||
content: (objectData, { onOk } = {}) => {
|
content: (objectData, { onOk } = {}) => {
|
||||||
return createElement(NewProductSku, {
|
return createElement(NewProductSku, { defaultValues: objectData, onOk, reset: true })
|
||||||
defaultValues: objectData,
|
|
||||||
onOk,
|
|
||||||
reset: true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -132,8 +126,6 @@ export const ProductSku = {
|
|||||||
'cost',
|
'cost',
|
||||||
'costWithTax',
|
'costWithTax',
|
||||||
'price',
|
'price',
|
||||||
'overridePrice',
|
|
||||||
'overrideCost',
|
|
||||||
'priceWithTax',
|
'priceWithTax',
|
||||||
'margin',
|
'margin',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
@ -148,8 +140,6 @@ export const ProductSku = {
|
|||||||
'costWithTax',
|
'costWithTax',
|
||||||
'price',
|
'price',
|
||||||
'priceWithTax',
|
'priceWithTax',
|
||||||
'overridePrice',
|
|
||||||
'overrideCost',
|
|
||||||
'margin',
|
'margin',
|
||||||
'createdAt',
|
'createdAt',
|
||||||
'updatedAt'
|
'updatedAt'
|
||||||
@ -225,7 +215,7 @@ export const ProductSku = {
|
|||||||
label: 'Override Cost',
|
label: 'Override Cost',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
columnWidth: 162
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'cost',
|
name: 'cost',
|
||||||
@ -239,7 +229,9 @@ export const ProductSku = {
|
|||||||
return objectData?.overrideCost
|
return objectData?.overrideCost
|
||||||
},
|
},
|
||||||
value: (objectData) =>
|
value: (objectData) =>
|
||||||
objectData?.overrideCost ? objectData?.cost : objectData?.product?.cost,
|
objectData?.overrideCost
|
||||||
|
? objectData?.cost
|
||||||
|
: objectData?.product?.cost,
|
||||||
columnWidth: 100
|
columnWidth: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -290,7 +282,7 @@ export const ProductSku = {
|
|||||||
label: 'Override Price',
|
label: 'Override Price',
|
||||||
required: true,
|
required: true,
|
||||||
type: 'bool',
|
type: 'bool',
|
||||||
columnWidth: 165
|
columnWidth: 150
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'priceMode',
|
name: 'priceMode',
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user