aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/views/common
diff options
context:
space:
mode:
authorcrupest <crupest@outlook.com>2023-08-19 02:13:26 +0800
committercrupest <crupest@outlook.com>2023-08-19 02:13:26 +0800
commitd6c1c9f2c9eddd7d6e4e91b2a9de71cfd9db6b73 (patch)
tree4b546fe67049a8211b3265a5d3316ae3947ac6e7 /FrontEnd/src/views/common
parenteec2e74a928f6448a0503e003d8afa693730b365 (diff)
downloadtimeline-d6c1c9f2c9eddd7d6e4e91b2a9de71cfd9db6b73.tar.gz
timeline-d6c1c9f2c9eddd7d6e4e91b2a9de71cfd9db6b73.tar.bz2
timeline-d6c1c9f2c9eddd7d6e4e91b2a9de71cfd9db6b73.zip
...
Diffstat (limited to 'FrontEnd/src/views/common')
-rw-r--r--FrontEnd/src/views/common/BlobImage.tsx33
-rw-r--r--FrontEnd/src/views/common/ImageCropper.tsx30
-rw-r--r--FrontEnd/src/views/common/button/ButtonRowV2.tsx143
-rw-r--r--FrontEnd/src/views/common/button/IconButton.tsx1
-rw-r--r--FrontEnd/src/views/common/button/index.tsx10
-rw-r--r--FrontEnd/src/views/common/dialog/DialogContainer.tsx71
6 files changed, 236 insertions, 52 deletions
diff --git a/FrontEnd/src/views/common/BlobImage.tsx b/FrontEnd/src/views/common/BlobImage.tsx
index 5e050ebe..259c2210 100644
--- a/FrontEnd/src/views/common/BlobImage.tsx
+++ b/FrontEnd/src/views/common/BlobImage.tsx
@@ -1,27 +1,26 @@
-import * as React from "react";
+import { ComponentPropsWithoutRef, useState, useEffect } from "react";
-const BlobImage: React.FC<
- Omit<React.ImgHTMLAttributes<HTMLImageElement>, "src"> & {
- blob?: Blob | unknown;
- }
-> = (props) => {
- const { blob, ...otherProps } = props;
+type BlobImageProps = Omit<ComponentPropsWithoutRef<"img">, "src"> & {
+ imgRef?: React.Ref<HTMLImageElement>;
+ src?: Blob | string | null;
+};
+
+export default function BlobImage(props: BlobImageProps) {
+ const { imgRef, src, ...otherProps } = props;
- const [url, setUrl] = React.useState<string | undefined>(undefined);
+ const [url, setUrl] = useState<string | null | undefined>(undefined);
- React.useEffect(() => {
- if (blob instanceof Blob) {
- const url = URL.createObjectURL(blob);
+ useEffect(() => {
+ if (src instanceof Blob) {
+ const url = URL.createObjectURL(src);
setUrl(url);
return () => {
URL.revokeObjectURL(url);
};
} else {
- setUrl(undefined);
+ setUrl(src);
}
- }, [blob]);
-
- return <img {...otherProps} src={url} />;
-};
+ }, [src]);
-export default BlobImage;
+ return <img ref={imgRef} {...otherProps} src={url ?? undefined} />;
+}
diff --git a/FrontEnd/src/views/common/ImageCropper.tsx b/FrontEnd/src/views/common/ImageCropper.tsx
index 04e17415..fcab74b0 100644
--- a/FrontEnd/src/views/common/ImageCropper.tsx
+++ b/FrontEnd/src/views/common/ImageCropper.tsx
@@ -4,6 +4,7 @@ import classnames from "classnames";
import { UiLogicError } from "@/common";
import "./ImageCropper.css";
+import BlobImage from "./BlobImage";
export interface Clip {
left: number;
@@ -33,17 +34,17 @@ interface ImageCropperSavedState {
export interface ImageCropperProps {
clip: Clip | null;
- imageUrl: string;
+ image: string | Blob;
onChange: (clip: Clip) => void;
imageElementCallback?: (element: HTMLImageElement | null) => void;
className?: string;
}
const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
- const { clip, imageUrl, onChange, imageElementCallback, className } = props;
+ const { clip, image, onChange, imageElementCallback, className } = props;
const [oldState, setOldState] = React.useState<ImageCropperSavedState | null>(
- null
+ null,
);
const [imageInfo, setImageInfo] = React.useState<ImageInfo | null>(null);
@@ -71,7 +72,7 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
imageElementCallback(null);
}
},
- [imageElementCallback]
+ [imageElementCallback],
);
const onImageLoad = React.useCallback(
@@ -93,7 +94,7 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
imageElementCallback(img);
}
},
- [onChange, imageElementCallback]
+ [onChange, imageElementCallback],
);
const onPointerDown = React.useCallback(
@@ -107,7 +108,7 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
pointerId: e.pointerId,
});
},
- [oldState, c]
+ [oldState, c],
);
const onPointerUp = React.useCallback(
@@ -116,7 +117,7 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
e.currentTarget.releasePointerCapture(e.pointerId);
setOldState(null);
},
- [oldState]
+ [oldState],
);
const onPointerMove = React.useCallback(
@@ -153,7 +154,7 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
onChange({ left: newRatio.x, top: newRatio.y, width: oldClip.width });
},
- [oldState, onChange]
+ [oldState, onChange],
);
const onHandlerPointerMove = React.useCallback(
@@ -198,7 +199,7 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
onChange({ left: oldClip.left, top: oldClip.top, width: newWidth });
},
- [imageInfo, oldState, onChange]
+ [imageInfo, oldState, onChange],
);
const toPercentage = (n: number): string => `${n}%`;
@@ -229,7 +230,12 @@ const ImageCropper = (props: ImageCropperProps): React.ReactElement => {
className={classnames("image-cropper-container", className)}
style={containerStyle}
>
- <img ref={onImageRef} src={imageUrl} onLoad={onImageLoad} alt="to crop" />
+ <BlobImage
+ imgRef={onImageRef}
+ src={image}
+ onLoad={onImageLoad}
+ alt="to crop"
+ />
<div className="image-cropper-mask-container">
<div
className="image-cropper-mask"
@@ -263,7 +269,7 @@ export default ImageCropper;
export function applyClipToImage(
image: HTMLImageElement,
clip: Clip,
- mimeType: string
+ mimeType: string,
): Promise<Blob> {
return new Promise((resolve, reject) => {
const naturalSize = {
@@ -292,7 +298,7 @@ export function applyClipToImage(
0,
0,
clipArea.length,
- clipArea.length
+ clipArea.length,
);
canvas.toBlob((blob) => {
diff --git a/FrontEnd/src/views/common/button/ButtonRowV2.tsx b/FrontEnd/src/views/common/button/ButtonRowV2.tsx
new file mode 100644
index 00000000..3467ad52
--- /dev/null
+++ b/FrontEnd/src/views/common/button/ButtonRowV2.tsx
@@ -0,0 +1,143 @@
+import { ComponentPropsWithoutRef, Ref } from "react";
+import classNames from "classnames";
+
+import Button from "./Button";
+import FlatButton from "./FlatButton";
+import IconButton from "./IconButton";
+import LoadingButton from "./LoadingButton";
+
+import "./ButtonRow.css";
+import { Text, ThemeColor } from "../common";
+
+interface ButtonRowV2ButtonBase {
+ key: string | number;
+ action?: "primary" | "secondary";
+ color?: ThemeColor;
+ disabled?: boolean;
+ onClick?: () => void;
+}
+
+interface ButtonRowV2ButtonWithNoType extends ButtonRowV2ButtonBase {
+ type?: undefined | null;
+ text: Text;
+ outline?: boolean;
+ props?: ComponentPropsWithoutRef<typeof Button>;
+}
+
+interface ButtonRowV2NormalButton extends ButtonRowV2ButtonBase {
+ type: "normal";
+ text: Text;
+ outline?: boolean;
+ props?: ComponentPropsWithoutRef<typeof Button>;
+}
+
+interface ButtonRowV2FlatButton extends ButtonRowV2ButtonBase {
+ type: "flat";
+ text: Text;
+ props?: ComponentPropsWithoutRef<typeof FlatButton>;
+}
+
+interface ButtonRowV2IconButton extends ButtonRowV2ButtonBase {
+ type: "icon";
+ icon: string;
+ props?: ComponentPropsWithoutRef<typeof IconButton>;
+}
+
+interface ButtonRowV2LoadingButton extends ButtonRowV2ButtonBase {
+ type: "loading";
+ text: Text;
+ loading?: boolean;
+ props?: ComponentPropsWithoutRef<typeof LoadingButton>;
+}
+
+type ButtonRowV2Button =
+ | ButtonRowV2ButtonWithNoType
+ | ButtonRowV2NormalButton
+ | ButtonRowV2FlatButton
+ | ButtonRowV2IconButton
+ | ButtonRowV2LoadingButton;
+
+interface ButtonRowV2Props {
+ className?: string;
+ containerRef?: Ref<HTMLDivElement>;
+ buttons: ButtonRowV2Button[];
+ buttonsClassName?: string;
+}
+
+export default function ButtonRowV2({
+ className,
+ containerRef,
+ buttons,
+ buttonsClassName,
+}: ButtonRowV2Props) {
+ return (
+ <div ref={containerRef} className={classNames("cru-button-row", className)}>
+ {buttons.map((button) => {
+ const { key, action, color, disabled, onClick } = button;
+
+ const realAction = action ?? "primary";
+ const realColor =
+ color ?? (realAction === "primary" ? "primary" : "secondary");
+
+ const commonProps = { key, color: realColor, disabled, onClick };
+ const newClassName = classNames(
+ button.props?.className,
+ buttonsClassName,
+ );
+
+ switch (button.type) {
+ case null:
+ case undefined:
+ case "normal": {
+ const { text, outline, props } = button;
+ return (
+ <Button
+ {...commonProps}
+ text={text}
+ outline={outline ?? realAction !== "primary"}
+ {...props}
+ className={newClassName}
+ />
+ );
+ }
+ case "flat": {
+ const { text, props } = button;
+ return (
+ <FlatButton
+ {...commonProps}
+ text={text}
+ {...props}
+ className={newClassName}
+ />
+ );
+ }
+ case "icon": {
+ const { icon, props } = button;
+ return (
+ <IconButton
+ {...commonProps}
+ icon={icon}
+ {...props}
+ className={newClassName}
+ />
+ );
+ }
+ case "loading": {
+ const { text, loading, props } = button;
+ return (
+ <LoadingButton
+ {...commonProps}
+ text={text}
+ loading={loading}
+ {...props}
+ className={newClassName}
+ />
+ );
+ }
+ default:
+ throw new Error();
+ }
+ })}
+ </div>
+ );
+}
diff --git a/FrontEnd/src/views/common/button/IconButton.tsx b/FrontEnd/src/views/common/button/IconButton.tsx
index 60050e0d..126f4263 100644
--- a/FrontEnd/src/views/common/button/IconButton.tsx
+++ b/FrontEnd/src/views/common/button/IconButton.tsx
@@ -9,6 +9,7 @@ interface IconButtonProps extends ComponentPropsWithoutRef<"i"> {
icon: string;
color?: ThemeColor;
large?: boolean;
+ disabled?: boolean; // TODO: Not implemented
}
export default function IconButton(props: IconButtonProps) {
diff --git a/FrontEnd/src/views/common/button/index.tsx b/FrontEnd/src/views/common/button/index.tsx
index 73038849..b5aa5470 100644
--- a/FrontEnd/src/views/common/button/index.tsx
+++ b/FrontEnd/src/views/common/button/index.tsx
@@ -3,5 +3,13 @@ import FlatButton from "./FlatButton";
import IconButton from "./IconButton";
import LoadingButton from "./LoadingButton";
import ButtonRow from "./ButtonRow";
+import ButtonRowV2 from "./ButtonRowV2";
-export { Button, FlatButton, IconButton, LoadingButton, ButtonRow };
+export {
+ Button,
+ FlatButton,
+ IconButton,
+ LoadingButton,
+ ButtonRow,
+ ButtonRowV2,
+};
diff --git a/FrontEnd/src/views/common/dialog/DialogContainer.tsx b/FrontEnd/src/views/common/dialog/DialogContainer.tsx
index b0a87ea5..afee2669 100644
--- a/FrontEnd/src/views/common/dialog/DialogContainer.tsx
+++ b/FrontEnd/src/views/common/dialog/DialogContainer.tsx
@@ -2,11 +2,11 @@ import { ComponentProps, Ref, ReactNode } from "react";
import classNames from "classnames";
import { ThemeColor, Text, useC } from "../common";
-import { ButtonRow } from "../button";
+import { ButtonRow, ButtonRowV2 } from "../button";
import "./DialogContainer.css";
-interface DialogContainerProps {
+interface DialogContainerBaseProps {
className?: string;
title: Text;
titleColor?: ThemeColor;
@@ -14,25 +14,37 @@ interface DialogContainerProps {
titleRef?: Ref<HTMLDivElement>;
bodyContainerClassName?: string;
bodyContainerRef?: Ref<HTMLDivElement>;
- buttons: ComponentProps<typeof ButtonRow>["buttons"];
buttonsClassName?: string;
buttonsContainerRef?: ComponentProps<typeof ButtonRow>["containerRef"];
children: ReactNode;
}
-export default function DialogContainer({
- className,
- title,
- titleColor,
- titleClassName,
- titleRef,
- bodyContainerClassName,
- bodyContainerRef,
- buttons,
- buttonsClassName,
- buttonsContainerRef,
- children,
-}: DialogContainerProps) {
+interface DialogContainerWithButtonsProps extends DialogContainerBaseProps {
+ buttons: ComponentProps<typeof ButtonRow>["buttons"];
+}
+
+interface DialogContainerWithButtonsV2Props extends DialogContainerBaseProps {
+ buttonsV2: ComponentProps<typeof ButtonRowV2>["buttons"];
+}
+
+type DialogContainerProps =
+ | DialogContainerWithButtonsProps
+ | DialogContainerWithButtonsV2Props;
+
+export default function DialogContainer(props: DialogContainerProps) {
+ const {
+ className,
+ title,
+ titleColor,
+ titleClassName,
+ titleRef,
+ bodyContainerClassName,
+ bodyContainerRef,
+ buttonsClassName,
+ buttonsContainerRef,
+ children,
+ } = props;
+
const c = useC();
return (
@@ -57,12 +69,27 @@ export default function DialogContainer({
{children}
</div>
<hr className="cru-dialog-container-hr" />
- <ButtonRow
- containerRef={buttonsContainerRef}
- className={classNames("cru-dialog-container-button-row", buttonsClassName)}
- buttons={buttons}
- buttonsClassName="cru-dialog-container-button"
- />
+ {"buttons" in props ? (
+ <ButtonRow
+ containerRef={buttonsContainerRef}
+ className={classNames(
+ "cru-dialog-container-button-row",
+ buttonsClassName,
+ )}
+ buttons={props.buttons}
+ buttonsClassName="cru-dialog-container-button"
+ />
+ ) : (
+ <ButtonRowV2
+ containerRef={buttonsContainerRef}
+ className={classNames(
+ "cru-dialog-container-button-row",
+ buttonsClassName,
+ )}
+ buttons={props.buttonsV2}
+ buttonsClassName="cru-dialog-container-button"
+ />
+ )}
</div>
);
}