aboutsummaryrefslogtreecommitdiff
path: root/FrontEnd/src/app/views
diff options
context:
space:
mode:
Diffstat (limited to 'FrontEnd/src/app/views')
-rw-r--r--FrontEnd/src/app/views/common/OperationDialog.tsx55
-rw-r--r--FrontEnd/src/app/views/timeline-common/PostPropertyChangeDialog.tsx36
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx1
-rw-r--r--FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx46
4 files changed, 119 insertions, 19 deletions
diff --git a/FrontEnd/src/app/views/common/OperationDialog.tsx b/FrontEnd/src/app/views/common/OperationDialog.tsx
index 40c14e9e..0ede42e5 100644
--- a/FrontEnd/src/app/views/common/OperationDialog.tsx
+++ b/FrontEnd/src/app/views/common/OperationDialog.tsx
@@ -66,11 +66,18 @@ export interface OperationDialogColorInput {
canBeNull?: boolean;
}
+export interface OperationDialogDateTimeInput {
+ type: "datetime";
+ label?: I18nText;
+ initValue?: string;
+}
+
export type OperationDialogInput =
| OperationDialogTextInput
| OperationDialogBoolInput
| OperationDialogSelectInput
- | OperationDialogColorInput;
+ | OperationDialogColorInput
+ | OperationDialogDateTimeInput;
type MapOperationInputInfoValueType<T> = T extends OperationDialogTextInput
? string
@@ -80,8 +87,20 @@ type MapOperationInputInfoValueType<T> = T extends OperationDialogTextInput
? string
: T extends OperationDialogColorInput
? string | null
+ : T extends OperationDialogDateTimeInput
+ ? string
: never;
+const defaultValueMap: {
+ [T in OperationDialogInput as T["type"]]: MapOperationInputInfoValueType<T>;
+} = {
+ bool: false,
+ color: null,
+ datetime: "",
+ select: "",
+ text: "",
+};
+
type MapOperationInputInfoValueTypeList<
Tuple extends readonly OperationDialogInput[]
> = {
@@ -153,14 +172,9 @@ const OperationDialog = <
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 if (i.type === "color") {
- return i.initValue ?? null;
- }
- {
+ if (i.type in defaultValueMap) {
+ return i.initValue ?? defaultValueMap[i.type];
+ } else {
throw new UiLogicError("Unknown input scheme.");
}
})
@@ -342,6 +356,29 @@ const OperationDialog = <
)}
</Form.Group>
);
+ } else if (item.type === "datetime") {
+ return (
+ <Form.Group key={index}>
+ {item.label && (
+ <Form.Label>{convertI18nText(item.label, t)}</Form.Label>
+ )}
+ <Form.Control
+ type="datetime-local"
+ value={value as string}
+ onChange={(e) => {
+ const v = e.target.value;
+ updateValue(index, v);
+ }}
+ isInvalid={error != null}
+ disabled={process}
+ />
+ {error != null && (
+ <Form.Control.Feedback type="invalid">
+ {error}
+ </Form.Control.Feedback>
+ )}
+ </Form.Group>
+ );
}
})}
</Modal.Body>
diff --git a/FrontEnd/src/app/views/timeline-common/PostPropertyChangeDialog.tsx b/FrontEnd/src/app/views/timeline-common/PostPropertyChangeDialog.tsx
new file mode 100644
index 00000000..001e52d7
--- /dev/null
+++ b/FrontEnd/src/app/views/timeline-common/PostPropertyChangeDialog.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+
+import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
+
+import OperationDialog from "../common/OperationDialog";
+
+function PostPropertyChangeDialog(props: {
+ onClose: () => void;
+ post: HttpTimelinePostInfo;
+ onSuccess: (post: HttpTimelinePostInfo) => void;
+}): React.ReactElement | null {
+ const { onClose, post, onSuccess } = props;
+
+ return (
+ <OperationDialog
+ title="timeline.changePostPropertyDialog.title"
+ close={onClose}
+ open
+ inputScheme={[
+ {
+ label: "timeline.changePostPropertyDialog.time",
+ type: "datetime",
+ initValue: post.time,
+ },
+ ]}
+ onProcess={([time]) => {
+ return getHttpTimelineClient().patchPost(post.timelineName, post.id, {
+ time: time === "" ? undefined : new Date(time).toISOString(),
+ });
+ }}
+ onSuccessAndClose={onSuccess}
+ />
+ );
+}
+
+export default PostPropertyChangeDialog;
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx
index d9c45a4c..ba204b72 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostListView.tsx
@@ -64,6 +64,7 @@ const TimelinePostListView: React.FC<TimelinePostListViewProps> = (props) => {
key={post.id}
post={post}
current={posts.length - 1 === post.index}
+ onChanged={onReload}
onDeleted={onReload}
/>
);
diff --git a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
index 2f778ab1..a008d8e5 100644
--- a/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
+++ b/FrontEnd/src/app/views/timeline-common/TimelinePostView.tsx
@@ -1,6 +1,7 @@
import React from "react";
import classnames from "classnames";
import { Link } from "react-router-dom";
+import { useTranslation } from "react-i18next";
import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline";
@@ -10,6 +11,7 @@ import UserAvatar from "../common/user/UserAvatar";
import TimelineLine from "./TimelineLine";
import TimelinePostContentView from "./TimelinePostContentView";
import TimelinePostDeleteConfirmDialog from "./TimelinePostDeleteConfirmDialog";
+import PostPropertyChangeDialog from "./PostPropertyChangeDialog";
export interface TimelinePostViewProps {
post: HttpTimelinePostInfo;
@@ -17,16 +19,20 @@ export interface TimelinePostViewProps {
className?: string;
style?: React.CSSProperties;
cardStyle?: React.CSSProperties;
- onDeleted?: () => void;
+ onChanged: (post: HttpTimelinePostInfo) => void;
+ onDeleted: () => void;
}
const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
- const { post, className, style, cardStyle, onDeleted } = props;
+ const { post, className, style, cardStyle, onChanged, onDeleted } = props;
const current = props.current === true;
+ const { t } = useTranslation();
+
const [operationMaskVisible, setOperationMaskVisible] =
React.useState<boolean>(false);
- const [deleteDialog, setDeleteDialog] = React.useState<boolean>(false);
+ const [dialog, setDialog] =
+ React.useState<"delete" | "changeproperty" | null>(null);
const cardRef = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
@@ -84,25 +90,36 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
</div>
{operationMaskVisible ? (
<div
- className="position-absolute position-lt w-100 h-100 mask d-flex justify-content-center align-items-center"
+ className="position-absolute position-lt w-100 h-100 mask d-flex justify-content-around align-items-center"
onClick={() => {
setOperationMaskVisible(false);
}}
>
- <i
- className="bi-trash text-danger icon-button large"
+ <span
+ className="tl-color-primary"
+ onClick={(e) => {
+ setDialog("changeproperty");
+ e.stopPropagation();
+ }}
+ >
+ {t("changeProperty")}
+ </span>
+ <span
+ className="tl-color-danger"
onClick={(e) => {
- setDeleteDialog(true);
+ setDialog("delete");
e.stopPropagation();
}}
- />
+ >
+ {t("delete")}
+ </span>
</div>
) : null}
</div>
- {deleteDialog ? (
+ {dialog === "delete" ? (
<TimelinePostDeleteConfirmDialog
onClose={() => {
- setDeleteDialog(false);
+ setDialog(null);
setOperationMaskVisible(false);
}}
onConfirm={() => {
@@ -116,6 +133,15 @@ const TimelinePostView: React.FC<TimelinePostViewProps> = (props) => {
});
}}
/>
+ ) : dialog === "changeproperty" ? (
+ <PostPropertyChangeDialog
+ onClose={() => {
+ setDialog(null);
+ setOperationMaskVisible(false);
+ }}
+ post={post}
+ onSuccess={onChanged}
+ />
) : null}
</div>
);