diff options
author | crupest <crupest@outlook.com> | 2021-05-06 22:01:12 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-05-06 22:01:12 +0800 |
commit | ccc9fd220ac52b2b24ebe9e5978a63fd2ec8c968 (patch) | |
tree | 6184aac232f9f327d4a8ddacdc7b226ff7617499 /FrontEnd | |
parent | 669b425e4795ce245c664c82223ee9fa4611dfa9 (diff) | |
download | timeline-ccc9fd220ac52b2b24ebe9e5978a63fd2ec8c968.tar.gz timeline-ccc9fd220ac52b2b24ebe9e5978a63fd2ec8c968.tar.bz2 timeline-ccc9fd220ac52b2b24ebe9e5978a63fd2ec8c968.zip |
feat: Timeline color.
Diffstat (limited to 'FrontEnd')
-rw-r--r-- | FrontEnd/package-lock.json | 124 | ||||
-rw-r--r-- | FrontEnd/package.json | 2 | ||||
-rw-r--r-- | FrontEnd/src/app/http/timeline.ts | 1 | ||||
-rw-r--r-- | FrontEnd/src/app/locales/en/translation.json | 3 | ||||
-rw-r--r-- | FrontEnd/src/app/locales/zh/translation.json | 3 | ||||
-rw-r--r-- | FrontEnd/src/app/palette.ts | 4 | ||||
-rw-r--r-- | FrontEnd/src/app/views/admin/UserAdmin.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/app/views/common/OperationDialog.tsx | 107 | ||||
-rw-r--r-- | FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx | 57 |
9 files changed, 246 insertions, 59 deletions
diff --git a/FrontEnd/package-lock.json b/FrontEnd/package-lock.json index a2b8539e..2524e59d 100644 --- a/FrontEnd/package-lock.json +++ b/FrontEnd/package-lock.json @@ -21,6 +21,7 @@ "pepjs": "^0.5.3", "react": "^17.0.1", "react-bootstrap": "^2.0.0-alpha.1", + "react-color": "^2.19.3", "react-dom": "^17.0.2", "react-i18next": "^11.8.15", "react-inlinesvg": "^2.3.0", @@ -54,6 +55,7 @@ "@types/lodash": "^4.14.168", "@types/node": "^15.0.2", "@types/react": "^17.0.5", + "@types/react-color": "^3.0.4", "@types/react-dom": "^17.0.3", "@types/react-responsive": "^8.0.2", "@types/react-router": "^5.1.13", @@ -1654,6 +1656,14 @@ "@hapi/hoek": "^8.3.0" } }, + "node_modules/@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "peerDependencies": { + "react": "*" + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -2089,6 +2099,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-color": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.4.tgz", + "integrity": "sha512-EswbYJDF1kkrx93/YU+BbBtb46CCtDMvTiGmcOa/c5PETnwTiSWoseJ1oSWeRl/4rUXkhME9bVURvvPg0W5YQw==", + "dev": true, + "dependencies": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, "node_modules/@types/react-dom": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.3.tgz", @@ -2146,6 +2166,15 @@ "@types/react": "*" } }, + "node_modules/@types/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-d2gQQ0IL6hXLnoRfVYZukQNWHuVsE75DzFTLPUuyyEhJS8G2VvlE+qfQQ91SJjaMqlURRCNIsX7Jcsw6cEuJlA==", + "dev": true, + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/remarkable": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz", @@ -7909,6 +7938,11 @@ "css-mediaquery": "^0.1.2" } }, + "node_modules/material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -10980,6 +11014,23 @@ "react-dom": ">=16.14.0" } }, + "node_modules/react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "dependencies": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "react": "*" + } + }, "node_modules/react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -11145,6 +11196,14 @@ "react-dom": ">=16.6.0" } }, + "node_modules/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "dependencies": { + "lodash": "^4.0.1" + } + }, "node_modules/read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -12609,6 +12668,14 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "node_modules/tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", + "engines": { + "node": "*" + } + }, "node_modules/to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -15114,6 +15181,12 @@ "@hapi/hoek": "^8.3.0" } }, + "@icons/material": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/@icons/material/-/material-0.2.4.tgz", + "integrity": "sha512-QPcGmICAPbGLGb6F/yNf/KzKqvFx8z5qx3D1yFqVAjoFmXK35EgyW+cJ57Te3CNsmzblwtzakLGFqHPqrfb4Tw==", + "requires": {} + }, "@nodelib/fs.scandir": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz", @@ -15460,6 +15533,16 @@ "csstype": "^3.0.2" } }, + "@types/react-color": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/react-color/-/react-color-3.0.4.tgz", + "integrity": "sha512-EswbYJDF1kkrx93/YU+BbBtb46CCtDMvTiGmcOa/c5PETnwTiSWoseJ1oSWeRl/4rUXkhME9bVURvvPg0W5YQw==", + "dev": true, + "requires": { + "@types/react": "*", + "@types/reactcss": "*" + } + }, "@types/react-dom": { "version": "17.0.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.3.tgz", @@ -15517,6 +15600,15 @@ "@types/react": "*" } }, + "@types/reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@types/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-d2gQQ0IL6hXLnoRfVYZukQNWHuVsE75DzFTLPUuyyEhJS8G2VvlE+qfQQ91SJjaMqlURRCNIsX7Jcsw6cEuJlA==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/remarkable": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/@types/remarkable/-/remarkable-2.0.1.tgz", @@ -19995,6 +20087,11 @@ "css-mediaquery": "^0.1.2" } }, + "material-colors": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/material-colors/-/material-colors-1.2.6.tgz", + "integrity": "sha512-6qE4B9deFBIa9YSpOc9O0Sgc43zTeVYbgDT5veRKSlB2+ZuHNoVVxA1L/ckMUayV9Ay9y7Z/SZCLcGteW9i7bg==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -22268,6 +22365,20 @@ "warning": "^4.0.3" } }, + "react-color": { + "version": "2.19.3", + "resolved": "https://registry.npmjs.org/react-color/-/react-color-2.19.3.tgz", + "integrity": "sha512-LEeGE/ZzNLIsFWa1TMe8y5VYqr7bibneWmvJwm1pCn/eNmrabWDh659JSPn9BuaMpEfU83WTOJfnCcjDZwNQTA==", + "requires": { + "@icons/material": "^0.2.4", + "lodash": "^4.17.15", + "lodash-es": "^4.17.15", + "material-colors": "^1.2.1", + "prop-types": "^15.5.10", + "reactcss": "^1.2.0", + "tinycolor2": "^1.4.1" + } + }, "react-dom": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz", @@ -22394,6 +22505,14 @@ "prop-types": "^15.6.2" } }, + "reactcss": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/reactcss/-/reactcss-1.2.3.tgz", + "integrity": "sha512-KiwVUcFu1RErkI97ywr8nvx8dNOpT03rbnma0SSalTYjkrPYaEajR4a/MRt6DZ46K6arDRbWMNHF+xH7G7n/8A==", + "requires": { + "lodash": "^4.0.1" + } + }, "read-pkg": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", @@ -23554,6 +23673,11 @@ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz", "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==" }, + "tinycolor2": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", + "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 5abb8d65..c44613cc 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -19,6 +19,7 @@ "pepjs": "^0.5.3",
"react": "^17.0.1",
"react-bootstrap": "^2.0.0-alpha.1",
+ "react-color": "^2.19.3",
"react-dom": "^17.0.2",
"react-i18next": "^11.8.15",
"react-inlinesvg": "^2.3.0",
@@ -69,6 +70,7 @@ "@types/lodash": "^4.14.168",
"@types/node": "^15.0.2",
"@types/react": "^17.0.5",
+ "@types/react-color": "^3.0.4",
"@types/react-dom": "^17.0.3",
"@types/react-responsive": "^8.0.2",
"@types/react-router": "^5.1.13",
diff --git a/FrontEnd/src/app/http/timeline.ts b/FrontEnd/src/app/http/timeline.ts index 50af259e..efc402c1 100644 --- a/FrontEnd/src/app/http/timeline.ts +++ b/FrontEnd/src/app/http/timeline.ts @@ -72,6 +72,7 @@ export interface HttpTimelinePostPostRequest { export interface HttpTimelinePatchRequest { name?: string; title?: string; + color?: string; visibility?: TimelineVisibility; description?: string; } diff --git a/FrontEnd/src/app/locales/en/translation.json b/FrontEnd/src/app/locales/en/translation.json index 73cee2e6..1261b086 100644 --- a/FrontEnd/src/app/locales/en/translation.json +++ b/FrontEnd/src/app/locales/en/translation.json @@ -85,7 +85,8 @@ "title": "Change Timeline Properties", "titleField": "Title", "visibility": "Visibility", - "description": "Description" + "description": "Description", + "color": "Color" }, "member": { "noUserAvailableToAdd": "Sorry, no user available to be a member in search result.", diff --git a/FrontEnd/src/app/locales/zh/translation.json b/FrontEnd/src/app/locales/zh/translation.json index 1a1a70ab..b2c651f6 100644 --- a/FrontEnd/src/app/locales/zh/translation.json +++ b/FrontEnd/src/app/locales/zh/translation.json @@ -85,7 +85,8 @@ "title": "修改时间线属性", "titleField": "标题", "visibility": "可见性", - "description": "描述" + "description": "描述", + "color": "颜色" }, "member": { "noUserAvailableToAdd": "搜索结果显示没有可以添加为成员的用户。", diff --git a/FrontEnd/src/app/palette.ts b/FrontEnd/src/app/palette.ts index 98e7d814..c4f4f4f9 100644 --- a/FrontEnd/src/app/palette.ts +++ b/FrontEnd/src/app/palette.ts @@ -95,9 +95,11 @@ const paletteSubject: BehaviorSubject<Palette> = new BehaviorSubject<Palette>( export const palette$: Observable<Palette> = paletteSubject.asObservable(); palette$.subscribe((palette) => { - let styleTag = document.getElementById("timeline-palette-css"); + const styleTagId = "timeline-palette-css"; + let styleTag = document.getElementById(styleTagId); if (styleTag == null) { styleTag = document.createElement("style"); + styleTag.id = styleTagId; document.head.append(styleTag); } styleTag.innerHTML = generatePaletteCSS(palette); diff --git a/FrontEnd/src/app/views/admin/UserAdmin.tsx b/FrontEnd/src/app/views/admin/UserAdmin.tsx index 61140bb1..26c40a33 100644 --- a/FrontEnd/src/app/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/app/views/admin/UserAdmin.tsx @@ -3,7 +3,7 @@ import classnames from "classnames"; import { ListGroup, Row, Col, Spinner, Button } from "react-bootstrap"; import OperationDialog, { - OperationBoolInputInfo, + OperationDialogBoolInput, } from "../common/OperationDialog"; import { AuthUser } from "@/services/user"; @@ -145,7 +145,7 @@ const UserPermissionModifyDialog: React.FC< 0<UsernameLabel>{username}</UsernameLabel>2 </Trans> )} - inputScheme={kUserPermissionList.map<OperationBoolInputInfo>( + inputScheme={kUserPermissionList.map<OperationDialogBoolInput>( (permission, index) => ({ type: "bool", label: permission, diff --git a/FrontEnd/src/app/views/common/OperationDialog.tsx b/FrontEnd/src/app/views/common/OperationDialog.tsx index 5887be48..c9fa709f 100644 --- a/FrontEnd/src/app/views/common/OperationDialog.tsx +++ b/FrontEnd/src/app/views/common/OperationDialog.tsx @@ -1,6 +1,7 @@ import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { Form, Button, Modal } from "react-bootstrap"; +import { ChromePicker } from "react-color"; import { convertI18nText, I18nText, UiLogicError } from "@/common"; @@ -27,7 +28,7 @@ const DefaultErrorPrompt: React.FC<DefaultErrorPromptProps> = (props) => { return result; }; -export interface OperationTextInputInfo { +export interface OperationDialogTextInput { type: "text"; label?: I18nText; password?: boolean; @@ -39,40 +40,51 @@ export interface OperationTextInputInfo { helperText?: string; } -export interface OperationBoolInputInfo { +export interface OperationDialogBoolInput { type: "bool"; label: I18nText; initValue?: boolean; } -export interface OperationSelectInputInfoOption { +export interface OperationDialogSelectInputOption { value: string; label: I18nText; icon?: React.ReactElement; } -export interface OperationSelectInputInfo { +export interface OperationDialogSelectInput { type: "select"; label: I18nText; - options: OperationSelectInputInfoOption[]; + options: OperationDialogSelectInputOption[]; initValue?: string; } -export type OperationInputInfo = - | OperationTextInputInfo - | OperationBoolInputInfo - | OperationSelectInputInfo; +export interface OperationDialogColorInput { + type: "color"; + label?: I18nText; + initValue?: string | null; + disableAlpha?: boolean; + canBeNull?: boolean; +} -type MapOperationInputInfoValueType<T> = T extends OperationTextInputInfo +export type OperationDialogInput = + | OperationDialogTextInput + | OperationDialogBoolInput + | OperationDialogSelectInput + | OperationDialogColorInput; + +type MapOperationInputInfoValueType<T> = T extends OperationDialogTextInput ? string - : T extends OperationBoolInputInfo + : T extends OperationDialogBoolInput ? boolean - : T extends OperationSelectInputInfo + : T extends OperationDialogSelectInput ? string + : T extends OperationDialogColorInput + ? string | null : never; type MapOperationInputInfoValueTypeList< - Tuple extends readonly OperationInputInfo[] + Tuple extends readonly OperationDialogInput[] > = { [Index in keyof Tuple]: MapOperationInputInfoValueType<Tuple[Index]>; } & { length: Tuple["length"] }; @@ -94,7 +106,7 @@ const isNoError = (error: OperationInputError): boolean => { export interface OperationDialogProps< TData, - OperationInputInfoList extends readonly OperationInputInfo[] + OperationInputInfoList extends readonly OperationDialogInput[] > { open: boolean; close: () => void; @@ -116,18 +128,18 @@ export interface OperationDialogProps< const OperationDialog = < TData, - OperationInputInfoList extends readonly OperationInputInfo[] + OperationInputInfoList extends readonly OperationDialogInput[] >( props: OperationDialogProps<TData, OperationInputInfoList> ): React.ReactElement => { const inputScheme = (props.inputScheme ?? - []) as readonly OperationInputInfo[]; + []) as readonly OperationDialogInput[]; const { t } = useTranslation(); type Step = - | "input" - | "process" + | { type: "input" } + | { type: "process" } | { type: "success"; data: TData; @@ -136,14 +148,20 @@ const OperationDialog = < type: "failure"; data: unknown; }; - const [step, setStep] = useState<Step>("input"); - const [values, setValues] = useState<(boolean | string)[]>( + const [step, setStep] = useState<Step>({ type: "input" }); + + type ValueType = boolean | string | null | undefined; + + const [values, setValues] = useState<ValueType[]>( inputScheme.map((i) => { if (i.type === "bool") { return i.initValue ?? false; } else if (i.type === "text" || i.type === "select") { return i.initValue ?? ""; - } else { + } else if (i.type === "color") { + return i.initValue ?? null; + } + { throw new UiLogicError("Unknown input scheme."); } }) @@ -154,13 +172,9 @@ const OperationDialog = < const [inputError, setInputError] = useState<OperationInputError>(); const close = (): void => { - if (step !== "process") { + if (step.type !== "process") { props.close(); - if ( - typeof step === "object" && - step.type === "success" && - props.onSuccessAndClose - ) { + if (step.type === "success" && props.onSuccessAndClose) { props.onSuccessAndClose(step.data); } } else { @@ -169,7 +183,7 @@ const OperationDialog = < }; const onConfirm = (): void => { - setStep("process"); + setStep({ type: "process" }); props .onProcess( (values as unknown) as MapOperationInputInfoValueTypeList<OperationInputInfoList> @@ -191,8 +205,8 @@ const OperationDialog = < }; let body: React.ReactNode; - if (step === "input" || step === "process") { - const process = step === "process"; + if (step.type === "input" || step.type === "process") { + const process = step.type === "process"; let inputPrompt = typeof props.inputPrompt === "function" @@ -200,7 +214,7 @@ const OperationDialog = < : convertI18nText(props.inputPrompt, t); inputPrompt = <h6>{inputPrompt}</h6>; - const validate = (values: (string | boolean)[]): boolean => { + const validate = (values: ValueType[]): boolean => { const { inputValidator } = props; if (inputValidator != null) { const result = inputValidator( @@ -212,7 +226,7 @@ const OperationDialog = < return true; }; - const updateValue = (index: number, newValue: string | boolean): void => { + const updateValue = (index: number, newValue: ValueType): void => { const oldValues = values; const newValues = oldValues.slice(); newValues[index] = newValue; @@ -301,6 +315,35 @@ const OperationDialog = < </Form.Control> </Form.Group> ); + } else if (item.type === "color") { + return ( + <Form.Group key={index}> + {item.canBeNull ? ( + <Form.Check<"input"> + type="checkbox" + checked={value !== null} + onChange={(event) => { + if (event.currentTarget.checked) { + updateValue(index, "#007bff"); + } else { + updateValue(index, null); + } + }} + label={convertI18nText(item.label, t)} + disabled={process} + /> + ) : ( + <Form.Label>{convertI18nText(item.label, t)}</Form.Label> + )} + {value !== null && ( + <ChromePicker + color={value as string} + onChange={(result) => updateValue(index, result.hex)} + disableAlpha={item.disableAlpha} + /> + )} + </Form.Group> + ); } })} </Modal.Body> diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx index a5628a9a..c65e097d 100644 --- a/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx +++ b/FrontEnd/src/app/views/timeline-common/TimelinePropertyChangeDialog.tsx @@ -31,30 +31,39 @@ const TimelinePropertyChangeDialog: React.FC<TimelinePropertyChangeDialogProps> return ( <OperationDialog title={"timeline.dialogChangeProperty.title"} - inputScheme={[ - { - type: "text", - label: "timeline.dialogChangeProperty.titleField", - initValue: timeline.title, - }, - { - type: "select", - label: "timeline.dialogChangeProperty.visibility", - options: kTimelineVisibilities.map((v) => ({ - label: labelMap[v], - value: v, - })), - initValue: timeline.visibility, - }, - { - type: "text", - label: "timeline.dialogChangeProperty.description", - initValue: timeline.description, - }, - ]} + inputScheme={ + [ + { + type: "text", + label: "timeline.dialogChangeProperty.titleField", + initValue: timeline.title, + }, + { + type: "select", + label: "timeline.dialogChangeProperty.visibility", + options: kTimelineVisibilities.map((v) => ({ + label: labelMap[v], + value: v, + })), + initValue: timeline.visibility, + }, + { + type: "text", + label: "timeline.dialogChangeProperty.description", + initValue: timeline.description, + }, + { + type: "color", + label: "timeline.dialogChangeProperty.color", + initValue: timeline.color ?? null, + disableAlpha: true, + canBeNull: true, + }, + ] as const + } open={props.open} close={props.close} - onProcess={([newTitle, newVisibility, newDescription]) => { + onProcess={([newTitle, newVisibility, newDescription, newColor]) => { const req: HttpTimelinePatchRequest = {}; if (newTitle !== timeline.title) { req.title = newTitle; @@ -65,6 +74,10 @@ const TimelinePropertyChangeDialog: React.FC<TimelinePropertyChangeDialogProps> if (newDescription !== timeline.description) { req.description = newDescription; } + const nc = newColor ?? "#007bff"; + if (nc !== timeline.color) { + req.color = nc; + } return getHttpTimelineClient() .patchTimeline(timeline.name, req) .then(onChange); |