Skip to content
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
import React, { ReactNode, Reducer, useCallback, useEffect, useReducer } from 'react'
import SettingComponent from './SettingComponent'
import { SettingsDynamic, SettingsTypeWithoutDynamic, DynamicSettingsValue } from '@shared/types'
import { SETTINGS_COMPONENTS, SettingsProps } from '.'
import Button from '../Button'
import { IconTrash } from '@renderer/assets/icons'

interface SettingsDynamicProps {
setting: SettingsDynamic
handleSettingChange: (value: DynamicSettingsValue) => void
className?: string
}

interface SettingsPropsWithoutDynamic extends Omit<SettingsProps, 'setting'> {
setting: SettingsTypeWithoutDynamic
}

type SettingsAction =
| {
type: 'UPDATE_ENTRY'
index: number
key: string
value: SettingsTypeWithoutDynamic['value']
}
| { type: 'ADD_ENTRY'; options: SettingsTypeWithoutDynamic[] }
| { type: 'REMOVE_ENTRY'; index: number }

const createEmptyEntry = (
options: SettingsTypeWithoutDynamic[]
): Record<string, SettingsTypeWithoutDynamic['value']> => {
return options.reduce(
(acc, option) => ({
...acc,
[option.label]: option.value
}),
{}
)
}

const settingsReducer: Reducer<DynamicSettingsValue, SettingsAction> = (state, action) => {
switch (action.type) {
case 'UPDATE_ENTRY':
return state.map((entry, i) =>
i === action.index
? {
...entry,
[action.key]: action.value
}
: entry
)
case 'ADD_ENTRY':
return [...state, createEmptyEntry(action.options)]
case 'REMOVE_ENTRY':
return state.filter((_, i) => i !== action.index)
default:
return state
}
}

const settingRenderer = ({
setting,
className,
handleSettingChange
}: SettingsPropsWithoutDynamic): ReactNode => {
const SettingComponent = SETTINGS_COMPONENTS[setting.type] as React.ComponentType<{
setting: SettingsTypeWithoutDynamic
handleSettingChange: (value: SettingsTypeWithoutDynamic['value']) => void
className?: string
}>

return SettingComponent ? (
<SettingComponent
setting={setting}
className={className}
handleSettingChange={handleSettingChange}
/>
) : null
}

const createInitialState = (setting: SettingsDynamic): DynamicSettingsValue => {
return setting.value || [createEmptyEntry(setting.options)]
}

export const SettingsDynamicComponent: React.FC<SettingsDynamicProps> = ({
className,
setting,
handleSettingChange
}: SettingsDynamicProps) => {
const [state, dispatch] = useReducer(settingsReducer, setting, createInitialState)

useEffect(() => {
handleSettingChange(state)
}, [state, handleSettingChange])

const handleChange = useCallback(
(index: number, key: string, value: SettingsTypeWithoutDynamic['value']): void => {
dispatch({
type: 'UPDATE_ENTRY',
index,
key,
value
})
},
[]
)

const handleAddEntry = useCallback(() => {
dispatch({ type: 'ADD_ENTRY', options: setting.options })
}, [setting.options])

const handleRemoveEntry = useCallback((index: number) => {
dispatch({ type: 'REMOVE_ENTRY', index })
}, [])

const renderEntry = useCallback(
(index: number): ReturnType<typeof settingRenderer>[] => {
return setting.options.map((option) => {
const currentValue = state[index]?.[option.label]

return settingRenderer({
setting: {
...option,
value: currentValue ?? option.value
} as Extract<SettingsTypeWithoutDynamic, { type: typeof option.type }>,
className: 'flex flex-col',
handleSettingChange: (value) =>
handleChange(index, option.label, value as SettingsTypeWithoutDynamic['value'])
})
})
},
[setting.options, state, handleChange]
)

return (
<SettingComponent setting={setting} className={className}>
{state.map((_, index) => (
<div key={index} className="flex flex-col gap-4 mb-4">
<div className="flex items-center flex-col w-full">
{renderEntry(index)}
{state.length > 1 && (
<Button
className="justify-center gap-2 hover:bg-red-500 border-red-500 border w-10 shrink-0"
onClick={() => handleRemoveEntry(index)}
>
<IconTrash iconSize={64} className="w-full h-full text-red-500" />
</Button>
)}
</div>
</div>
))}

<button
onClick={handleAddEntry}
className="mt-4 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
Add New Entry
</button>

<code>
<pre>{JSON.stringify(state, null, 2)}</pre>
</code>
</SettingComponent>
)
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingsBoolean } from '@shared/types'
import { SettingsBoolean, SettingsOutputValue } from '@shared/types'
import Button from '../Button'
import { IconToggle } from '@renderer/assets/icons'

interface SettingsBooleanProps {
setting: SettingsBoolean
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useState, useEffect } from 'react'
import SettingComponent from './SettingComponent'
import { SettingsColor } from '@shared/types'
import { SettingsColor, SettingsOutputValue } from '@shared/types'

interface SettingsColorProps {
setting: SettingsColor
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingsList } from '@shared/types'
import { SettingsList, SettingsOutputValue } from '@shared/types'
import TagList from '../TagList'

interface SettingsListProps {
setting: SettingsList
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingOption, SettingsMultiSelect } from '@shared/types'
import { SettingOption, SettingsMultiSelect, SettingsOutputValue } from '@shared/types'
import Select from '../Select'
import { MultiValue } from 'react-select'

interface SettingsMultiSelectProps {
setting: SettingsMultiSelect
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingsNumber } from '@shared/types'
import { SettingsNumber, SettingsOutputValue } from '@shared/types'

interface SettingsNumberProps {
setting: SettingsNumber
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingsRange } from '@shared/types'
import { SettingsOutputValue, SettingsRange } from '@shared/types'

interface SettingsRangeProps {
setting: SettingsRange
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingsRanked } from '@shared/types'
import { SettingsOutputValue, SettingsRanked } from '@shared/types'
import RankableList from '../RankableList'

interface SettingsRankedProps {
setting: SettingsRanked
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingOption, SettingsSelect } from '@shared/types'
import { SettingOption, SettingsOutputValue, SettingsSelect } from '@shared/types'
import { SingleValue } from 'react-select'
import Select from '../Select'

interface SettingsSelectProps {
setting: SettingsSelect
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React from 'react'
import SettingComponent from './SettingComponent'
import { SettingsString } from '@shared/types'
import { SettingsOutputValue, SettingsString } from '@shared/types'

interface SettingsStringProps {
setting: SettingsString
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

Expand All @@ -21,7 +21,7 @@ export const SettingsStringComponent: React.FC<SettingsStringProps> = ({
<div className="flex items-center w-full">
<input
type="text"
value={setting.value as string}
defaultValue={setting.value as string}
maxLength={(setting as SettingsString).maxLength}
onChange={(e) => handleSettingChange(e.target.value)}
className={commonClasses + ' text-black w-96 max-w-s'}
Expand Down
14 changes: 8 additions & 6 deletions DeskThingServer/src/renderer/src/components/settings/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React from 'react'
import { SettingsType } from '@shared/types'
import { SettingsOutputValue, SettingsType } from '@shared/types'
import { SettingsBooleanComponent } from './SettingsBoolean'
import { SettingsListComponent } from './SettingsList'
import { SettingsMultiSelectComponent } from './SettingsMultiSelect'
Expand All @@ -9,17 +9,18 @@ import { SettingsRankedComponent } from './SettingsRanked'
import { SettingsSelectComponent } from './SettingsSelect'
import { SettingsStringComponent } from './SettingsString'
import { SettingsColorComponent } from './SettingsColor'
import { SettingsDynamicComponent } from './SettingDynamic'

export interface SettingsProps {
setting: SettingsType
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}

const SETTINGS_COMPONENTS: {
export const SETTINGS_COMPONENTS: {
[K in SettingsType['type']]: React.ComponentType<{
setting: SettingsType & { type: K }
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}>
} = {
Expand All @@ -31,13 +32,14 @@ const SETTINGS_COMPONENTS: {
ranked: SettingsRankedComponent,
select: SettingsSelectComponent,
string: SettingsStringComponent,
color: SettingsColorComponent
color: SettingsColorComponent,
dynamic: SettingsDynamicComponent
} as const

export const Settings: React.FC<SettingsProps> = ({ setting, className, handleSettingChange }) => {
const SettingComponent = SETTINGS_COMPONENTS[setting.type] as React.ComponentType<{
setting: SettingsType
handleSettingChange: (value: number | boolean | string | string[]) => void
handleSettingChange: (value: SettingsOutputValue) => void
className?: string
}>
return SettingComponent ? (
Expand Down
Loading