aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-07-29 01:22:38 +0800
committercrupest <crupest@outlook.com>2023-07-29 01:22:38 +0800
commit2d4a75a21a8a97db8017b56e321c56c7d70bc674 (patch)
tree4817a03160b530d7efb3dbcd230b00281ee0a303 /FrontEnd/src
parenta9dc6b16d6730d8d1dc1ea2fab8ab3830fe56ce4 (diff)
downloadtimeline-2d4a75a21a8a97db8017b56e321c56c7d70bc674.tar.gz
timeline-2d4a75a21a8a97db8017b56e321c56c7d70bc674.tar.bz2
timeline-2d4a75a21a8a97db8017b56e321c56c7d70bc674.zip
...
Diffstat (limited to 'FrontEnd/src')
-rw-r--r--FrontEnd/src/index.css16
-rw-r--r--FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx29
-rw-r--r--FrontEnd/src/pages/setting/ChangePasswordDialog.tsx79
-rw-r--r--FrontEnd/src/views/common/button/Button.css1
-rw-r--r--FrontEnd/src/views/common/dialog/Dialog.css11
-rw-r--r--FrontEnd/src/views/common/dialog/Dialog.tsx27
-rw-r--r--FrontEnd/src/views/common/dialog/OperationDialog.css29
-rw-r--r--FrontEnd/src/views/common/dialog/OperationDialog.tsx32
-rw-r--r--FrontEnd/src/views/common/input/InputGroup.css43
-rw-r--r--FrontEnd/src/views/common/input/InputGroup.tsx44
-rw-r--r--FrontEnd/src/views/common/theme.css2
11 files changed, 162 insertions, 151 deletions
diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css
index 49791c23..ee92520b 100644
--- a/FrontEnd/src/index.css
+++ b/FrontEnd/src/index.css
@@ -30,22 +30,6 @@ textarea:focus {
border-color: var(--cru-primary-color);
}
-input:not([type="checkbox"]):not([type="radio"]) {
- resize: none;
- outline: none;
- border: 1px solid;
- transition: all 0.5s;
- border-color: var(--cru-background-2-color);
-}
-
-input:hover:not([type="checkbox"]):not([type="radio"]) {
- border-color: var(--cru-primary-r2-color);
-}
-
-input:focus:not([type="checkbox"]):not([type="radio"]) {
- border-color: var(--cru-primary-color);
-}
-
.white-space-no-wrap {
white-space: nowrap;
}
diff --git a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
index 58bbac5f..5606ce94 100644
--- a/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangeNicknameDialog.tsx
@@ -1,6 +1,5 @@
import { getHttpUserClient } from "@/http/user";
-import { useUser } from "@/services/user";
-import * as React from "react";
+import { useUserLoggedIn } from "@/services/user";
import OperationDialog from "@/views/common/dialog/OperationDialog";
@@ -9,26 +8,28 @@ export interface ChangeNicknameDialogProps {
close: () => void;
}
-const ChangeNicknameDialog: React.FC<ChangeNicknameDialogProps> = (props) => {
- const user = useUser();
+export default function ChangeNicknameDialog(props: ChangeNicknameDialogProps) {
+ const { open, close } = props;
- if (user == null) return null;
+ const user = useUserLoggedIn();
return (
<OperationDialog
- open={props.open}
+ open={open}
title="settings.dialogChangeNickname.title"
- inputScheme={[
- { type: "text", label: "settings.dialogChangeNickname.inputLabel" },
+ inputs={[
+ {
+ key: "newNickname",
+ type: "text",
+ label: "settings.dialogChangeNickname.inputLabel",
+ },
]}
- onProcess={([newNickname]) => {
+ onProcess={({ newNickname }) => {
return getHttpUserClient().patch(user.username, {
- nickname: newNickname,
+ nickname: newNickname as string,
});
}}
- close={props.close}
+ close={close}
/>
);
-};
-
-export default ChangeNicknameDialog;
+}
diff --git a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
index 9ca95168..407f3051 100644
--- a/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
+++ b/FrontEnd/src/pages/setting/ChangePasswordDialog.tsx
@@ -3,7 +3,9 @@ import { useNavigate } from "react-router-dom";
import { userService } from "@/services/user";
-import OperationDialog from "@/views/common/dialog/OperationDialog";
+import OperationDialog, {
+ InputErrorDict,
+} from "@/views/common/dialog/OperationDialog";
interface ChangePasswordDialogProps {
open: boolean;
@@ -20,45 +22,56 @@ export function ChangePasswordDialog(props: ChangePasswordDialogProps) {
return (
<OperationDialog
open={open}
+ close={close}
title="settings.dialogChangePassword.title"
color="danger"
inputPrompt="settings.dialogChangePassword.prompt"
- inputScheme={[
- {
- type: "text",
- label: "settings.dialogChangePassword.inputOldPassword",
- password: true,
+ inputs={{
+ inputs: [
+ {
+ key: "oldPassword",
+ type: "text",
+ label: "settings.dialogChangePassword.inputOldPassword",
+ password: true,
+ },
+ {
+ key: "newPassword",
+ type: "text",
+ label: "settings.dialogChangePassword.inputNewPassword",
+ password: true,
+ },
+ {
+ key: "retypedNewPassword",
+ type: "text",
+ label: "settings.dialogChangePassword.inputRetypeNewPassword",
+ password: true,
+ },
+ ],
+ validator: ({ oldPassword, newPassword, retypedNewPassword }) => {
+ const result: InputErrorDict = {};
+ if (oldPassword === "") {
+ result["oldPassword"] =
+ "settings.dialogChangePassword.errorEmptyOldPassword";
+ }
+ if (newPassword === "") {
+ result["newPassword"] =
+ "settings.dialogChangePassword.errorEmptyNewPassword";
+ }
+ if (retypedNewPassword !== newPassword) {
+ result["retypedNewPassword"] =
+ "settings.dialogChangePassword.errorRetypeNotMatch";
+ }
+ return result;
},
- {
- type: "text",
- label: "settings.dialogChangePassword.inputNewPassword",
- password: true,
- },
- {
- type: "text",
- label: "settings.dialogChangePassword.inputRetypeNewPassword",
- password: true,
- },
- ]}
- inputValidator={([oldPassword, newPassword, retypedNewPassword]) => {
- const result: Record<number, string> = {};
- if (oldPassword === "") {
- result[0] = "settings.dialogChangePassword.errorEmptyOldPassword";
- }
- if (newPassword === "") {
- result[1] = "settings.dialogChangePassword.errorEmptyNewPassword";
- }
- if (retypedNewPassword !== newPassword) {
- result[2] = "settings.dialogChangePassword.errorRetypeNotMatch";
- }
- return result;
}}
- onProcess={async ([oldPassword, newPassword]) => {
- await userService.changePassword(oldPassword, newPassword);
+ onProcess={async ({ oldPassword, newPassword }) => {
+ await userService.changePassword(
+ oldPassword as string,
+ newPassword as string,
+ );
setRedirect(true);
}}
- close={() => {
- props.close();
+ onSuccessAndClose={() => {
if (redirect) {
navigate("/login");
}
diff --git a/FrontEnd/src/views/common/button/Button.css b/FrontEnd/src/views/common/button/Button.css
index 12c6903e..fe619f9d 100644
--- a/FrontEnd/src/views/common/button/Button.css
+++ b/FrontEnd/src/views/common/button/Button.css
@@ -5,6 +5,7 @@
border-radius: 0.2em;
border: 1px solid;
cursor: pointer;
+ background-color: var(--cru-surface-color);
}
.cru-button:not(.outline) {
diff --git a/FrontEnd/src/views/common/dialog/Dialog.css b/FrontEnd/src/views/common/dialog/Dialog.css
index 99e1a516..8f12614b 100644
--- a/FrontEnd/src/views/common/dialog/Dialog.css
+++ b/FrontEnd/src/views/common/dialog/Dialog.css
@@ -6,7 +6,6 @@
right: 0;
bottom: 0;
display: flex;
- padding: 2em;
overflow: auto;
}
@@ -14,8 +13,8 @@
position: absolute;
z-index: -1;
left: 0;
- top: 0;
right: 0;
+ top: 0;
bottom: 0;
background-color: var(--cru-surface-dim-color);
opacity: 0.8;
@@ -25,7 +24,7 @@
max-width: 100%;
min-width: 30vw;
- margin: auto;
+ margin: 2em auto;
border: var(--cru-key-container-color) 1px solid;
border-radius: 5px;
@@ -33,6 +32,12 @@
background-color: var(--cru-surface-color);
}
+@media (min-width: 576px) {
+ .cru-dialog-container {
+ max-width: 800px;
+ }
+}
+
.cru-dialog-bottom-area {
display: flex;
justify-content: flex-end;
diff --git a/FrontEnd/src/views/common/dialog/Dialog.tsx b/FrontEnd/src/views/common/dialog/Dialog.tsx
index 31dd113b..9ce344dc 100644
--- a/FrontEnd/src/views/common/dialog/Dialog.tsx
+++ b/FrontEnd/src/views/common/dialog/Dialog.tsx
@@ -38,23 +38,18 @@ export default function Dialog({
timeout={300}
classNames="cru-dialog"
>
- <div
- className={classNames("cru-dialog-overlay", `cru-${color}`)}
- onPointerDown={
- disableCloseOnClickOnOverlay
- ? undefined
- : () => {
- onClose();
- }
- }
- >
- <div className="cru-dialog-background" />
+ <div className={classNames("cru-dialog-overlay", `cru-${color}`)}>
<div
- className="cru-dialog-container"
- onPointerDown={(e) => e.stopPropagation()}
- >
- {children}
- </div>
+ className="cru-dialog-background"
+ onClick={
+ disableCloseOnClickOnOverlay
+ ? undefined
+ : () => {
+ onClose();
+ }
+ }
+ />
+ <div className="cru-dialog-container">{children}</div>
</div>
</CSSTransition>,
portalElement,
diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.css b/FrontEnd/src/views/common/dialog/OperationDialog.css
index 19c5d806..43cdb692 100644
--- a/FrontEnd/src/views/common/dialog/OperationDialog.css
+++ b/FrontEnd/src/views/common/dialog/OperationDialog.css
@@ -9,32 +9,15 @@
color: var(--cru-surface-on-color);
}
-.cru-operation-dialog-main-area {
- margin-top: 0.5em;
-}
-
-.cru-operation-dialog-group {
- display: block;
- margin: 0.4em 0;
-}
-
-.cru-operation-dialog-label {
- display: block;
- color: var(--cru-primary-color);
+.cru-dialog-middle-area {
+ margin: 0.5em 0;
}
-.cru-operation-dialog-inline-label {
- margin-inline-start: 0.5em;
+.cru-dialog-bottom-area {
+ margin-top: 0.5em;
}
-.cru-operation-dialog-error-text {
+.cru-operation-dialog-input-group {
display: block;
- font-size: 0.8em;
- color: var(--cru-danger-color);
+ margin: 0.5em 0;
}
-
-.cru-operation-dialog-helper-text {
- display: block;
- font-size: 0.8em;
- color: var(--cru-primary-color);
-} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/dialog/OperationDialog.tsx b/FrontEnd/src/views/common/dialog/OperationDialog.tsx
index 97d135e9..8aab45d9 100644
--- a/FrontEnd/src/views/common/dialog/OperationDialog.tsx
+++ b/FrontEnd/src/views/common/dialog/OperationDialog.tsx
@@ -7,15 +7,17 @@ import Button from "../button/Button";
import {
useInputs,
InputGroup,
- InitializeInfo as InputInitializer,
+ Initializer as InputInitializer,
InputValueDict,
- InputScheme,
+ InputErrorDict,
} from "../input/InputGroup";
import LoadingButton from "../button/LoadingButton";
import Dialog from "./Dialog";
import "./OperationDialog.css";
+export type { InputInitializer, InputValueDict, InputErrorDict };
+
interface OperationDialogPromptProps {
message?: Text;
customMessage?: ReactNode;
@@ -40,13 +42,13 @@ export interface OperationDialogProps<TData> {
close: () => void;
color?: ThemeColor;
+ inputColor?: ThemeColor;
title: Text;
inputPrompt?: Text;
successPrompt?: (data: TData) => ReactNode;
failurePrompt?: (error: unknown) => ReactNode;
- inputInit?: InputInitializer;
- inputScheme?: InputScheme;
+ inputs: InputInitializer;
onProcess: (inputs: InputValueDict) => Promise<TData>;
onSuccessAndClose?: (data: TData) => void;
@@ -57,25 +59,16 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
open,
close,
color,
+ inputColor,
title,
inputPrompt,
successPrompt,
failurePrompt,
- inputInit,
- inputScheme,
+ inputs,
onProcess,
onSuccessAndClose,
} = props;
- if (process.env.NODE_ENV === "development") {
- if (inputScheme == null && inputInit == null) {
- throw Error("Scheme or Init? Choose one and create one.");
- }
- if (inputScheme != null && inputInit != null) {
- throw Error("Scheme or Init? Choose one and drop one");
- }
- }
-
const c = useC();
type Step =
@@ -93,14 +86,13 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
const [step, setStep] = useState<Step>({ type: "input" });
const { inputGroupProps, hasError, setAllDisabled, confirm } = useInputs({
- /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
- init: inputInit ?? { scheme: inputScheme! },
+ init: inputs,
});
function onClose() {
if (step.type !== "process") {
close();
- if (step.type === "success" && props.onSuccessAndClose) {
+ if (step.type === "success" && onSuccessAndClose) {
onSuccessAndClose?.(step.data);
}
} else {
@@ -136,11 +128,11 @@ function OperationDialog<TData>(props: OperationDialogProps<TData>) {
body = (
<div className="cru-operation-dialog-main-area">
- <div>
+ <div className="cru-dialog-middle-area">
<OperationDialogPrompt customMessage={c(inputPrompt)} />
<InputGroup
containerClassName="cru-operation-dialog-input-group"
- color={color}
+ color={inputColor ?? "primary"}
{...inputGroupProps}
/>
</div>
diff --git a/FrontEnd/src/views/common/input/InputGroup.css b/FrontEnd/src/views/common/input/InputGroup.css
index f9d6ac8b..1763ea53 100644
--- a/FrontEnd/src/views/common/input/InputGroup.css
+++ b/FrontEnd/src/views/common/input/InputGroup.css
@@ -1,25 +1,54 @@
-.cru-input-panel-group {
+.cru-input-group {
display: block;
+}
+
+.cru-input-container {
margin: 0.4em 0;
}
-.cru-input-panel-label {
+.cru-input-label {
display: block;
- color: var(--cru-primary-color);
+ color: var(--cru-key-color);
+ font-size: 0.9em;
+ margin-bottom: 0.3em;
}
-.cru-input-panel-inline-label {
+.cru-input-label-inline {
margin-inline-start: 0.5em;
}
-.cru-input-panel-error-text {
+.cru-input-type-text input {
+ appearance: none;
+ display: block;
+ border: 1px solid var(--cru-surface-outline-color);
+ color: var(--cru-surface-on-color);
+ background-color: var(--cru-surface-color);
+ margin: 0;
+ padding: 0;
+ font-size: 1.2em;
+}
+
+.cru-input-type-text input:hover {
+ border-color: var(--cru-key-color);
+}
+
+.cru-input-type-text input:focus {
+ border-color: var(--cru-key-color);
+}
+
+.cru-input-type-text input:disabled {
+ border-color: var(--cru-surface-on-color);
+}
+
+.cru-input-error {
display: block;
font-size: 0.8em;
color: var(--cru-danger-color);
+ margin-top: 0.4em;
}
-.cru-input-panel-helper-text {
+.cru-input-helper {
display: block;
font-size: 0.8em;
color: var(--cru-primary-color);
-}
+} \ No newline at end of file
diff --git a/FrontEnd/src/views/common/input/InputGroup.tsx b/FrontEnd/src/views/common/input/InputGroup.tsx
index 232edfc9..eed8266b 100644
--- a/FrontEnd/src/views/common/input/InputGroup.tsx
+++ b/FrontEnd/src/views/common/input/InputGroup.tsx
@@ -98,14 +98,16 @@ export type State = {
data: InputData;
};
-export type DataInitializeInfo = Partial<InputData>;
+export type DataInitialization = Partial<InputData>;
-export type InitializeInfo = {
+export type Initialization = {
scheme: InputScheme;
- dataInit?: DataInitializeInfo;
+ dataInit?: DataInitialization;
};
-export type Initialize
+export type GeneralInitialization = Initialization | InputScheme | InputInfo[];
+
+export type Initializer = GeneralInitialization | (() => GeneralInitialization);
export interface InputGroupProps {
color?: ThemeColor;
@@ -136,9 +138,7 @@ export type ConfirmResult =
errors: InputErrorDict;
};
-export function useInputs(options: {
- init: InitializeInfo | (() => InitializeInfo);
-}): {
+export function useInputs(options: { init: Initializer }): {
inputGroupProps: InputGroupProps;
hasError: boolean;
confirm: () => ConfirmResult;
@@ -158,8 +158,14 @@ export function useInputs(options: {
throw new Error("Unknown input type");
}
- function initialize(info: InitializeInfo): State {
- const { scheme, dataInit } = info;
+ function initialize(generalInitialization: GeneralInitialization): State {
+ const initialization: Initialization = Array.isArray(generalInitialization)
+ ? { scheme: { inputs: generalInitialization } }
+ : "scheme" in generalInitialization
+ ? generalInitialization
+ : { scheme: generalInitialization };
+
+ const { scheme, dataInit } = initialization;
const { inputs, validator } = scheme;
const keys = inputs.map((input) => input.key);
@@ -185,8 +191,8 @@ export function useInputs(options: {
}
const values: InputValueDict = {};
- const disabled: InputDisabledDict = clean(info.dataInit?.disabled);
- const dirties: InputDirtyDict = clean(info.dataInit?.dirties);
+ const disabled: InputDisabledDict = clean(dataInit?.disabled);
+ const dirties: InputDirtyDict = clean(dataInit?.dirties);
for (let i = 0; i < inputs.length; i++) {
const input = inputs[i];
@@ -195,7 +201,7 @@ export function useInputs(options: {
values[key] = initializeValue(input, dataInit?.values?.[key]);
}
- let errors = info.dataInit?.errors;
+ let errors = dataInit?.errors;
if (errors != null) {
if (process.env.NODE_ENV === "development") {
@@ -331,13 +337,13 @@ export function InputGroup({
)}
>
{inputs.map((item, index) => {
- const { type, value, label, error, helper, disabled } = item;
+ const { key, type, value, label, error, helper, disabled } = item;
const getContainerClassName = (
...additionalClassNames: classNames.ArgumentArray
) =>
classNames(
- `cru-input-container cru-input-${type}`,
+ `cru-input-container cru-input-type-${type}`,
error && "error",
...additionalClassNames,
);
@@ -350,7 +356,7 @@ export function InputGroup({
const { password } = item;
return (
<div
- key={index}
+ key={key}
className={getContainerClassName(password && "password")}
>
{label && <label className="cru-input-label">{c(label)}</label>}
@@ -369,7 +375,7 @@ export function InputGroup({
);
} else if (type === "bool") {
return (
- <div key={index} className={getContainerClassName()}>
+ <div key={key} className={getContainerClassName()}>
<input
type="checkbox"
checked={value}
@@ -386,7 +392,7 @@ export function InputGroup({
);
} else if (type === "select") {
return (
- <div key={index} className={getContainerClassName()}>
+ <div key={key} className={getContainerClassName()}>
<label className="cru-input-label">{c(label)}</label>
<select
value={value}
@@ -396,9 +402,9 @@ export function InputGroup({
}}
disabled={disabled}
>
- {item.options.map((option, i) => {
+ {item.options.map((option) => {
return (
- <option value={option.value} key={i}>
+ <option value={option.value} key={option.value}>
{option.icon}
{c(option.label)}
</option>
diff --git a/FrontEnd/src/views/common/theme.css b/FrontEnd/src/views/common/theme.css
index 6478ed7a..1aa56a8c 100644
--- a/FrontEnd/src/views/common/theme.css
+++ b/FrontEnd/src/views/common/theme.css
@@ -3,6 +3,8 @@
:root {
--cru-default-font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
--cru-page-padding: 1em 2em;
+
+ --cru-border-radius: 4px;
--cru-card-border-radius: 4px;
--cru-secondary-text-color: var(--cru-surface-on-color);