diff options
Diffstat (limited to 'src/components/configuration')
| -rw-r--r-- | src/components/configuration/checkBoxWrapper.tsx | 37 | ||||
| -rw-r--r-- | src/components/configuration/config.tsx | 248 | ||||
| -rw-r--r-- | src/components/configuration/inputWrapper.tsx | 41 | ||||
| -rw-r--r-- | src/components/configuration/selectWrapper.tsx | 44 | ||||
| -rw-r--r-- | src/components/configuration/textAreaWrapper.tsx | 59 |
5 files changed, 429 insertions, 0 deletions
diff --git a/src/components/configuration/checkBoxWrapper.tsx b/src/components/configuration/checkBoxWrapper.tsx new file mode 100644 index 0000000..e9dd1eb --- /dev/null +++ b/src/components/configuration/checkBoxWrapper.tsx @@ -0,0 +1,37 @@ +import ConfigType from '../../../common/types/configType' + +type CheckBoxProps = { + title: string + keyName: keyof ConfigType + checked?: boolean + disabled?: boolean + + handleChange: (value: any, key: keyof ConfigType) => void +} + +const CheckBoxWrapper = ({ + title, + keyName, + checked, + disabled, + handleChange +}: CheckBoxProps) => { + return ( + <div className="form-control"> + <label className="label cursor-pointer justify-start gap-2"> + <input + className="checkbox checkbox-sm" + type="checkbox" + checked={!!checked} + disabled={disabled} + onChange={(e) => { + handleChange({ state: e.target.checked }, keyName) + }} + /> + <span className="label-text">{title}</span> + </label> + </div> + ) +} + +export default CheckBoxWrapper diff --git a/src/components/configuration/config.tsx b/src/components/configuration/config.tsx new file mode 100644 index 0000000..9331f87 --- /dev/null +++ b/src/components/configuration/config.tsx @@ -0,0 +1,248 @@ +import React, { useContext, useEffect } from 'react' +import { useRouter } from 'next/router' + +import { RepoQueryResponse } from '../../../common/github/repoQuery' +import ConfigContext from '../../contexts/ConfigContext' + +import ConfigType, { + Theme, + Pattern, + Font, + RequiredConfigsKeys +} from '../../../common/types/configType' + +import { getOptionalConfig } from '../../../common/configHelper' + +import SelectWrapper from './selectWrapper' +import CheckBoxWrapper from './checkBoxWrapper' +import InputWrapper from './inputWrapper' +import TextAreaWrapper from './textAreaWrapper' + +type ConfigProp = { + repository: RepoQueryResponse['repository'] +} + +const Config = ({ repository }: ConfigProp) => { + const router = useRouter() + + const { config, setConfig } = useContext(ConfigContext) + + const handleChanges = (changes: { value: any; key: keyof ConfigType }[]) => { + let newConfig: ConfigType = { ...config } + const urlParams = router.query + // Remove extraneous params from route + delete urlParams._owner + delete urlParams._name + changes.forEach(({ value, key }) => { + const currentValue = newConfig[key] ? newConfig[key] : {} + if (value.required === true) { + newConfig = { ...newConfig, [key]: value.val } + } else { + newConfig = { ...newConfig, [key]: { ...currentValue, ...value } } + } + + if (value && value.state === true && value.editable) { + urlParams[key] = '1' + urlParams[`${key}Editable`] = value.value + } else if (value && value.state === true) { + urlParams[key] = '1' + } else if (value && value.required === true) { + urlParams[key] = value.val + } else { + urlParams[key] = '0' + } + + if (!urlParams[key] || urlParams[key] === '0') { + delete urlParams[key] + if (`${key}Editable` in urlParams) { + delete urlParams[`${key}Editable`] + } + } + }) + + router.push( + `${window.location.pathname}?${Object.entries(urlParams) + .sort() + .map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`) + .join('&')}`, + undefined, + { shallow: true } + ) + } + + const handleChange = (value: any, key: keyof ConfigType) => { + handleChanges([{ value, key }]) + } + + useEffect(() => { + const handleRouteChange = (asPath: string) => { + if (repository) { + const newConfig = getOptionalConfig(repository) + if (newConfig) { + const params = new URLSearchParams(asPath.split('?')[1]) + + Array.from(params.keys()).forEach((stringKey) => { + const key = stringKey as keyof ConfigType + if (key in newConfig) { + const query = params.get(key) + const currentConfig = newConfig[key as keyof typeof newConfig] + const newChange = { + state: query === '1' + } + if (currentConfig?.editable) { + const editableValue = params.get(`${key}Editable`) + if (editableValue != null) { + Object.assign(newChange, { + value: editableValue + }) + } + } + + Object.assign( + newConfig[key as keyof typeof newConfig] ?? {}, + newChange + ) + } else if (key in RequiredConfigsKeys) { + const query = params.get(key) + if (query != null) { + const newChange = { + [key]: query + } + + Object.assign(newConfig, newChange) + } + } + }) + setConfig({ ...config, ...newConfig }) + } + } + } + + router.events.on('routeChangeComplete', handleRouteChange) + handleRouteChange(router.asPath) + + return () => { + router.events.off('routeChangeComplete', handleRouteChange) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + if (!repository) { + return null + } + + return ( + <div className="card w-96 max-w-[90vw] bg-base-200 text-primary-content shadow-xl"> + <div className="card-body"> + <SelectWrapper + title="Theme" + keyName="theme" + map={Object.keys(Theme).map((key) => ({ + key, + label: (Theme as any)[key] + }))} + value={config.theme} + handleChange={handleChange} + /> + <SelectWrapper + title="Font" + keyName="font" + map={Object.keys(Font).map((key) => ({ + key, + label: (Font as any)[key] + }))} + value={config.font} + handleChange={handleChange} + /> + <SelectWrapper + title="Background Pattern" + keyName="pattern" + map={Object.keys(Pattern).map((key) => ({ + key, + label: (Pattern as any)[key] + }))} + value={config.pattern} + handleChange={handleChange} + /> + <InputWrapper + title="Logo" + alt="Image url or data uri" + keyName="logo" + placeholder="Optional" + value={config.logo} + handleChange={handleChange} + /> + + <div className="columns-2"> + <CheckBoxWrapper + title="Name" + keyName="name" + checked={config.name?.state} + handleChange={handleChange} + disabled + /> + <CheckBoxWrapper + title="Owner" + keyName="owner" + checked={config.owner?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="Language" + keyName="language" + checked={config.language?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="2nd Language" + keyName="language2" + checked={config.language2?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="Stars" + keyName="stargazers" + checked={config.stargazers?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="Forks" + keyName="forks" + checked={config.forks?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="Issues" + keyName="issues" + checked={config.issues?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="Pull Requests" + keyName="pulls" + checked={config.pulls?.state} + handleChange={handleChange} + /> + <CheckBoxWrapper + title="Description" + keyName="description" + checked={config.description?.state} + handleChange={handleChange} + /> + </div> + + {config.description?.state && ( + <TextAreaWrapper + title="Description" + keyName="description" + value={config.description?.value} + handleChange={handleChange} + disabled={!config.description?.state} + /> + )} + </div> + </div> + ) +} + +export default Config diff --git a/src/components/configuration/inputWrapper.tsx b/src/components/configuration/inputWrapper.tsx new file mode 100644 index 0000000..0d3fd95 --- /dev/null +++ b/src/components/configuration/inputWrapper.tsx @@ -0,0 +1,41 @@ +import ConfigType from '../../../common/types/configType' + +type InputProps = { + title: string + alt?: string + keyName: keyof ConfigType + value: string + placeholder: string + disabled?: boolean + handleChange: (value: any, key: keyof ConfigType) => void +} + +const InputWrapper = ({ + title, + alt, + keyName, + value, + placeholder, + disabled, + handleChange +}: InputProps) => { + return ( + <div className="form-control w-full" style={{ display: 'none' }}> + <label className="label"> + <span className="label-text">{title}</span> + {alt && <span className="label-text-alt">{alt}</span>} + </label> + <input + className="input input-bordered w-full input-sm" + type="text" + value={value || ''} + disabled={!!disabled} + placeholder={placeholder} + onChange={(e) => { + handleChange({ val: e.target.value, required: true }, keyName) + }} + /> + </div> + ) +} +export default InputWrapper diff --git a/src/components/configuration/selectWrapper.tsx b/src/components/configuration/selectWrapper.tsx new file mode 100644 index 0000000..81c3709 --- /dev/null +++ b/src/components/configuration/selectWrapper.tsx @@ -0,0 +1,44 @@ +import ConfigType from '../../../common/types/configType' + +type SelectWrapperProps = { + title: string + alt?: string + keyName: keyof ConfigType + map: { key: string; label: any }[] + value: string + handleChange: (value: any, key: keyof ConfigType) => void +} + +const SelectWrapper = ({ + title, + alt, + keyName, + map, + value, + handleChange +}: SelectWrapperProps) => { + return ( + <div className="form-control w-full"> + <label className="label"> + <span className="label-text">{title}</span> + {alt && <span className="label-text-alt">{alt}</span>} + </label> + <select + className="select select-bordered select-sm" + onChange={(e) => { + handleChange({ val: e.target.value, required: true }, keyName) + }} + value={value}> + {map.map(({ key, label }) => { + return ( + <option key={key} value={label}> + {label} + </option> + ) + })} + </select> + </div> + ) +} + +export default SelectWrapper diff --git a/src/components/configuration/textAreaWrapper.tsx b/src/components/configuration/textAreaWrapper.tsx new file mode 100644 index 0000000..6a30f42 --- /dev/null +++ b/src/components/configuration/textAreaWrapper.tsx @@ -0,0 +1,59 @@ +import React, { useEffect, useState } from 'react' + +import { useDebouncedCallback } from 'use-debounce' + +import ConfigType from '../../../common/types/configType' + +type TextAreaProps = { + title?: string + alt?: string + value: string + placeholder?: string + keyName: keyof ConfigType + handleChange: (value: any, key: keyof ConfigType) => void + disabled?: boolean +} + +const TextAreaWrapper = ({ + title, + alt, + keyName, + value, + placeholder, + handleChange, + disabled +}: TextAreaProps) => { + const [internalValue, setInternalValue] = useState(value) + + const debounced = useDebouncedCallback((value) => { + handleChange({ value, editable: true, state: true }, keyName) + }, 500) + + const processChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { + setInternalValue(e.target.value) + debounced(e.target.value) + } + + useEffect(() => { + setInternalValue(value) + }, [value]) + + return ( + <div className="form-control" style={{ display: 'none' }}> + {title && ( + <label className="label"> + <span className="label-text">{title}</span> + {alt && <span className="label-text-alt">{alt}</span>} + </label> + )} + <textarea + className="textarea textarea-bordered h-20" + value={internalValue} + onChange={processChange} + disabled={disabled} + placeholder={placeholder} + /> + </div> + ) +} +export default TextAreaWrapper |
