diff options
author | crupest <crupest@outlook.com> | 2021-07-01 17:54:04 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2021-07-01 17:54:04 +0800 |
commit | 88851e84f070207581f5dfa78a94e52194a2281b (patch) | |
tree | f9a0c2be1df48d7be842a8db66fb44b24d570bd7 /FrontEnd/src/views | |
parent | 672778ca5d9de5513c86d70394b2dd048639cdea (diff) | |
download | timeline-88851e84f070207581f5dfa78a94e52194a2281b.tar.gz timeline-88851e84f070207581f5dfa78a94e52194a2281b.tar.bz2 timeline-88851e84f070207581f5dfa78a94e52194a2281b.zip |
...
Diffstat (limited to 'FrontEnd/src/views')
-rw-r--r-- | FrontEnd/src/views/common/Skeleton.css | 14 | ||||
-rw-r--r-- | FrontEnd/src/views/common/Skeleton.tsx | 4 | ||||
-rw-r--r-- | FrontEnd/src/views/common/index.css | 23 | ||||
-rw-r--r-- | FrontEnd/src/views/common/tab/TabPages.tsx | 33 | ||||
-rw-r--r-- | FrontEnd/src/views/common/tab/Tabs.css | 31 | ||||
-rw-r--r-- | FrontEnd/src/views/common/tab/Tabs.tsx | 47 | ||||
-rw-r--r-- | FrontEnd/src/views/settings/ChangeAvatarDialog.tsx | 35 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/MarkdownPostEdit.css | 21 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx | 16 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePostEdit.css | 24 | ||||
-rw-r--r-- | FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx | 6 |
11 files changed, 187 insertions, 67 deletions
diff --git a/FrontEnd/src/views/common/Skeleton.css b/FrontEnd/src/views/common/Skeleton.css new file mode 100644 index 00000000..db1a1c34 --- /dev/null +++ b/FrontEnd/src/views/common/Skeleton.css @@ -0,0 +1,14 @@ +.cru-skeleton {
+ padding: 0 1em;
+}
+
+.cru-skeleton-line {
+ height: 1em;
+ background-color: #e6e6e6;
+ margin: 0.7em 0;
+ border-radius: 0.2em;
+}
+
+.cru-skeleton-line.last {
+ width: 50%;
+}
diff --git a/FrontEnd/src/views/common/Skeleton.tsx b/FrontEnd/src/views/common/Skeleton.tsx index 14886c71..58d34215 100644 --- a/FrontEnd/src/views/common/Skeleton.tsx +++ b/FrontEnd/src/views/common/Skeleton.tsx @@ -1,6 +1,8 @@ import React from "react"; import classnames from "classnames"; -import { range } from "lodash"; +import range from "lodash/range"; + +import "./Skeleton.css"; export interface SkeletonProps { lineNumber?: number; diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css index 62167cfc..529e0e51 100644 --- a/FrontEnd/src/views/common/index.css +++ b/FrontEnd/src/views/common/index.css @@ -108,6 +108,10 @@ color: var(--cru-danger-color);
}
+.cru-text-center {
+ text-align: center;
+}
+
.cru-text-end {
text-align: end;
}
@@ -128,6 +132,11 @@ clear: both;
}
+.cru-fill-parent {
+ width: 100%;
+ height: 100%;
+}
+
.icon-button {
font-size: 1.4rem;
cursor: pointer;
@@ -160,20 +169,6 @@ border-radius: 50%;
}
-.cru-skeleton {
- padding: 0 1em;
-}
-
-.cru-skeleton-line {
- height: 1em;
- background-color: #e6e6e6;
- margin: 0.7em 0;
- border-radius: 0.2em;
-}
-.cru-skeleton-line.last {
- width: 50%;
-}
-
.cru-tab-pages-action-area {
display: flex;
align-items: center;
diff --git a/FrontEnd/src/views/common/tab/TabPages.tsx b/FrontEnd/src/views/common/tab/TabPages.tsx index b7a9fb36..677f558a 100644 --- a/FrontEnd/src/views/common/tab/TabPages.tsx +++ b/FrontEnd/src/views/common/tab/TabPages.tsx @@ -1,17 +1,19 @@ import React from "react"; -import { useTranslation } from "react-i18next"; -import { convertI18nText, I18nText, UiLogicError } from "@/common"; +import { I18nText, UiLogicError } from "@/common"; + +import Tabs from "./Tabs"; export interface TabPage { - id: string; - tabText: I18nText; + name: string; + text: I18nText; page: React.ReactNode; } export interface TabPagesProps { pages: TabPage[]; actions?: React.ReactNode; + dense?: boolean; className?: string; style?: React.CSSProperties; navClassName?: string; @@ -23,6 +25,7 @@ export interface TabPagesProps { const TabPages: React.FC<TabPagesProps> = ({ pages, actions, + dense, className, style, navClassName, @@ -30,17 +33,13 @@ const TabPages: React.FC<TabPagesProps> = ({ pageContainerClassName, pageContainerStyle, }) => { - // TODO: - if (pages.length === 0) { throw new UiLogicError("Page list can't be empty."); } - const { t } = useTranslation(); - - const [tab, setTab] = React.useState<string>(pages[0].id); + const [tab, setTab] = React.useState<string>(pages[0].name); - const currentPage = pages.find((p) => p.id === tab); + const currentPage = pages.find((p) => p.name === tab); if (currentPage == null) { throw new UiLogicError("Current tab value is bad."); @@ -48,6 +47,20 @@ const TabPages: React.FC<TabPagesProps> = ({ return ( <div className={className} style={style}> + <Tabs + tabs={pages.map((page) => ({ + name: page.name, + text: page.text, + onClick: () => { + setTab(page.name); + }, + }))} + dense={dense} + activeTabName={tab} + className={navClassName} + style={navStyle} + actions={actions} + /> <div className={pageContainerClassName} style={pageContainerStyle}> {currentPage.page} </div> diff --git a/FrontEnd/src/views/common/tab/Tabs.css b/FrontEnd/src/views/common/tab/Tabs.css new file mode 100644 index 00000000..53505a3c --- /dev/null +++ b/FrontEnd/src/views/common/tab/Tabs.css @@ -0,0 +1,31 @@ +.cru-nav {
+ border-bottom: var(--cru-background-2-color) 1px solid;
+ display: flex;
+}
+
+.cru-nav-item {
+ color: var(--cru-primary-color);
+ border: var(--cru-background-2-color) 0.5px solid;
+ border-bottom: none;
+ padding: 0.5em 1.5em;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
+ transition: all 0.5s;
+ cursor: pointer;
+}
+
+.cru-nav.dense .cru-nav-item {
+ padding: 0.2em 1em;
+}
+
+.cru-nav-item:hover {
+ background-color: var(--cru-background-1-color);
+}
+
+.cru-nav-item:not(.active) {
+ color: var(--cru-primary-r2-color);
+}
+
+.cru-nav-action-area {
+ margin-left: auto;
+}
diff --git a/FrontEnd/src/views/common/tab/Tabs.tsx b/FrontEnd/src/views/common/tab/Tabs.tsx index 29ebcbd8..701b4073 100644 --- a/FrontEnd/src/views/common/tab/Tabs.tsx +++ b/FrontEnd/src/views/common/tab/Tabs.tsx @@ -1,6 +1,11 @@ import React from "react"; +import { Link } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import classnames from "classnames"; -import { I18nText } from "@/common"; +import { convertI18nText, I18nText } from "@/common"; + +import "./Tabs.css"; export interface Tab { name: string; @@ -11,9 +16,47 @@ export interface Tab { export interface TabsProps { activeTabName?: string; + actions?: React.ReactNode; + dense?: boolean; tabs: Tab[]; + className?: string; + style?: React.CSSProperties; } export default function Tabs(props: TabsProps): React.ReactElement | null { - return <div></div>; + const { tabs, activeTabName, className, style, dense, actions } = props; + + const { t } = useTranslation(); + + return ( + <div + className={classnames("cru-nav", dense && "dense", className)} + style={style} + > + {tabs.map((tab) => { + const active = activeTabName === tab.name; + const className = classnames("cru-nav-item", active && "active"); + + if (tab.link != null) { + return ( + <Link + key={tab.name} + to={tab.link} + onClick={tab.onClick} + className={className} + > + {convertI18nText(tab.text, t)} + </Link> + ); + } else { + return ( + <span key={tab.name} onClick={tab.onClick} className={className}> + {convertI18nText(tab.text, t)} + </span> + ); + } + })} + <div className="cru-nav-action-area">{actions}</div> + </div> + ); } diff --git a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx index 0bf51c21..784da2d7 100644 --- a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx @@ -161,7 +161,10 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { return ( <Dialog open={props.open} onClose={close}> - <h3>{t("settings.dialogChangeAvatar.title")}</h3> + <h3 className="cru-color-primary"> + {t("settings.dialogChangeAvatar.title")} + </h3> + <hr /> {(() => { if (state === "select") { return ( @@ -171,10 +174,16 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {t("settings.dialogChangeAvatar.prompt.select")} </div> <div className="row"> - <input type="file" accept="image/*" onChange={onSelectFile} /> + <input + className="px-0" + type="file" + accept="image/*" + onChange={onSelectFile} + /> </div> </div> - <div> + <hr /> + <div className="cru-dialog-bottom-area"> <Button text="operationDialog.cancel" color="secondary" @@ -202,15 +211,18 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {t("settings.dialogChangeAvatar.prompt.crop")} </div> </div> - <div> + <hr /> + <div className="cru-dialog-bottom-area"> <Button text="operationDialog.cancel" color="secondary" + outline onClick={close} /> <Button text="operationDialog.previousStep" color="secondary" + outline onClick={onCropPrevious} /> <Button @@ -232,16 +244,19 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {t("settings.dialogChangeAvatar.prompt.processingCrop")} </div> </div> - <div> + <hr /> + <div className="cru-dialog-bottom-area"> <Button text="operationDialog.cancel" color="secondary" onClick={close} + outline /> <Button text="operationDialog.previousStep" color="secondary" onClick={onPreviewPrevious} + outline /> </div> </> @@ -255,15 +270,18 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {t("settings.dialogChangeAvatar.prompt.preview")} </div> </div> - <div> + <hr /> + <div className="cru-dialog-bottom-area"> <Button text="operationDialog.cancel" color="secondary" + outline onClick={close} /> <Button text="operationDialog.previousStep" color="secondary" + outline onClick={onPreviewPrevious} /> <Button @@ -283,7 +301,6 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {t("settings.dialogChangeAvatar.prompt.uploading")} </div> </div> - <div></div> </> ); } else if (state === "success") { @@ -294,7 +311,8 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {t("operationDialog.success")} </div> </div> - <div> + <hr /> + <div className="cru-dialog-bottom-area"> <Button text="operationDialog.ok" color="success" @@ -310,6 +328,7 @@ const ChangeAvatarDialog: React.FC<ChangeAvatarDialogProps> = (props) => { {createPreviewRow()} <div className="row text-danger">{trueMessage}</div> </div> + <hr /> <div> <Button text="operationDialog.cancel" diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.css b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.css new file mode 100644 index 00000000..a303b227 --- /dev/null +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.css @@ -0,0 +1,21 @@ +.timeline-markdown-post-edit-page {
+ overflow: scroll;
+ max-height: 300px;
+}
+
+.timeline-markdown-post-edit-image-container {
+ position: relative;
+ text-align: center;
+ margin-bottom: 1em;
+}
+
+.timeline-markdown-post-edit-image {
+ max-width: 100%;
+ max-height: 200px;
+}
+
+.timeline-markdown-post-edit-image-delete-button {
+ position: absolute;
+ right: 10px;
+ top: 2px;
+}
diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx index b16bf43d..0066701f 100644 --- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx @@ -13,6 +13,8 @@ import TabPages from "../common/tab/TabPages"; import ConfirmDialog from "../common/dailog/ConfirmDialog"; import Spinner from "../common/Spinner"; +import "./MarkdownPostEdit.css"; + export interface MarkdownPostEditProps { timeline: string; onPosted: (post: HttpTimelinePostInfo) => void; @@ -102,6 +104,7 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({ className={className} style={style} pageContainerClassName="py-2" + dense actions={ process ? ( <Spinner /> @@ -125,12 +128,13 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({ } pages={[ { - id: "text", - tabText: "edit", + name: "text", + text: "edit", page: ( <textarea value={text} disabled={process} + className="cru-fill-parent" onChange={(event) => { getBuilder().setMarkdownText(event.currentTarget.value); }} @@ -138,8 +142,8 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({ ), }, { - id: "images", - tabText: "image", + name: "images", + text: "image", page: ( <div className="timeline-markdown-post-edit-page"> {images.map((image, index) => ( @@ -177,8 +181,8 @@ const MarkdownPostEdit: React.FC<MarkdownPostEditProps> = ({ ), }, { - id: "preview", - tabText: "preview", + name: "preview", + text: "preview", page: ( <div className="markdown-container timeline-markdown-post-edit-page" diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.css b/FrontEnd/src/views/timeline-common/TimelinePostEdit.css index 0c7deaa2..4ce98383 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.css +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.css @@ -9,7 +9,7 @@ }
.timeline-post-edit {
- position: sticky;
+ position: sticky !important;
bottom: 0;
z-index: 1;
}
@@ -18,25 +18,3 @@ max-width: 100px;
max-height: 100px;
}
-
-.timeline-markdown-post-edit-page {
- overflow: scroll;
- max-height: 300px;
-}
-
-.timeline-markdown-post-edit-image-container {
- position: relative;
- text-align: center;
- margin-bottom: 1em;
-}
-
-.timeline-markdown-post-edit-image {
- max-width: 100%;
- max-height: 200px;
-}
-
-.timeline-markdown-post-edit-image-delete-button {
- position: absolute;
- right: 10px;
- top: 2px;
-}
diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index e2045429..9c48c7c8 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -203,7 +203,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { <Card className="timeline-item-card"> {showMarkdown ? ( <MarkdownPostEdit - className="w-100" + className="cru-fill-parent" onClose={() => setShowMarkdown(false)} timeline={timeline.name} onPosted={onPosted} @@ -216,7 +216,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { if (kind === "text") { return ( <TimelinePostEditText - className="w-100 h-100 timeline-post-edit" + className="cru-fill-parent timeline-post-edit" text={text} disabled={process} onChange={(t) => { @@ -239,7 +239,7 @@ const TimelinePostEdit: React.FC<TimelinePostEditProps> = (props) => { })()} </div> <div className="col col-auto align-self-end m-1"> - <div className="d-block text-center mt-1 mb-2"> + <div className="d-block cru-text-center mt-1 mb-2"> <PopupMenu items={(["text", "image", "markdown"] as const).map( (kind) => ({ |