From 47587812b809fee2a95c76266d9d0e42fc4ac1ca Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 15 Jun 2021 14:14:28 +0800 Subject: ... --- FrontEnd/src/views/admin/Admin.tsx | 48 ++++ FrontEnd/src/views/admin/AdminNav.tsx | 44 ++++ FrontEnd/src/views/admin/MoreAdmin.tsx | 13 ++ FrontEnd/src/views/admin/UserAdmin.tsx | 396 +++++++++++++++++++++++++++++++++ FrontEnd/src/views/admin/admin.sass | 22 ++ 5 files changed, 523 insertions(+) create mode 100644 FrontEnd/src/views/admin/Admin.tsx create mode 100644 FrontEnd/src/views/admin/AdminNav.tsx create mode 100644 FrontEnd/src/views/admin/MoreAdmin.tsx create mode 100644 FrontEnd/src/views/admin/UserAdmin.tsx create mode 100644 FrontEnd/src/views/admin/admin.sass (limited to 'FrontEnd/src/views/admin') diff --git a/FrontEnd/src/views/admin/Admin.tsx b/FrontEnd/src/views/admin/Admin.tsx new file mode 100644 index 00000000..0b6d1f05 --- /dev/null +++ b/FrontEnd/src/views/admin/Admin.tsx @@ -0,0 +1,48 @@ +import React, { Fragment } from "react"; +import { Redirect, Route, Switch, useRouteMatch, match } from "react-router"; +import { Container } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; + +import { AuthUser } from "@/services/user"; + +import AdminNav from "./AdminNav"; +import UserAdmin from "./UserAdmin"; +import MoreAdmin from "./MoreAdmin"; + +interface AdminProps { + user: AuthUser; +} + +const Admin: React.FC = ({ user }) => { + useTranslation("admin"); + + const match = useRouteMatch(); + + return ( + + + + + {(p) => { + const match = p.match as match<{ name: string }>; + const name = match.params["name"]; + return ( + + + {(() => { + if (name === "users") { + return ; + } else if (name === "more") { + return ; + } + })()} + + ); + }} + + + + ); +}; + +export default Admin; diff --git a/FrontEnd/src/views/admin/AdminNav.tsx b/FrontEnd/src/views/admin/AdminNav.tsx new file mode 100644 index 00000000..47e2138f --- /dev/null +++ b/FrontEnd/src/views/admin/AdminNav.tsx @@ -0,0 +1,44 @@ +import React from "react"; +import { Nav } from "react-bootstrap"; +import { useTranslation } from "react-i18next"; +import { useHistory, useRouteMatch } from "react-router"; + +const AdminNav: React.FC = () => { + const match = useRouteMatch<{ name: string }>(); + const history = useHistory(); + + const { t } = useTranslation(); + + const name = match.params.name; + + function toggle(newTab: string): void { + history.push(`/admin/${newTab}`); + } + + return ( + + ); +}; + +export default AdminNav; diff --git a/FrontEnd/src/views/admin/MoreAdmin.tsx b/FrontEnd/src/views/admin/MoreAdmin.tsx new file mode 100644 index 00000000..042789a0 --- /dev/null +++ b/FrontEnd/src/views/admin/MoreAdmin.tsx @@ -0,0 +1,13 @@ +import React from "react"; + +import { AuthUser } from "@/services/user"; + +export interface MoreAdminProps { + user: AuthUser; +} + +const MoreAdmin: React.FC = () => { + return <>More...; +}; + +export default MoreAdmin; diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx new file mode 100644 index 00000000..4e9cd600 --- /dev/null +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -0,0 +1,396 @@ +import React, { useState, useEffect } from "react"; +import classnames from "classnames"; +import { ListGroup, Row, Col, Spinner, Button } from "react-bootstrap"; + +import OperationDialog, { + OperationDialogBoolInput, +} from "../common/OperationDialog"; + +import { AuthUser } from "@/services/user"; +import { + getHttpUserClient, + HttpUser, + kUserPermissionList, + UserPermission, +} from "http/user"; +import { Trans, useTranslation } from "react-i18next"; + +interface DialogProps { + open: boolean; + close: () => void; + data: TData; + onSuccess: (data: TReturn) => void; +} + +const CreateUserDialog: React.FC> = ({ + open, + close, + onSuccess, +}) => { + return ( + + getHttpUserClient().post({ + username, + password, + }) + } + close={close} + open={open} + onSuccessAndClose={onSuccess} + /> + ); +}; + +const UsernameLabel: React.FC = (props) => { + return {props.children}; +}; + +const UserDeleteDialog: React.FC> = + ({ open, close, data: { username }, onSuccess }) => { + return ( + ( + + 0{username}2 + + )} + onProcess={() => getHttpUserClient().delete(username)} + onSuccessAndClose={onSuccess} + /> + ); + }; + +const UserModifyDialog: React.FC< + DialogProps< + { + oldUser: HttpUser; + }, + HttpUser + > +> = ({ open, close, data: { oldUser }, onSuccess }) => { + return ( + ( + + 0{oldUser.username}2 + + )} + inputScheme={ + [ + { + type: "text", + label: "admin:user.username", + initValue: oldUser.username, + }, + { type: "text", label: "admin:user.password" }, + { + type: "text", + label: "admin:user.nickname", + initValue: oldUser.nickname, + }, + ] as const + } + onProcess={([username, password, nickname]) => + getHttpUserClient().patch(oldUser.username, { + username: username !== oldUser.username ? username : undefined, + password: password !== "" ? password : undefined, + nickname: nickname !== oldUser.nickname ? nickname : undefined, + }) + } + onSuccessAndClose={onSuccess} + /> + ); +}; + +const UserPermissionModifyDialog: React.FC< + DialogProps< + { + username: string; + permissions: UserPermission[]; + }, + UserPermission[] + > +> = ({ open, close, data: { username, permissions }, onSuccess }) => { + const oldPermissionBoolList: boolean[] = kUserPermissionList.map( + (permission) => permissions.includes(permission) + ); + + return ( + ( + + 0{username}2 + + )} + inputScheme={kUserPermissionList.map( + (permission, index) => ({ + type: "bool", + label: permission, + initValue: oldPermissionBoolList[index], + }) + )} + onProcess={async (newPermissionBoolList): Promise => { + for (let index = 0; index < kUserPermissionList.length; index++) { + const oldValue = oldPermissionBoolList[index]; + const newValue = newPermissionBoolList[index]; + const permission = kUserPermissionList[index]; + if (oldValue === newValue) continue; + if (newValue) { + await getHttpUserClient().putUserPermission(username, permission); + } else { + await getHttpUserClient().deleteUserPermission( + username, + permission + ); + } + } + return newPermissionBoolList; + }} + onSuccessAndClose={(newPermissionBoolList: boolean[]) => { + const permissions: UserPermission[] = []; + for (let index = 0; index < kUserPermissionList.length; index++) { + if (newPermissionBoolList[index]) { + permissions.push(kUserPermissionList[index]); + } + } + onSuccess(permissions); + }} + /> + ); +}; + +const kModify = "modify"; +const kModifyPermission = "permission"; +const kDelete = "delete"; + +type TModify = typeof kModify; +type TModifyPermission = typeof kModifyPermission; +type TDelete = typeof kDelete; + +type ContextMenuItem = TModify | TModifyPermission | TDelete; + +interface UserItemProps { + on: { [key in ContextMenuItem]: () => void }; + user: HttpUser; +} + +const UserItem: React.FC = ({ user, on }) => { + const { t } = useTranslation(); + + const [editMaskVisible, setEditMaskVisible] = React.useState(false); + + return ( + + setEditMaskVisible(true)} + /> +

{user.username}

+
+ {t("admin:user.nickname")} + {user.nickname} +
+
+ {t("admin:user.uniqueId")} + {user.uniqueId} +
+
+ {t("admin:user.permissions")} + {user.permissions.map((permission) => { + return ( + + {permission}{" "} + + ); + })} +
+
setEditMaskVisible(false)} + > + + + +
+
+ ); +}; + +interface UserAdminProps { + user: AuthUser; +} + +const UserAdmin: React.FC = () => { + const { t } = useTranslation(); + + type DialogInfo = + | null + | { + type: "create"; + } + | { + type: TModify; + user: HttpUser; + } + | { + type: TModifyPermission; + username: string; + permissions: UserPermission[]; + } + | { type: TDelete; username: string }; + + const [users, setUsers] = useState(null); + const [dialog, setDialog] = useState(null); + const [usersVersion, setUsersVersion] = useState(0); + const updateUsers = (): void => { + setUsersVersion(usersVersion + 1); + }; + + useEffect(() => { + let subscribe = true; + void getHttpUserClient() + .list() + .then((us) => { + if (subscribe) { + setUsers(us); + } + }); + return () => { + subscribe = false; + }; + }, [usersVersion]); + + let dialogNode: React.ReactNode; + if (dialog) { + switch (dialog.type) { + case "create": + dialogNode = ( + setDialog(null)} + data={undefined} + onSuccess={updateUsers} + /> + ); + break; + case kDelete: + dialogNode = ( + setDialog(null)} + data={{ username: dialog.username }} + onSuccess={updateUsers} + /> + ); + break; + case kModify: + dialogNode = ( + setDialog(null)} + data={{ oldUser: dialog.user }} + onSuccess={updateUsers} + /> + ); + break; + case kModifyPermission: + dialogNode = ( + setDialog(null)} + data={{ + username: dialog.username, + permissions: dialog.permissions, + }} + onSuccess={updateUsers} + /> + ); + break; + } + } + + if (users) { + const userComponents = users.map((user) => { + return ( + { + setDialog({ + type: "modify", + user, + }); + }, + permission: () => { + setDialog({ + type: kModifyPermission, + username: user.username, + permissions: user.permissions, + }); + }, + delete: () => { + setDialog({ + type: "delete", + username: user.username, + }); + }, + }} + /> + ); + }); + + return ( + <> + + + + + + {userComponents} + {dialogNode} + + ); + } else { + return ; + } +}; + +export default UserAdmin; diff --git a/FrontEnd/src/views/admin/admin.sass b/FrontEnd/src/views/admin/admin.sass new file mode 100644 index 00000000..1ce010f8 --- /dev/null +++ b/FrontEnd/src/views/admin/admin.sass @@ -0,0 +1,22 @@ +.admin-user-item + position: relative + + .edit-mask + position: absolute + top: 0 + left: 0 + bottom: 0 + right: 0 + + background: #ffffffc5 + position: absolute + + display: flex + justify-content: center + align-items: center + + @include media-breakpoint-down(xs) + flex-direction: column + + button + margin: 0.5em 2em -- cgit v1.2.3 From e0b766203d7576ab67b16ba556ba14120d0bc876 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 15 Jun 2021 15:02:49 +0800 Subject: ... --- BackEnd/Timeline/FrontEndMode.cs | 1 - BackEnd/Timeline/Properties/launchSettings.json | 2 +- BackEnd/Timeline/Startup.cs | 4 --- FrontEnd/index.html | 29 ++++++++++++++++++++++ FrontEnd/package.json | 5 ++-- FrontEnd/postcss.config.js | 11 +++----- FrontEnd/src/index.ejs | 29 ---------------------- FrontEnd/src/services/TimelinePostBuilder.ts | 4 +-- FrontEnd/src/services/timeline.ts | 4 +-- FrontEnd/src/services/user.ts | 6 ++--- FrontEnd/src/tsconfig.json | 23 ----------------- FrontEnd/src/views/admin/UserAdmin.tsx | 2 +- FrontEnd/src/views/center/CenterBoards.tsx | 6 ++--- FrontEnd/src/views/center/TimelineBoard.tsx | 4 +-- FrontEnd/src/views/center/TimelineCreateDialog.tsx | 2 +- FrontEnd/src/views/common/user/UserAvatar.tsx | 2 +- FrontEnd/src/views/home/TimelineListView.tsx | 4 +-- FrontEnd/src/views/home/index.tsx | 4 +-- FrontEnd/src/views/search/index.tsx | 10 ++++---- FrontEnd/src/views/settings/ChangeAvatarDialog.tsx | 2 +- .../src/views/settings/ChangeNicknameDialog.tsx | 2 +- .../timeline-common/ConnectionStatusBadge.tsx | 2 +- .../src/views/timeline-common/MarkdownPostEdit.tsx | 2 +- .../timeline-common/PostPropertyChangeDialog.tsx | 2 +- FrontEnd/src/views/timeline-common/Timeline.tsx | 6 ++--- .../src/views/timeline-common/TimelineMember.tsx | 8 +++--- .../timeline-common/TimelinePageCardTemplate.tsx | 4 +-- .../views/timeline-common/TimelinePageTemplate.tsx | 4 +-- .../timeline-common/TimelinePagedPostListView.tsx | 2 +- .../timeline-common/TimelinePostContentView.tsx | 4 +-- .../src/views/timeline-common/TimelinePostEdit.tsx | 6 ++--- .../views/timeline-common/TimelinePostListView.tsx | 2 +- .../src/views/timeline-common/TimelinePostView.tsx | 2 +- .../TimelinePropertyChangeDialog.tsx | 2 +- FrontEnd/src/views/timeline/TimelineCard.tsx | 2 +- .../src/views/timeline/TimelineDeleteDialog.tsx | 2 +- FrontEnd/src/views/user/index.tsx | 2 +- FrontEnd/tsconfig.json | 25 +++++++++++++++++++ FrontEnd/vite.config.js | 3 +++ 39 files changed, 116 insertions(+), 120 deletions(-) create mode 100644 FrontEnd/index.html delete mode 100644 FrontEnd/src/index.ejs delete mode 100644 FrontEnd/src/tsconfig.json create mode 100644 FrontEnd/tsconfig.json (limited to 'FrontEnd/src/views/admin') diff --git a/BackEnd/Timeline/FrontEndMode.cs b/BackEnd/Timeline/FrontEndMode.cs index 63503292..eb718028 100644 --- a/BackEnd/Timeline/FrontEndMode.cs +++ b/BackEnd/Timeline/FrontEndMode.cs @@ -4,7 +4,6 @@ { Disable, Mock, - Proxy, Normal } } diff --git a/BackEnd/Timeline/Properties/launchSettings.json b/BackEnd/Timeline/Properties/launchSettings.json index f683ca2d..7f68b709 100644 --- a/BackEnd/Timeline/Properties/launchSettings.json +++ b/BackEnd/Timeline/Properties/launchSettings.json @@ -4,7 +4,7 @@ "commandName": "Project", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", - "ASPNETCORE_FRONTEND": "Proxy" + "ASPNETCORE_FRONTEND": "Disable" } }, "Dev-Mock": { diff --git a/BackEnd/Timeline/Startup.cs b/BackEnd/Timeline/Startup.cs index 18097e2c..26ffb4b1 100644 --- a/BackEnd/Timeline/Startup.cs +++ b/BackEnd/Timeline/Startup.cs @@ -166,10 +166,6 @@ namespace Timeline { app.UseSpa(spa => { - if (_frontEndMode == FrontEndMode.Proxy) - { - spa.UseProxyToSpaDevelopmentServer(new UriBuilder("http", "localhost", 3000).Uri); - } }); } } diff --git a/FrontEnd/index.html b/FrontEnd/index.html new file mode 100644 index 00000000..87e19743 --- /dev/null +++ b/FrontEnd/index.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + Timeline + + + +
+ + + diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 1afb5ad0..5d12899e 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -33,8 +33,9 @@ "xregexp": "^5.0.2" }, "scripts": { - "start": "webpack serve --config ./webpack.config.dev.js", - "build": "webpack --config ./webpack.config.prod.js", + "start": "vite", + "build": "tsc && vite build", + "preview": "vite preview", "lint": "eslint src/ --ext .js --ext .jsx --ext .ts --ext .tsx", "lint:fix": "eslint src/ --ext .js --ext .jsx --ext .ts --ext .tsx --fix" }, diff --git a/FrontEnd/postcss.config.js b/FrontEnd/postcss.config.js index 74ee8155..9129aa1f 100644 --- a/FrontEnd/postcss.config.js +++ b/FrontEnd/postcss.config.js @@ -1,10 +1,5 @@ +import postcssPresetEnv from "postcss-preset-env"; + module.exports = { - plugins: [ - [ - "postcss-preset-env", - { - // Options - }, - ], - ], + plugins: [postcssPresetEnv()], }; diff --git a/FrontEnd/src/index.ejs b/FrontEnd/src/index.ejs deleted file mode 100644 index c2ff4182..00000000 --- a/FrontEnd/src/index.ejs +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - <%= htmlWebpackPlugin.options.title %> - - - -
- - - diff --git a/FrontEnd/src/services/TimelinePostBuilder.ts b/FrontEnd/src/services/TimelinePostBuilder.ts index fe4c7a9a..40279eca 100644 --- a/FrontEnd/src/services/TimelinePostBuilder.ts +++ b/FrontEnd/src/services/TimelinePostBuilder.ts @@ -2,8 +2,8 @@ import { Remarkable } from "remarkable"; import { UiLogicError } from "@/common"; -import { base64 } from "http/common"; -import { HttpTimelinePostPostRequest } from "http/timeline"; +import { base64 } from "@/http/common"; +import { HttpTimelinePostPostRequest } from "@/http/timeline"; export default class TimelinePostBuilder { private _onChange: () => void; diff --git a/FrontEnd/src/services/timeline.ts b/FrontEnd/src/services/timeline.ts index 4ebb705d..d8c0ae00 100644 --- a/FrontEnd/src/services/timeline.ts +++ b/FrontEnd/src/services/timeline.ts @@ -1,9 +1,9 @@ -import { TimelineVisibility } from "http/timeline"; +import { TimelineVisibility } from "@/http/timeline"; import XRegExp from "xregexp"; import { Observable } from "rxjs"; import { HubConnectionBuilder, HubConnectionState } from "@microsoft/signalr"; -import { getHttpToken } from "http/common"; +import { getHttpToken } from "@/http/common"; const timelineNameReg = XRegExp("^[-_\\p{L}]*$", "u"); diff --git a/FrontEnd/src/services/user.ts b/FrontEnd/src/services/user.ts index 3375c88a..9a8e5687 100644 --- a/FrontEnd/src/services/user.ts +++ b/FrontEnd/src/services/user.ts @@ -3,12 +3,12 @@ import { BehaviorSubject, Observable } from "rxjs"; import { UiLogicError } from "@/common"; -import { HttpNetworkError, setHttpToken } from "http/common"; +import { HttpNetworkError, setHttpToken } from "@/http/common"; import { getHttpTokenClient, HttpCreateTokenBadCredentialError, -} from "http/token"; -import { getHttpUserClient, HttpUser, UserPermission } from "http/user"; +} from "@/http/token"; +import { getHttpUserClient, HttpUser, UserPermission } from "@/http/user"; import { pushAlert } from "./alert"; diff --git a/FrontEnd/src/tsconfig.json b/FrontEnd/src/tsconfig.json deleted file mode 100644 index 817c50bd..00000000 --- a/FrontEnd/src/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "compilerOptions": { - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "jsx": "react", - "types": ["vite/client"], - "sourceMap": true, - "baseUrl": "./", - "paths": { - "@/*": ["*"] - }, - "lib": ["dom", "dom.iterable", "esnext"] - }, - "include": ["."] -} diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 4e9cd600..558d3aee 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -12,7 +12,7 @@ import { HttpUser, kUserPermissionList, UserPermission, -} from "http/user"; +} from "@/http/user"; import { Trans, useTranslation } from "react-i18next"; interface DialogProps { diff --git a/FrontEnd/src/views/center/CenterBoards.tsx b/FrontEnd/src/views/center/CenterBoards.tsx index 431d1e9a..f5200415 100644 --- a/FrontEnd/src/views/center/CenterBoards.tsx +++ b/FrontEnd/src/views/center/CenterBoards.tsx @@ -5,9 +5,9 @@ import { useTranslation } from "react-i18next"; import { pushAlert } from "@/services/alert"; import { useUserLoggedIn } from "@/services/user"; -import { getHttpTimelineClient } from "http/timeline"; -import { getHttpBookmarkClient } from "http/bookmark"; -import { getHttpHighlightClient } from "http/highlight"; +import { getHttpTimelineClient } from "@/http/timeline"; +import { getHttpBookmarkClient } from "@/http/bookmark"; +import { getHttpHighlightClient } from "@/http/highlight"; import TimelineBoard from "./TimelineBoard"; diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx index bb80266b..840c0415 100644 --- a/FrontEnd/src/views/center/TimelineBoard.tsx +++ b/FrontEnd/src/views/center/TimelineBoard.tsx @@ -4,7 +4,7 @@ import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; import { Spinner } from "react-bootstrap"; -import { HttpTimelineInfo } from "http/timeline"; +import { HttpTimelineInfo } from "@/http/timeline"; import TimelineLogo from "../common/TimelineLogo"; import UserTimelineLogo from "../common/UserTimelineLogo"; @@ -34,7 +34,7 @@ const TimelineBoardItem: React.FC = ({ actions, }) => { const { name, title } = timeline; - const isPersonal = name.startsWith("@"); + const isPersonal = name.startsWith("src"); const url = isPersonal ? `/users/${timeline.owner.username}` : `/timelines/${name}`; diff --git a/FrontEnd/src/views/center/TimelineCreateDialog.tsx b/FrontEnd/src/views/center/TimelineCreateDialog.tsx index a2437ae5..b4e25ba1 100644 --- a/FrontEnd/src/views/center/TimelineCreateDialog.tsx +++ b/FrontEnd/src/views/center/TimelineCreateDialog.tsx @@ -3,7 +3,7 @@ import { useHistory } from "react-router"; import { validateTimelineName } from "@/services/timeline"; import OperationDialog from "../common/OperationDialog"; -import { getHttpTimelineClient, HttpTimelineInfo } from "http/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; interface TimelineCreateDialogProps { open: boolean; diff --git a/FrontEnd/src/views/common/user/UserAvatar.tsx b/FrontEnd/src/views/common/user/UserAvatar.tsx index 901697db..9e822528 100644 --- a/FrontEnd/src/views/common/user/UserAvatar.tsx +++ b/FrontEnd/src/views/common/user/UserAvatar.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { getHttpUserClient } from "http/user"; +import { getHttpUserClient } from "@/http/user"; export interface UserAvatarProps extends React.ImgHTMLAttributes { diff --git a/FrontEnd/src/views/home/TimelineListView.tsx b/FrontEnd/src/views/home/TimelineListView.tsx index 975875af..2fb54820 100644 --- a/FrontEnd/src/views/home/TimelineListView.tsx +++ b/FrontEnd/src/views/home/TimelineListView.tsx @@ -2,7 +2,7 @@ import React from "react"; import { convertI18nText, I18nText } from "@/common"; -import { HttpTimelineInfo } from "http/timeline"; +import { HttpTimelineInfo } from "@/http/timeline"; import { useTranslation } from "react-i18next"; import { Link } from "react-router-dom"; @@ -13,7 +13,7 @@ interface TimelineListItemProps { const TimelineListItem: React.FC = ({ timeline }) => { const url = React.useMemo( () => - timeline.name.startsWith("@") + timeline.name.startsWith("src") ? `/users/${timeline.owner.username}` : `/timelines/${timeline.name}`, [timeline] diff --git a/FrontEnd/src/views/home/index.tsx b/FrontEnd/src/views/home/index.tsx index efc364d7..0eca23ee 100644 --- a/FrontEnd/src/views/home/index.tsx +++ b/FrontEnd/src/views/home/index.tsx @@ -1,8 +1,8 @@ import React from "react"; import { useHistory } from "react-router"; -import { HttpTimelineInfo } from "http/timeline"; -import { getHttpHighlightClient } from "http/highlight"; +import { HttpTimelineInfo } from "@/http/timeline"; +import { getHttpHighlightClient } from "@/http/highlight"; import SearchInput from "../common/SearchInput"; import TimelineListView from "./TimelineListView"; diff --git a/FrontEnd/src/views/search/index.tsx b/FrontEnd/src/views/search/index.tsx index 14a9709c..9a26802d 100644 --- a/FrontEnd/src/views/search/index.tsx +++ b/FrontEnd/src/views/search/index.tsx @@ -4,9 +4,9 @@ import { Container, Row } from "react-bootstrap"; import { useHistory, useLocation } from "react-router"; import { Link } from "react-router-dom"; -import { HttpNetworkError } from "http/common"; -import { getHttpSearchClient } from "http/search"; -import { HttpTimelineInfo } from "http/timeline"; +import { HttpNetworkError } from "@/http/common"; +import { getHttpSearchClient } from "@/http/search"; +import { HttpTimelineInfo } from "@/http/timeline"; import SearchInput from "../common/SearchInput"; import UserAvatar from "../common/user/UserAvatar"; @@ -14,7 +14,7 @@ import UserAvatar from "../common/user/UserAvatar"; const TimelineSearchResultItemView: React.FC<{ timeline: HttpTimelineInfo; }> = ({ timeline }) => { - const link = timeline.name.startsWith("@") + const link = timeline.name.startsWith("src") ? `users/${timeline.owner.username}` : `timelines/${timeline.name}`; @@ -33,7 +33,7 @@ const TimelineSearchResultItemView: React.FC<{ /> {timeline.owner.nickname} - @{timeline.owner.username} + src{timeline.owner.username} diff --git a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx index 338d2112..c4f6f492 100644 --- a/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx +++ b/FrontEnd/src/views/settings/ChangeAvatarDialog.tsx @@ -7,7 +7,7 @@ import { UiLogicError } from "@/common"; import { useUserLoggedIn } from "@/services/user"; -import { getHttpUserClient } from "http/user"; +import { getHttpUserClient } from "@/http/user"; import ImageCropper, { Clip, applyClipToImage } from "../common/ImageCropper"; diff --git a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx index e6420f36..4b44cdd6 100644 --- a/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx +++ b/FrontEnd/src/views/settings/ChangeNicknameDialog.tsx @@ -1,4 +1,4 @@ -import { getHttpUserClient } from "http/user"; +import { getHttpUserClient } from "@/http/user"; import { useUserLoggedIn } from "@/services/user"; import React from "react"; diff --git a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx b/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx index df43d8d2..1b9d6d2a 100644 --- a/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx +++ b/FrontEnd/src/views/timeline-common/ConnectionStatusBadge.tsx @@ -1,6 +1,6 @@ import React from "react"; import classnames from "classnames"; -import { HubConnectionState } from "@microsoft/signalr"; +import { HubConnectionState } from "srcmicrosoft/signalr"; import { useTranslation } from "react-i18next"; export interface ConnectionStatusBadgeProps { diff --git a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx index 1514d28f..685e17be 100644 --- a/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/MarkdownPostEdit.tsx @@ -4,7 +4,7 @@ import { Form, Spinner } from "react-bootstrap"; import { useTranslation } from "react-i18next"; import { Prompt } from "react-router"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import FlatButton from "../common/FlatButton"; import TabPages from "../common/TabPages"; diff --git a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx b/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx index 21c5272e..001e52d7 100644 --- a/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx +++ b/FrontEnd/src/views/timeline-common/PostPropertyChangeDialog.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import OperationDialog from "../common/OperationDialog"; diff --git a/FrontEnd/src/views/timeline-common/Timeline.tsx b/FrontEnd/src/views/timeline-common/Timeline.tsx index 40619e64..31ea5870 100644 --- a/FrontEnd/src/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/views/timeline-common/Timeline.tsx @@ -1,12 +1,12 @@ import React from "react"; -import { HubConnectionState } from "@microsoft/signalr"; +import { HubConnectionState } from "srcmicrosoft/signalr"; import { HttpForbiddenError, HttpNetworkError, HttpNotFoundError, -} from "http/common"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "http/timeline"; +} from "@/http/common"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import { getTimelinePostUpdate$ } from "@/services/timeline"; diff --git a/FrontEnd/src/views/timeline-common/TimelineMember.tsx b/FrontEnd/src/views/timeline-common/TimelineMember.tsx index 3d4de8b8..1ef085a7 100644 --- a/FrontEnd/src/views/timeline-common/TimelineMember.tsx +++ b/FrontEnd/src/views/timeline-common/TimelineMember.tsx @@ -4,12 +4,12 @@ import { Container, ListGroup, Modal, Row, Col, Button } from "react-bootstrap"; import { convertI18nText, I18nText } from "@/common"; -import { HttpUser } from "http/user"; -import { getHttpSearchClient } from "http/search"; +import { HttpUser } from "@/http/user"; +import { getHttpSearchClient } from "@/http/search"; import SearchInput from "../common/SearchInput"; import UserAvatar from "../common/user/UserAvatar"; -import { getHttpTimelineClient, HttpTimelineInfo } from "http/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; const TimelineMemberItem: React.FC<{ user: HttpUser; @@ -27,7 +27,7 @@ const TimelineMemberItem: React.FC<{ {user.nickname} - {"@" + user.username} + {"src" + user.username} {onAction ? ( diff --git a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx index 038ea3ab..623d643f 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageCardTemplate.tsx @@ -2,8 +2,8 @@ import React from "react"; import classnames from "classnames"; import { useTranslation } from "react-i18next"; -import { getHttpHighlightClient } from "http/highlight"; -import { getHttpBookmarkClient } from "http/bookmark"; +import { getHttpHighlightClient } from "@/http/highlight"; +import { getHttpBookmarkClient } from "@/http/bookmark"; import { useUser } from "@/services/user"; import { pushAlert } from "@/services/alert"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx index 44926cc6..658ce502 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePageTemplate.tsx @@ -3,8 +3,8 @@ import { useTranslation } from "react-i18next"; import { Container } from "react-bootstrap"; import { HubConnectionState } from "@microsoft/signalr"; -import { HttpNetworkError, HttpNotFoundError } from "http/common"; -import { getHttpTimelineClient, HttpTimelineInfo } from "http/timeline"; +import { HttpNetworkError, HttpNotFoundError } from "@/http/common"; +import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; import { getAlertHost } from "@/services/alert"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx b/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx index d569a2d7..37f02a82 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePagedPostListView.tsx @@ -1,6 +1,6 @@ import React from "react"; -import { HttpTimelinePostInfo } from "http/timeline"; +import { HttpTimelinePostInfo } from "@/http/timeline"; import useScrollToTop from "@/utilities/useScrollToTop"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx index f1b53335..607b72c9 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostContentView.tsx @@ -4,8 +4,8 @@ import { Remarkable } from "remarkable"; import { UiLogicError } from "@/common"; -import { HttpNetworkError } from "http/common"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "http/timeline"; +import { HttpNetworkError } from "@/http/common"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import { useUser } from "@/services/user"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx index 0f470fd6..1f9f02a5 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostEdit.tsx @@ -10,10 +10,10 @@ import { HttpTimelineInfo, HttpTimelinePostInfo, HttpTimelinePostPostRequestData, -} from "http/timeline"; +} from "@/http/timeline"; import { pushAlert } from "@/services/alert"; -import { base64 } from "http/common"; +import { base64 } from "@/http/common"; import BlobImage from "../common/BlobImage"; import LoadingButton from "../common/LoadingButton"; @@ -138,7 +138,7 @@ const TimelinePostEdit: React.FC = (props) => { (kind === "text" && text.length !== 0) || (kind === "image" && image != null); - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + // eslint-disable-next-line srctypescript-eslint/no-non-null-assertion const containerRef = React.useRef(null!); const notifyHeightChange = (): void => { diff --git a/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx index 49284720..ba204b72 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostListView.tsx @@ -1,7 +1,7 @@ import React, { Fragment } from "react"; import classnames from "classnames"; -import { HttpTimelinePostInfo } from "http/timeline"; +import { HttpTimelinePostInfo } from "@/http/timeline"; import TimelinePostView from "./TimelinePostView"; import TimelineDateLabel from "./TimelineDateLabel"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx index e8b32c71..f7b81478 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePostView.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePostView.tsx @@ -3,7 +3,7 @@ import classnames from "classnames"; import { Link } from "react-router-dom"; import { useTranslation } from "react-i18next"; -import { getHttpTimelineClient, HttpTimelinePostInfo } from "http/timeline"; +import { getHttpTimelineClient, HttpTimelinePostInfo } from "@/http/timeline"; import { pushAlert } from "@/services/alert"; diff --git a/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx b/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx index 83b24d01..70f72025 100644 --- a/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx +++ b/FrontEnd/src/views/timeline-common/TimelinePropertyChangeDialog.tsx @@ -6,7 +6,7 @@ import { HttpTimelinePatchRequest, kTimelineVisibilities, TimelineVisibility, -} from "http/timeline"; +} from "@/http/timeline"; import OperationDialog from "../common/OperationDialog"; diff --git a/FrontEnd/src/views/timeline/TimelineCard.tsx b/FrontEnd/src/views/timeline/TimelineCard.tsx index e031b565..86063843 100644 --- a/FrontEnd/src/views/timeline/TimelineCard.tsx +++ b/FrontEnd/src/views/timeline/TimelineCard.tsx @@ -29,7 +29,7 @@ const TimelineCard: React.FC = (props) => { /> {timeline.owner.nickname} - @{timeline.owner.username} + src{timeline.owner.username} diff --git a/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx b/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx index 8821507d..dbca62ca 100644 --- a/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx +++ b/FrontEnd/src/views/timeline/TimelineDeleteDialog.tsx @@ -2,7 +2,7 @@ import React from "react"; import { useHistory } from "react-router"; import { Trans } from "react-i18next"; -import { getHttpTimelineClient, HttpTimelineInfo } from "http/timeline"; +import { getHttpTimelineClient, HttpTimelineInfo } from "@/http/timeline"; import OperationDialog from "../common/OperationDialog"; diff --git a/FrontEnd/src/views/user/index.tsx b/FrontEnd/src/views/user/index.tsx index 57454d0d..0013b254 100644 --- a/FrontEnd/src/views/user/index.tsx +++ b/FrontEnd/src/views/user/index.tsx @@ -14,7 +14,7 @@ const UserPage: React.FC = () => { return ( <> setReloadKey(reloadKey + 1)} diff --git a/FrontEnd/tsconfig.json b/FrontEnd/tsconfig.json new file mode 100644 index 00000000..3afe2c3e --- /dev/null +++ b/FrontEnd/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": false, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react", + "noEmit": true, + "types": ["vite/client"], + "sourceMap": true, + "baseUrl": "./", + "paths": { + "@/*": ["src/*"] + } + }, + "include": ["./src"] +} diff --git a/FrontEnd/vite.config.js b/FrontEnd/vite.config.js index 6e8cde1b..2e85c36a 100644 --- a/FrontEnd/vite.config.js +++ b/FrontEnd/vite.config.js @@ -7,6 +7,9 @@ import { defineConfig } from "vite"; export default defineConfig({ plugins: [reactRefresh()], + resolve: { + alias: [{ find: "@", replacement: "/src" }], + }, server: { port: 13000, proxy: { -- cgit v1.2.3 From ddce03a67708249eec129eb36744be460345bd75 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 15 Jun 2021 16:23:44 +0800 Subject: ... --- FrontEnd/.eslintrc.js | 2 +- FrontEnd/package.json | 15 -- FrontEnd/postcss.config.js | 5 - FrontEnd/src/index.css | 155 +++++++++++ FrontEnd/src/index.sass | 120 --------- FrontEnd/src/index.tsx | 8 +- FrontEnd/src/palette.ts | 2 + FrontEnd/src/views/about/about.sass | 4 - FrontEnd/src/views/about/index.css | 4 + FrontEnd/src/views/about/index.tsx | 2 + FrontEnd/src/views/admin/Admin.tsx | 2 + FrontEnd/src/views/admin/admin.sass | 22 -- FrontEnd/src/views/admin/index.css | 19 ++ FrontEnd/src/views/center/TimelineBoard.tsx | 17 +- FrontEnd/src/views/center/center.sass | 36 --- FrontEnd/src/views/center/index.css | 73 ++++++ FrontEnd/src/views/center/index.tsx | 2 + FrontEnd/src/views/common/AppBar.tsx | 2 + FrontEnd/src/views/common/FlatButton.tsx | 36 --- FrontEnd/src/views/common/button/FlatButton.css | 45 ++++ FrontEnd/src/views/common/button/FlatButton.tsx | 36 +++ FrontEnd/src/views/common/common.sass | 191 -------------- FrontEnd/src/views/common/index.css | 273 +++++++++++++++++++ FrontEnd/src/views/home/home.sass | 29 --- FrontEnd/src/views/home/index.css | 73 ++++++ FrontEnd/src/views/home/index.tsx | 2 + FrontEnd/src/views/login/index.css | 3 + FrontEnd/src/views/login/index.tsx | 2 + FrontEnd/src/views/login/login.sass | 2 - FrontEnd/src/views/search/index.css | 15 ++ FrontEnd/src/views/search/index.tsx | 2 + FrontEnd/src/views/search/search.sass | 13 - FrontEnd/src/views/settings/index.css | 24 ++ FrontEnd/src/views/settings/index.tsx | 2 + FrontEnd/src/views/settings/settings.sass | 14 - FrontEnd/src/views/timeline-common/Timeline.tsx | 4 +- FrontEnd/src/views/timeline-common/index.css | 289 +++++++++++++++++++++ .../src/views/timeline-common/timeline-common.sass | 259 ------------------ FrontEnd/src/views/timeline/timeline.sass | 0 FrontEnd/src/views/user/index.css | 9 + FrontEnd/src/views/user/index.tsx | 2 + FrontEnd/src/views/user/user.sass | 7 - 42 files changed, 1054 insertions(+), 768 deletions(-) delete mode 100644 FrontEnd/postcss.config.js create mode 100644 FrontEnd/src/index.css delete mode 100644 FrontEnd/src/index.sass delete mode 100644 FrontEnd/src/views/about/about.sass create mode 100644 FrontEnd/src/views/about/index.css delete mode 100644 FrontEnd/src/views/admin/admin.sass create mode 100644 FrontEnd/src/views/admin/index.css delete mode 100644 FrontEnd/src/views/center/center.sass create mode 100644 FrontEnd/src/views/center/index.css delete mode 100644 FrontEnd/src/views/common/FlatButton.tsx create mode 100644 FrontEnd/src/views/common/button/FlatButton.css create mode 100644 FrontEnd/src/views/common/button/FlatButton.tsx delete mode 100644 FrontEnd/src/views/common/common.sass create mode 100644 FrontEnd/src/views/common/index.css delete mode 100644 FrontEnd/src/views/home/home.sass create mode 100644 FrontEnd/src/views/home/index.css create mode 100644 FrontEnd/src/views/login/index.css delete mode 100644 FrontEnd/src/views/login/login.sass create mode 100644 FrontEnd/src/views/search/index.css delete mode 100644 FrontEnd/src/views/search/search.sass create mode 100644 FrontEnd/src/views/settings/index.css delete mode 100644 FrontEnd/src/views/settings/settings.sass create mode 100644 FrontEnd/src/views/timeline-common/index.css delete mode 100644 FrontEnd/src/views/timeline-common/timeline-common.sass delete mode 100644 FrontEnd/src/views/timeline/timeline.sass create mode 100644 FrontEnd/src/views/user/index.css delete mode 100644 FrontEnd/src/views/user/user.sass (limited to 'FrontEnd/src/views/admin') diff --git a/FrontEnd/.eslintrc.js b/FrontEnd/.eslintrc.js index 611b965f..93b98978 100644 --- a/FrontEnd/.eslintrc.js +++ b/FrontEnd/.eslintrc.js @@ -18,7 +18,7 @@ module.exports = { }, parser: "@typescript-eslint/parser", parserOptions: { - project: ["./src/tsconfig.json"], + project: ["./tsconfig.json"], ecmaFeatures: { jsx: true, }, diff --git a/FrontEnd/package.json b/FrontEnd/package.json index 5d12899e..38dd85ff 100644 --- a/FrontEnd/package.json +++ b/FrontEnd/package.json @@ -39,18 +39,6 @@ "lint": "eslint src/ --ext .js --ext .jsx --ext .ts --ext .tsx", "lint:fix": "eslint src/ --ext .js --ext .jsx --ext .ts --ext .tsx --fix" }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - }, "devDependencies": { "@types/color": "^3.0.1", "@types/lodash": "^4.14.170", @@ -70,10 +58,7 @@ "eslint-plugin-prettier": "^3.4.0", "eslint-plugin-react": "^7.24.0", "eslint-plugin-react-hooks": "^4.2.0", - "postcss": "^8.3.0", - "postcss-preset-env": "^6.7.0", "prettier": "^2.3.1", - "sass": "^1.34.1", "typescript": "^4.3.2", "vite": "^2.3.7" } diff --git a/FrontEnd/postcss.config.js b/FrontEnd/postcss.config.js deleted file mode 100644 index 9129aa1f..00000000 --- a/FrontEnd/postcss.config.js +++ /dev/null @@ -1,5 +0,0 @@ -import postcssPresetEnv from "postcss-preset-env"; - -module.exports = { - plugins: [postcssPresetEnv()], -}; diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css new file mode 100644 index 00000000..ca0d4829 --- /dev/null +++ b/FrontEnd/src/index.css @@ -0,0 +1,155 @@ +.tl-color-primary { + color: var(--tl-primary-color); +} + +.tl-color-danger { + color: var(--tl-danger-color); +} + +small { + line-height: 1.2; +} + +.flex-fix-length { + flex-grow: 0; + flex-shrink: 0; +} + +.avatar { + width: 60px; + height: 60px; +} + +.avatar.large { + width: 100px; + height: 100px; +} + +.avatar.small { + width: 40px; + height: 40px; +} + +.icon-button { + font-size: 1.4rem; + cursor: pointer; +} + +.icon-button.large { + font-size: 1.6rem; +} + + + +.cursor-pointer { + cursor: pointer; +} + +textarea { + resize: none; +} + +.white-space-no-wrap { + white-space: nowrap; +} + +.cru-card { + border: 1px solid; + border-color: #e9ecef; + background: #f8f9fa; + transition: all 0.3s; +} + +.cru-card:hover { + border-color: var(--tl-primary-color); +} + +.full-viewport-center-child { + position: fixed; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; +} + +.text-orange { + color: #fd7e14; +} + +.text-yellow { + color: #ffc107; +} + +.text-button { + background: transparent; + border: none; +} +.text-button.primary { + color: #0d6efd; +} +.text-button.primary:hover { + color: #599bfe; +} +.text-button.secondary { + color: #6c757d; +} +.text-button.secondary:hover { + color: #939ba2; +} +.text-button.success { + color: #198754; +} +.text-button.success:hover { + color: #25c87c; +} +.text-button.info { + color: #0dcaf0; +} +.text-button.info:hover { + color: #54dbf6; +} +.text-button.warning { + color: #ffc107; +} +.text-button.warning:hover { + color: #ffd454; +} +.text-button.danger { + color: #dc3545; +} +.text-button.danger:hover { + color: #e77681; +} +.text-button.light { + color: #f8f9fa; +} +.text-button.light:hover { + color: white; +} +.text-button.dark { + color: #212529; +} +.text-button.dark:hover { + color: #434b53; +} + +.touch-action-none { + touch-action: none; +} + +i { + line-height: 1; +} + +.markdown-container { + white-space: initial; +} +.markdown-container img { + max-height: 200px; + max-width: 100%; +} + +a { + text-decoration: none; +} diff --git a/FrontEnd/src/index.sass b/FrontEnd/src/index.sass deleted file mode 100644 index 4cee155f..00000000 --- a/FrontEnd/src/index.sass +++ /dev/null @@ -1,120 +0,0 @@ -@import 'bootstrap/scss/bootstrap' -@import 'bootstrap-icons/font/bootstrap-icons.css' - -@import './views/common/common' -@import './views/common/alert/alert' -@import './views/center/center' -@import './views/home/home' -@import './views/about/about' -@import './views/login/login' -@import './views/settings/settings' -@import './views/timeline-common/timeline-common' -@import './views/timeline/timeline' -@import './views/user/user' -@import './views/search/search' - -@import './views/admin/admin' - -.tl-color-primary - color: var(--tl-primary-color) - -.tl-color-danger - color: var(--tl-danger-color) - -small - line-height: 1.2 - -.flex-fix-length - flex-grow: 0 - flex-shrink: 0 - -.position-lt - left: 0 - top: 0 - -.avatar - width: 60px - height: 60px - &.large - width: 100px - height: 100px - &.small - width: 40px - height: 40px - -.icon-button - font-size: 1.4rem - cursor: pointer - &.large - font-size: 1.6rem - -.flat-button - cursor: pointer - padding: 0.2em 0.5em - border-radius: 0.2em - &:hover:not(.disabled) - background-color: $gray-200 - &.disabled - cursor: default - @each $color, $value in $theme-colors - &.#{$color} - color: $value - &.disabled - color: adjust-color($value, $lightness: +15%) - -.cursor-pointer - cursor: pointer - -textarea - resize: none - -.white-space-no-wrap - white-space: nowrap - -.cru-card - @extend .shadow - @extend .rounded - border: 1px solid - border-color: $gray-200 - background: $gray-100 - transition: all 0.3s - &:hover - border-color: var(--tl-primary-color) - -.full-viewport-center-child - position: fixed - width: 100vw - height: 100vh - display: flex - justify-content: center - align-items: center - -.text-orange - color: $orange - -.text-yellow - color: $yellow - -.text-button - background: transparent - border: none - @each $color, $value in $theme-colors - &.#{$color} - color: $value - &:hover - color: adjust-color($value, $lightness: +15%) - -.touch-action-none - touch-action: none - -i - line-height: 1 - -.markdown-container - white-space: initial - img - max-height: 200px - max-width: 100% - -a - text-decoration: none diff --git a/FrontEnd/src/index.tsx b/FrontEnd/src/index.tsx index fb0c8899..83c25792 100644 --- a/FrontEnd/src/index.tsx +++ b/FrontEnd/src/index.tsx @@ -3,17 +3,19 @@ import "core-js/modules/es.promise"; import "core-js/modules/es.array.iterator"; import "pepjs"; +import "bootstrap/dist/css/bootstrap.css"; +import "bootstrap-icons/font/bootstrap-icons.css"; + import React from "react"; import ReactDOM from "react-dom"; -import "./index.sass"; +import "./index.css"; import "./i18n"; +import "./palette"; import App from "./App"; -import "./palette"; - import { userService } from "./services/user"; void userService.checkLoginState(); diff --git a/FrontEnd/src/palette.ts b/FrontEnd/src/palette.ts index c4f4f4f9..11b3de85 100644 --- a/FrontEnd/src/palette.ts +++ b/FrontEnd/src/palette.ts @@ -30,6 +30,8 @@ export interface Palette { [key: string]: PaletteColor; } +export type PaletteColorType = keyof Palette; + export function generatePaletteColor(color: string): PaletteColor { const c = Color(color); return { diff --git a/FrontEnd/src/views/about/about.sass b/FrontEnd/src/views/about/about.sass deleted file mode 100644 index f4d00cae..00000000 --- a/FrontEnd/src/views/about/about.sass +++ /dev/null @@ -1,4 +0,0 @@ -.about-link-icon - @extend .mx-2 - width: 1.2em - height: 1.2em diff --git a/FrontEnd/src/views/about/index.css b/FrontEnd/src/views/about/index.css new file mode 100644 index 00000000..2574f4b7 --- /dev/null +++ b/FrontEnd/src/views/about/index.css @@ -0,0 +1,4 @@ +.about-link-icon { + width: 1.2em; + height: 1.2em; +} diff --git a/FrontEnd/src/views/about/index.tsx b/FrontEnd/src/views/about/index.tsx index a8a53a97..db4814c4 100644 --- a/FrontEnd/src/views/about/index.tsx +++ b/FrontEnd/src/views/about/index.tsx @@ -4,6 +4,8 @@ import { useTranslation, Trans } from "react-i18next"; import authorAvatarUrl from "./author-avatar.png"; import githubLogoUrl from "./github.png"; +import "./index.css"; + const frontendCredits: { name: string; url: string; diff --git a/FrontEnd/src/views/admin/Admin.tsx b/FrontEnd/src/views/admin/Admin.tsx index 0b6d1f05..34e7e2f6 100644 --- a/FrontEnd/src/views/admin/Admin.tsx +++ b/FrontEnd/src/views/admin/Admin.tsx @@ -9,6 +9,8 @@ import AdminNav from "./AdminNav"; import UserAdmin from "./UserAdmin"; import MoreAdmin from "./MoreAdmin"; +import "./index.css"; + interface AdminProps { user: AuthUser; } diff --git a/FrontEnd/src/views/admin/admin.sass b/FrontEnd/src/views/admin/admin.sass deleted file mode 100644 index 1ce010f8..00000000 --- a/FrontEnd/src/views/admin/admin.sass +++ /dev/null @@ -1,22 +0,0 @@ -.admin-user-item - position: relative - - .edit-mask - position: absolute - top: 0 - left: 0 - bottom: 0 - right: 0 - - background: #ffffffc5 - position: absolute - - display: flex - justify-content: center - align-items: center - - @include media-breakpoint-down(xs) - flex-direction: column - - button - margin: 0.5em 2em diff --git a/FrontEnd/src/views/admin/index.css b/FrontEnd/src/views/admin/index.css new file mode 100644 index 00000000..00917600 --- /dev/null +++ b/FrontEnd/src/views/admin/index.css @@ -0,0 +1,19 @@ +.admin-user-item { + position: relative; +} +.admin-user-item .edit-mask { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: #ffffffc5; + position: absolute; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; +} +.admin-user-item .edit-mask button { + margin: 0.5em 2em; +} diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx index 840c0415..a6a60b3d 100644 --- a/FrontEnd/src/views/center/TimelineBoard.tsx +++ b/FrontEnd/src/views/center/TimelineBoard.tsx @@ -9,6 +9,7 @@ import { HttpTimelineInfo } from "@/http/timeline"; import TimelineLogo from "../common/TimelineLogo"; import UserTimelineLogo from "../common/UserTimelineLogo"; import LoadFailReload from "../common/LoadFailReload"; +import FlatButton from "../common/button/FlatButton"; interface TimelineBoardItemProps { timeline: HttpTimelineInfo; @@ -231,23 +232,19 @@ const TimelineBoardUI: React.FC = (props) => { {title != null &&

{title}

} {editable && (editing ? ( -
{ setEditing(false); }} - > - {t("done")} -
+ /> ) : ( -
{ setEditing(true); }} - > - {t("edit")} -
+ /> ))} {(() => { diff --git a/FrontEnd/src/views/center/center.sass b/FrontEnd/src/views/center/center.sass deleted file mode 100644 index c0dfb9c0..00000000 --- a/FrontEnd/src/views/center/center.sass +++ /dev/null @@ -1,36 +0,0 @@ -.timeline-board - @extend .cru-card - @extend .d-flex - @extend .flex-column - @extend .py-3 - min-height: 200px - height: 100% - position: relative - -.timeline-board-header - @extend .px-3 - display: flex - align-items: center - justify-content: space-between - -.timeline-board-item - font-size: 1.1em - @extend .px-3 - height: 48px - transition: background 0.3s - display: flex - align-items: center - .icon - height: 1.3em - color: black - @extend .me-2 - &:hover - background: $gray-300 - .right - display: flex - align-items: center - flex-shrink: 0 - .title - white-space: nowrap - overflow: hidden - text-overflow: ellipsis diff --git a/FrontEnd/src/views/center/index.css b/FrontEnd/src/views/center/index.css new file mode 100644 index 00000000..516aba52 --- /dev/null +++ b/FrontEnd/src/views/center/index.css @@ -0,0 +1,73 @@ +.timeline-board { + min-height: 200px; + height: 100%; + position: relative; +} + +.timeline-board-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.timeline-board-item { + font-size: 1.1em; + height: 48px; + transition: background 0.3s; + display: flex; + align-items: center; +} +.timeline-board-item .icon { + height: 1.3em; + color: black; +} +.timeline-board-item:hover { + background: #dee2e6; +} +.timeline-board-item .right { + display: flex; + align-items: center; + flex-shrink: 0; +} +.timeline-board-item .title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.home-timeline-list-item { + display: flex; + align-items: center; +} + +.home-timeline-list-item-timeline { + transition: background 0.8s; + animation: 0.8s home-timeline-list-item-timeline-enter; +} +.home-timeline-list-item-timeline:hover { + background: #e9ecef; +} + +@keyframes home-timeline-list-item-timeline-enter { + from { + transform: translate(-100%, 0); + opacity: 0; + } +} +.home-timeline-list-item-line { + width: 80px; + flex-shrink: 0; +} + +@keyframes home-timeline-list-loading-head-animation { + from { + transform: translate(0, -30px); + opacity: 1; + } + to { + opacity: 0; + } +} +.home-timeline-list-loading-head { + animation: 1s infinite home-timeline-list-loading-head-animation; +} diff --git a/FrontEnd/src/views/center/index.tsx b/FrontEnd/src/views/center/index.tsx index 0a2abb2c..28d8b372 100644 --- a/FrontEnd/src/views/center/index.tsx +++ b/FrontEnd/src/views/center/index.tsx @@ -9,6 +9,8 @@ import SearchInput from "../common/SearchInput"; import CenterBoards from "./CenterBoards"; import TimelineCreateDialog from "./TimelineCreateDialog"; +import "./index.css"; + const HomePage: React.FC = () => { const history = useHistory(); diff --git a/FrontEnd/src/views/common/AppBar.tsx b/FrontEnd/src/views/common/AppBar.tsx index 91dfbee9..ebc8bf0c 100644 --- a/FrontEnd/src/views/common/AppBar.tsx +++ b/FrontEnd/src/views/common/AppBar.tsx @@ -9,6 +9,8 @@ import { useUser } from "@/services/user"; import TimelineLogo from "./TimelineLogo"; import UserAvatar from "./user/UserAvatar"; +import "./index.css"; + const AppBar: React.FC = (_) => { const { t } = useTranslation(); diff --git a/FrontEnd/src/views/common/FlatButton.tsx b/FrontEnd/src/views/common/FlatButton.tsx deleted file mode 100644 index b1f7a051..00000000 --- a/FrontEnd/src/views/common/FlatButton.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React from "react"; -import classnames from "classnames"; - -import { BootstrapThemeColor } from "@/common"; - -export interface FlatButtonProps { - variant?: BootstrapThemeColor | string; - disabled?: boolean; - className?: string; - style?: React.CSSProperties; - onClick?: () => void; -} - -const FlatButton: React.FC = (props) => { - const { disabled, className, style } = props; - const variant = props.variant ?? "primary"; - - const onClick = disabled ? undefined : props.onClick; - - return ( -
- {props.children} -
- ); -}; - -export default FlatButton; diff --git a/FrontEnd/src/views/common/button/FlatButton.css b/FrontEnd/src/views/common/button/FlatButton.css new file mode 100644 index 00000000..779e3562 --- /dev/null +++ b/FrontEnd/src/views/common/button/FlatButton.css @@ -0,0 +1,45 @@ +.cru-flat-button { + cursor: pointer; + padding: 0.2em 0.5em; + border-radius: 0.2em; +} + +.cru-flat-button:hover:not(.disabled) { + background-color: #e9ecef; +} + +.cru-flat-button.disabled { + cursor: default; +} + +.cru-flat-button.primary { + color: var(--tl-primary-color); +} + +.cru-flat-button.primary.disabled { + color: var(--tl-primary-lighter-color); +} + +.cru-flat-button.secondary { + color: var(--tl-secondary-color); +} + +.cru-flat-button.secondary.disabled { + color: var(--tl-secondary-lighter-color); +} + +.cru-flat-button.success { + color: var(--tl-success-color); +} + +.cru-flat-button.success.disabled { + color: var(--tl-success-lighter-color); +} + +.cru-flat-button.danger { + color: var(--tl-danger-color); +} + +.cru-flat-button.danger.disabled { + color: var(--tl-danger-ligher-color); +} diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx new file mode 100644 index 00000000..0727eb88 --- /dev/null +++ b/FrontEnd/src/views/common/button/FlatButton.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; + +import { convertI18nText, I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; + +import "./FlatButton.css"; +import classNames from "classnames"; + +function _FlatButton( + { + text, + color, + onClick, + }: { + text: I18nText; + color?: PaletteColorType; + onClick?: () => void; + }, + ref: React.ForwardedRef +): React.ReactElement | null { + const { t } = useTranslation(); + + return ( + + ); +} + +const FlatButton = React.forwardRef(_FlatButton); +export default FlatButton; diff --git a/FrontEnd/src/views/common/common.sass b/FrontEnd/src/views/common/common.sass deleted file mode 100644 index cbf7292e..00000000 --- a/FrontEnd/src/views/common/common.sass +++ /dev/null @@ -1,191 +0,0 @@ -.image-cropper-container - position: relative - box-sizing: border-box - user-select: none - -.image-cropper-container img - position: absolute - left: 0 - top: 0 - width: 100% - height: 100% - -.image-cropper-mask-container - position: absolute - left: 0 - top: 0 - right: 0 - bottom: 0 - overflow: hidden - -.image-cropper-mask - position: absolute - box-shadow: 0 0 0 10000px rgba(255, 255, 255, 80%) - touch-action: none - -.image-cropper-handler - position: absolute - width: 26px - height: 26px - border: black solid 2px - border-radius: 50% - background: white - touch-action: none - -.app-bar - display: flex - align-items: center - height: 56px - - position: fixed - z-index: 1030 - top: 0 - left: 0 - right: 0 - - background-color: var(--tl-primary-color) - - transition: background-color 1s - - a - color: var(--tl-text-on-primary-inactive-color) - text-decoration: none - margin: 0 1em - - &:hover - color: var(--tl-text-on-primary-color) - - &.active - color: var(--tl-text-on-primary-color) - -.app-bar-brand - display: flex - align-items: center - -.app-bar-brand-icon - height: 2em - -.app-bar-main-area - display: flex - flex-grow: 1 - -.app-bar-link-area - display: flex - align-items: center - flex-shrink: 0 - -.app-bar-user-area - display: flex - align-items: center - flex-shrink: 0 - margin-left: auto - -.small-screen - .app-bar-main-area - position: absolute - top: 56px - left: 0 - right: 0 - - transform-origin: top - transition: transform 0.6s, background-color 1s - - background-color: var(--tl-primary-color) - - flex-direction: column - - &.app-bar-collapse - transform: scale(1,0) - - a - text-align: left - padding: 0.5em 0.5em - - .app-bar-link-area - flex-direction: column - align-items: stretch - - .app-bar-user-area - flex-direction: column - align-items: stretch - margin-left: unset - - .app-bar-avatar - align-self: flex-end - -.app-bar-toggler - margin-left: auto - font-size: 2em - margin-right: 1em - color: var(--tl-text-on-primary-color) - cursor: pointer - user-select: none - -.cru-skeleton - padding: 0 1em - -.cru-skeleton-line - height: 1em - background-color: #e6e6e6 - margin: 0.7em 0 - border-radius: 0.2em - - &.last - width: 50% - -.cru-full-page - position: fixed - z-index: 1031 - left: 0 - top: 0 - right: 0 - bottom: 0 - background-color: white - padding-top: 56px - -.cru-full-page-top-bar - height: 56px - - position: absolute - top: 0 - left: 0 - right: 0 - z-index: 1 - - background-color: var(--tl-primary-color) - - display: flex - align-items: center - -.cru-full-page-content-container - overflow: scroll - -.cru-menu - min-width: 200px - -.cru-menu-item - font-size: 1.2em - padding: 0.5em 1.5em - cursor: pointer - - @each $color, $value in $theme-colors - &.color-#{$color} - color: $value - - &:hover - color: white - background-color: $value - -.cru-menu-item-icon - margin-right: 1em - -.cru-menu-divider - border-top: 1px solid $gray-200 - -.cru-tab-pages-action-area - display: flex - align-items: center - -.cru-search-input - display: flex - flex-wrap: wrap diff --git a/FrontEnd/src/views/common/index.css b/FrontEnd/src/views/common/index.css new file mode 100644 index 00000000..bfd82b58 --- /dev/null +++ b/FrontEnd/src/views/common/index.css @@ -0,0 +1,273 @@ +.image-cropper-container { + position: relative; + box-sizing: border-box; + user-select: none; +} + +.image-cropper-container img { + position: absolute; + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.image-cropper-mask-container { + position: absolute; + left: 0; + top: 0; + right: 0; + bottom: 0; + overflow: hidden; +} + +.image-cropper-mask { + position: absolute; + box-shadow: 0 0 0 10000px rgba(255, 255, 255, 0.8); + touch-action: none; +} + +.image-cropper-handler { + position: absolute; + width: 26px; + height: 26px; + border: black solid 2px; + border-radius: 50%; + background: white; + touch-action: none; +} + +.app-bar { + display: flex; + align-items: center; + height: 56px; + position: fixed; + z-index: 1030; + top: 0; + left: 0; + right: 0; + background-color: var(--tl-primary-color); + transition: background-color 1s; +} +.app-bar a { + color: var(--tl-text-on-primary-inactive-color); + text-decoration: none; + margin: 0 1em; +} +.app-bar a:hover { + color: var(--tl-text-on-primary-color); +} +.app-bar a.active { + color: var(--tl-text-on-primary-color); +} + +.app-bar-brand { + display: flex; + align-items: center; +} + +.app-bar-brand-icon { + height: 2em; +} + +.app-bar-main-area { + display: flex; + flex-grow: 1; +} + +.app-bar-link-area { + display: flex; + align-items: center; + flex-shrink: 0; +} + +.app-bar-user-area { + display: flex; + align-items: center; + flex-shrink: 0; + margin-left: auto; +} + +.small-screen .app-bar-main-area { + position: absolute; + top: 56px; + left: 0; + right: 0; + transform-origin: top; + transition: transform 0.6s, background-color 1s; + background-color: var(--tl-primary-color); + flex-direction: column; +} +.small-screen .app-bar-main-area.app-bar-collapse { + transform: scale(1, 0); +} +.small-screen .app-bar-main-area a { + text-align: left; + padding: 0.5em 0.5em; +} +.small-screen .app-bar-link-area { + flex-direction: column; + align-items: stretch; +} +.small-screen .app-bar-user-area { + flex-direction: column; + align-items: stretch; + margin-left: unset; +} +.small-screen .app-bar-avatar { + align-self: flex-end; +} + +.app-bar-toggler { + margin-left: auto; + font-size: 2em; + margin-right: 1em; + color: var(--tl-text-on-primary-color); + cursor: pointer; + user-select: none; +} + +.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-full-page { + position: fixed; + z-index: 1031; + left: 0; + top: 0; + right: 0; + bottom: 0; + background-color: white; + padding-top: 56px; +} + +.cru-full-page-top-bar { + height: 56px; + position: absolute; + top: 0; + left: 0; + right: 0; + z-index: 1; + background-color: var(--tl-primary-color); + display: flex; + align-items: center; +} + +.cru-full-page-content-container { + overflow: scroll; +} + +.cru-menu { + min-width: 200px; +} + +.cru-menu-item { + font-size: 1.2em; + padding: 0.5em 1.5em; + cursor: pointer; +} +.cru-menu-item.color-primary { + color: #0d6efd; +} +.cru-menu-item.color-primary:hover { + color: white; + background-color: #0d6efd; +} +.cru-menu-item.color-secondary { + color: #6c757d; +} +.cru-menu-item.color-secondary:hover { + color: white; + background-color: #6c757d; +} +.cru-menu-item.color-success { + color: #198754; +} +.cru-menu-item.color-success:hover { + color: white; + background-color: #198754; +} +.cru-menu-item.color-info { + color: #0dcaf0; +} +.cru-menu-item.color-info:hover { + color: white; + background-color: #0dcaf0; +} +.cru-menu-item.color-warning { + color: #ffc107; +} +.cru-menu-item.color-warning:hover { + color: white; + background-color: #ffc107; +} +.cru-menu-item.color-danger { + color: #dc3545; +} +.cru-menu-item.color-danger:hover { + color: white; + background-color: #dc3545; +} +.cru-menu-item.color-light { + color: #f8f9fa; +} +.cru-menu-item.color-light:hover { + color: white; + background-color: #f8f9fa; +} +.cru-menu-item.color-dark { + color: #212529; +} +.cru-menu-item.color-dark:hover { + color: white; + background-color: #212529; +} + +.cru-menu-item-icon { + margin-right: 1em; +} + +.cru-menu-divider { + border-top: 1px solid #e9ecef; +} + +.cru-tab-pages-action-area { + display: flex; + align-items: center; +} + +.cru-search-input { + display: flex; + flex-wrap: wrap; +} + +.alert-container { + position: fixed; + z-index: 1070; +} + +@media (min-width: 576px) { + .alert-container { + bottom: 0; + right: 0; + } +} +@media (max-width: 575.98px) { + .alert-container { + bottom: 0; + right: 0; + left: 0; + text-align: center; + } +} diff --git a/FrontEnd/src/views/home/home.sass b/FrontEnd/src/views/home/home.sass deleted file mode 100644 index b4cda586..00000000 --- a/FrontEnd/src/views/home/home.sass +++ /dev/null @@ -1,29 +0,0 @@ -.home-timeline-list-item - display: flex - align-items: center - -.home-timeline-list-item-timeline - transition: background 0.8s - animation: 0.8s home-timeline-list-item-timeline-enter - &:hover - background: $gray-200 - -@keyframes home-timeline-list-item-timeline-enter - from - transform: translate(-100%,0) - opacity: 0 - -.home-timeline-list-item-line - width: 80px - flex-shrink: 0 - -@keyframes home-timeline-list-loading-head-animation - from - transform: translate(0,-30px) - opacity: 1 - - to - opacity: 0 - -.home-timeline-list-loading-head - animation: 1s infinite home-timeline-list-loading-head-animation diff --git a/FrontEnd/src/views/home/index.css b/FrontEnd/src/views/home/index.css new file mode 100644 index 00000000..516aba52 --- /dev/null +++ b/FrontEnd/src/views/home/index.css @@ -0,0 +1,73 @@ +.timeline-board { + min-height: 200px; + height: 100%; + position: relative; +} + +.timeline-board-header { + display: flex; + align-items: center; + justify-content: space-between; +} + +.timeline-board-item { + font-size: 1.1em; + height: 48px; + transition: background 0.3s; + display: flex; + align-items: center; +} +.timeline-board-item .icon { + height: 1.3em; + color: black; +} +.timeline-board-item:hover { + background: #dee2e6; +} +.timeline-board-item .right { + display: flex; + align-items: center; + flex-shrink: 0; +} +.timeline-board-item .title { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.home-timeline-list-item { + display: flex; + align-items: center; +} + +.home-timeline-list-item-timeline { + transition: background 0.8s; + animation: 0.8s home-timeline-list-item-timeline-enter; +} +.home-timeline-list-item-timeline:hover { + background: #e9ecef; +} + +@keyframes home-timeline-list-item-timeline-enter { + from { + transform: translate(-100%, 0); + opacity: 0; + } +} +.home-timeline-list-item-line { + width: 80px; + flex-shrink: 0; +} + +@keyframes home-timeline-list-loading-head-animation { + from { + transform: translate(0, -30px); + opacity: 1; + } + to { + opacity: 0; + } +} +.home-timeline-list-loading-head { + animation: 1s infinite home-timeline-list-loading-head-animation; +} diff --git a/FrontEnd/src/views/home/index.tsx b/FrontEnd/src/views/home/index.tsx index 0eca23ee..ddb72e76 100644 --- a/FrontEnd/src/views/home/index.tsx +++ b/FrontEnd/src/views/home/index.tsx @@ -8,6 +8,8 @@ import SearchInput from "../common/SearchInput"; import TimelineListView from "./TimelineListView"; import WebsiteIntroduction from "./WebsiteIntroduction"; +import "./index.css"; + const highlightTimelineMessageMap = { loading: "home.loadingHighlightTimelines", done: "home.loadedHighlightTimelines", diff --git a/FrontEnd/src/views/login/index.css b/FrontEnd/src/views/login/index.css new file mode 100644 index 00000000..dca7054d --- /dev/null +++ b/FrontEnd/src/views/login/index.css @@ -0,0 +1,3 @@ +.login-container { + max-width: 600px; +} diff --git a/FrontEnd/src/views/login/index.tsx b/FrontEnd/src/views/login/index.tsx index 6adcef39..a39a9972 100644 --- a/FrontEnd/src/views/login/index.tsx +++ b/FrontEnd/src/views/login/index.tsx @@ -8,6 +8,8 @@ import { useUser, userService } from "@/services/user"; import AppBar from "../common/AppBar"; import LoadingButton from "../common/LoadingButton"; +import "./index.css"; + const LoginPage: React.FC = (_) => { const { t } = useTranslation(); const history = useHistory(); diff --git a/FrontEnd/src/views/login/login.sass b/FrontEnd/src/views/login/login.sass deleted file mode 100644 index 0bf385f5..00000000 --- a/FrontEnd/src/views/login/login.sass +++ /dev/null @@ -1,2 +0,0 @@ -.login-container - max-width: 600px diff --git a/FrontEnd/src/views/search/index.css b/FrontEnd/src/views/search/index.css new file mode 100644 index 00000000..6ff4d9fa --- /dev/null +++ b/FrontEnd/src/views/search/index.css @@ -0,0 +1,15 @@ +.timeline-search-result-item { + border: 1px solid; + border-color: #e9ecef; + background: #f8f9fa; + transition: all 0.3s; +} +.timeline-search-result-item:hover { + border-color: #0d6efd; +} + +.timeline-search-result-item-avatar { + width: 2em; + height: 2em; + border-radius: 50%; +} diff --git a/FrontEnd/src/views/search/index.tsx b/FrontEnd/src/views/search/index.tsx index 9a26802d..f5018c3e 100644 --- a/FrontEnd/src/views/search/index.tsx +++ b/FrontEnd/src/views/search/index.tsx @@ -11,6 +11,8 @@ import { HttpTimelineInfo } from "@/http/timeline"; import SearchInput from "../common/SearchInput"; import UserAvatar from "../common/user/UserAvatar"; +import "./index.css"; + const TimelineSearchResultItemView: React.FC<{ timeline: HttpTimelineInfo; }> = ({ timeline }) => { diff --git a/FrontEnd/src/views/search/search.sass b/FrontEnd/src/views/search/search.sass deleted file mode 100644 index 83f297fe..00000000 --- a/FrontEnd/src/views/search/search.sass +++ /dev/null @@ -1,13 +0,0 @@ -.timeline-search-result-item - @extend .rounded - border: 1px solid - border-color: $gray-200 - background: $gray-100 - transition: all 0.3s - &:hover - border-color: $primary - -.timeline-search-result-item-avatar - width: 2em - height: 2em - border-radius: 50% diff --git a/FrontEnd/src/views/settings/index.css b/FrontEnd/src/views/settings/index.css new file mode 100644 index 00000000..566d501b --- /dev/null +++ b/FrontEnd/src/views/settings/index.css @@ -0,0 +1,24 @@ +.change-avatar-cropper-row { + max-height: 400px; +} + +.change-avatar-img { + min-width: 50%; + max-width: 100%; + max-height: 400px; +} + +.settings-item { + padding: 0.5em 1em; + transition: background 0.3s; + border-bottom: 1px solid #e9ecef; +} +.settings-item.first { + border-top: 1px solid #e9ecef; +} +.settings-item.clickable { + cursor: pointer; +} +.settings-item:hover { + background: #dee2e6; +} diff --git a/FrontEnd/src/views/settings/index.tsx b/FrontEnd/src/views/settings/index.tsx index 04a2777a..f0bed222 100644 --- a/FrontEnd/src/views/settings/index.tsx +++ b/FrontEnd/src/views/settings/index.tsx @@ -9,6 +9,8 @@ import ChangePasswordDialog from "./ChangePasswordDialog"; import ChangeAvatarDialog from "./ChangeAvatarDialog"; import ChangeNicknameDialog from "./ChangeNicknameDialog"; +import "./index.css"; + const ConfirmLogoutDialog: React.FC<{ onClose: () => void; onConfirm: () => void; diff --git a/FrontEnd/src/views/settings/settings.sass b/FrontEnd/src/views/settings/settings.sass deleted file mode 100644 index 8c6d24b8..00000000 --- a/FrontEnd/src/views/settings/settings.sass +++ /dev/null @@ -1,14 +0,0 @@ -.settings-item - padding: 0.5em 1em - transition: background 0.3s - border-bottom: 1px solid $gray-200 - - &.first - border-top: 1px solid $gray-200 - - &.clickable - cursor: pointer - - &:hover - background: $gray-300 - diff --git a/FrontEnd/src/views/timeline-common/Timeline.tsx b/FrontEnd/src/views/timeline-common/Timeline.tsx index 31ea5870..21daa5e2 100644 --- a/FrontEnd/src/views/timeline-common/Timeline.tsx +++ b/FrontEnd/src/views/timeline-common/Timeline.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { HubConnectionState } from "srcmicrosoft/signalr"; +import { HubConnectionState } from "@microsoft/signalr"; import { HttpForbiddenError, @@ -14,6 +14,8 @@ import TimelinePagedPostListView from "./TimelinePagedPostListView"; import TimelineTop from "./TimelineTop"; import TimelineLoading from "./TimelineLoading"; +import "./index.css"; + export interface TimelineProps { className?: string; style?: React.CSSProperties; diff --git a/FrontEnd/src/views/timeline-common/index.css b/FrontEnd/src/views/timeline-common/index.css new file mode 100644 index 00000000..89399961 --- /dev/null +++ b/FrontEnd/src/views/timeline-common/index.css @@ -0,0 +1,289 @@ +.timeline { + z-index: 0; + position: relative; + width: 100%; + overflow-wrap: break-word; + animation: 1s timeline-enter; +} + +@keyframes timeline-line-node-noncurrent { + to { + box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color); + } +} +@keyframes timeline-line-node-current { + to { + box-shadow: 0 0 20px 3px var(--tl-primary-enhance-lighter-color); + } +} +@keyframes timeline-line-node-loading { + to { + box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color); + } +} +@keyframes timeline-line-node-loading-edge { + from { + transform: rotate(0turn); + } + to { + transform: rotate(1turn); + } +} +@keyframes timeline-enter { + from { + transform: translate(0, -100vh); + } +} +@keyframes timeline-top-loading-enter { + from { + transform: translate(0, -100%); + } +} +@keyframes timeline-post-enter { + from { + transform: translate(0, -100%); + opacity: 0; + } + to { + opacity: 1; + } +} +.timeline-top-loading-enter { + animation: 1s timeline-top-loading-enter; +} + +.timeline-line { + display: flex; + flex-direction: column; + align-items: center; + width: 30px; + position: absolute; + z-index: 1; + left: 2em; + top: 0; + bottom: 0; + transition: left 0.5s; +} +@media (max-width: 575.98px) { + .timeline-line { + left: 1em; + } +} +.timeline-line .segment { + width: 7px; + background: var(--tl-primary-color); +} +.timeline-line .segment.start { + height: 1.8em; + flex: 0 0 auto; +} +.timeline-line .segment.end { + flex: 1 1 auto; +} +.timeline-line .segment.current-end { + height: 2em; + flex: 0 0 auto; + background: linear-gradient(var(--tl-primary-enhance-color), white); +} +.timeline-line .node-container { + flex: 0 0 auto; + position: relative; + width: 18px; + height: 18px; +} +.timeline-line .node { + width: 20px; + height: 20px; + position: absolute; + background: var(--tl-primary-color); + left: -1px; + top: -1px; + border-radius: 50%; + box-sizing: border-box; + z-index: 1; + animation: 1s infinite alternate; + animation-name: timeline-line-node-noncurrent; +} +.timeline-line .node-loading-edge { + color: var(--tl-primary-color); + width: 38px; + height: 38px; + position: absolute; + left: -10px; + top: -10px; + box-sizing: border-box; + z-index: 2; + animation: 1.5s linear infinite timeline-line-node-loading-edge; +} +.timeline-line.current .segment.start { + background: linear-gradient( + var(--tl-primary-color), + var(--tl-primary-enhance-color) + ); +} +.timeline-line.current .segment.end { + background: var(--tl-primary-enhance-color); +} +.timeline-line.current .node { + background: var(--tl-primary-enhance-color); + animation-name: timeline-line-node-current; +} +.timeline-line.loading .node { + background: var(--tl-primary-color); + animation-name: timeline-line-node-loading; +} + +.timeline-item.current { + padding-bottom: 2.5em; +} + +.timeline-top { + position: relative; + text-align: right; +} + +.timeline-item { + position: relative; + padding: 0.5em; +} + +.timeline-item-card { + position: relative; + padding: 0.3em 0.5em 1em 4em; + transition: background 0.5s, padding-left 0.5s; + animation: 0.6s forwards; + opacity: 0; +} +@media (max-width: 575.98px) { + .timeline-item-card { + padding-left: 3em; + } +} + +.timeline-item-header { + display: flex; + align-items: center; +} + +.timeline-avatar { + border-radius: 50%; + width: 2em; + height: 2em; +} + +.timeline-item-delete-button { + position: absolute; + right: 0; + bottom: 0; +} + +.timeline-content { + white-space: pre-line; +} + +.timeline-content-image { + max-width: 80%; + max-height: 200px; +} + +.timeline-date-item { + position: relative; + padding: 0.3em 0 0.3em 4em; +} + +.timeline-date-item-badge { + display: inline-block; + padding: 0.1em 0.4em; + border-radius: 0.4em; + background: #7c7c7c; + color: white; + font-size: 0.8em; +} + +.timeline-post-edit-image { + max-width: 100px; + max-height: 100px; +} + +.mask { + background: rgba(255, 255, 255, 0.8); + z-index: 100; +} + +.timeline-sync-state-badge { + font-size: 0.8em; + padding: 3px 8px; + border-radius: 5px; + background: #e8fbff; +} + +.timeline-sync-state-badge-pin { + display: inline-block; + width: 0.4em; + height: 0.4em; + border-radius: 50%; + vertical-align: middle; + margin-right: 0.6em; +} + +.timeline-template-card { + position: fixed; + top: 56px; + right: 0; + margin: 0.5em; +} + +.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; +} + +.connection-status-badge { + font-size: 0.8em; + border-radius: 5px; + padding: 0.1em 1em; + background-color: #eaf2ff; +} +.connection-status-badge::before { + width: 10px; + height: 10px; + border-radius: 50%; + display: inline-block; + content: ""; + margin-right: 0.6em; +} +.connection-status-badge.success { + color: #006100; +} +.connection-status-badge.success::before { + background-color: #006100; +} +.connection-status-badge.warning { + color: #e4a700; +} +.connection-status-badge.warning::before { + background-color: #e4a700; +} +.connection-status-badge.danger { + color: #fd1616; +} +.connection-status-badge.danger::before { + background-color: #fd1616; +} diff --git a/FrontEnd/src/views/timeline-common/timeline-common.sass b/FrontEnd/src/views/timeline-common/timeline-common.sass deleted file mode 100644 index 4400fead..00000000 --- a/FrontEnd/src/views/timeline-common/timeline-common.sass +++ /dev/null @@ -1,259 +0,0 @@ -@use 'sass:color' - -.timeline - z-index: 0 - position: relative - width: 100% - overflow-wrap: break-word - animation: 1s timeline-enter - -$timeline-line-width: 7px -$timeline-line-node-radius: 18px -$timeline-line-color: var(--tl-primary-color) -$timeline-line-color-current: var(--tl-primary-enhance-color) - -@keyframes timeline-line-node-noncurrent - to - box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color) - -@keyframes timeline-line-node-current - to - box-shadow: 0 0 20px 3px var(--tl-primary-enhance-lighter-color) - -@keyframes timeline-line-node-loading - to - box-shadow: 0 0 20px 3px var(--tl-primary-lighter-color) - -@keyframes timeline-line-node-loading-edge - from - transform: rotate(0turn) - to - transform: rotate(1turn) - -@keyframes timeline-enter - from - transform: translate(0, -100vh) - -@keyframes timeline-top-loading-enter - from - transform: translate(0, -100%) - -@keyframes timeline-post-enter - from - transform: translate(0, -100%) - opacity: 0 - - to - opacity: 1 - -.timeline-top-loading-enter - animation: 1s timeline-top-loading-enter - -.timeline-line - display: flex - flex-direction: column - align-items: center - width: 30px - - position: absolute - z-index: 1 - left: 2em - top: 0 - bottom: 0 - - transition: left 0.5s - - @include media-breakpoint-down(sm) - left: 1em - - .segment - width: $timeline-line-width - background: $timeline-line-color - - &.start - height: 1.8em - flex: 0 0 auto - - &.end - flex: 1 1 auto - - &.current-end - height: 2em - flex: 0 0 auto - background: linear-gradient($timeline-line-color-current, white) - - .node-container - flex: 0 0 auto - position: relative - width: $timeline-line-node-radius - height: $timeline-line-node-radius - - .node - width: $timeline-line-node-radius + 2 - height: $timeline-line-node-radius + 2 - position: absolute - background: $timeline-line-color - left: -1px - top: -1px - border-radius: 50% - box-sizing: border-box - z-index: 1 - animation: 1s infinite alternate - animation-name: timeline-line-node-noncurrent - - .node-loading-edge - color: $timeline-line-color - width: $timeline-line-node-radius + 20 - height: $timeline-line-node-radius + 20 - position: absolute - left: -10px - top: -10px - box-sizing: border-box - z-index: 2 - animation: 1.5s linear infinite timeline-line-node-loading-edge - - &.current - .segment - &.start - background: linear-gradient($timeline-line-color, $timeline-line-color-current) - &.end - background: $timeline-line-color-current - .node - background: $timeline-line-color-current - animation-name: timeline-line-node-current - - &.loading - .node - background: $timeline-line-color - animation-name: timeline-line-node-loading - -.timeline-item.current - padding-bottom: 2.5em - -.timeline-top - position: relative - text-align: right - -.timeline-item - position: relative - padding: 0.5em - -.timeline-item-card - @extend .cru-card - position: relative - padding: 0.3em 0.5em 1em 4em - transition: background 0.5s, padding-left 0.5s - animation: 0.6s forwards - opacity: 0 - - @include media-breakpoint-down(sm) - padding-left: 3em - -.timeline-item-header - display: flex - align-items: center - @extend .my-2 - -.timeline-avatar - border-radius: 50% - width: 2em - height: 2em - -.timeline-item-delete-button - position: absolute - right: 0 - bottom: 0 - -.timeline-content - white-space: pre-line - -.timeline-content-image - max-width: 80% - max-height: 200px - -.timeline-date-item - position: relative - padding: 0.3em 0 0.3em 4em - -.timeline-date-item-badge - display: inline-block - padding: 0.1em 0.4em - border-radius: 0.4em - background: #7c7c7c - color: white - font-size: 0.8em - -.timeline-post-edit-image - max-width: 100px - max-height: 100px - -.mask - background: change-color($color: white, $alpha: 0.8) - z-index: 100 - -.timeline-sync-state-badge - font-size: 0.8em - padding: 3px 8px - border-radius: 5px - background: #e8fbff - -.timeline-sync-state-badge-pin - display: inline-block - width: 0.4em - height: 0.4em - border-radius: 50% - vertical-align: middle - margin-right: 0.6em - -.timeline-template-card - position: fixed - top: 56px - right: 0 - margin: 0.5em - -.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 - -.connection-status-badge - font-size: 0.8em - border-radius: 5px - padding: 0.1em 1em - background-color: rgb(234 242 255) - - &::before - width: 10px - height: 10px - border-radius: 50% - display: inline-block - content: '' - margin-right: 0.6em - - &.success - color: #006100 - &::before - background-color: #006100 - - &.warning - color: #e4a700 - &::before - background-color: #e4a700 - - &.danger - color: #fd1616 - &::before - background-color: #fd1616 diff --git a/FrontEnd/src/views/timeline/timeline.sass b/FrontEnd/src/views/timeline/timeline.sass deleted file mode 100644 index e69de29b..00000000 diff --git a/FrontEnd/src/views/user/index.css b/FrontEnd/src/views/user/index.css new file mode 100644 index 00000000..35f01d38 --- /dev/null +++ b/FrontEnd/src/views/user/index.css @@ -0,0 +1,9 @@ +.change-avatar-cropper-row { + max-height: 400px; +} + +.change-avatar-img { + min-width: 50%; + max-width: 100%; + max-height: 400px; +} diff --git a/FrontEnd/src/views/user/index.tsx b/FrontEnd/src/views/user/index.tsx index 0013b254..1f2fe9ed 100644 --- a/FrontEnd/src/views/user/index.tsx +++ b/FrontEnd/src/views/user/index.tsx @@ -4,6 +4,8 @@ import { useParams } from "react-router"; import TimelinePageTemplate from "../timeline-common/TimelinePageTemplate"; import UserCard from "./UserCard"; +import "./index.css"; + const UserPage: React.FC = () => { const { username } = useParams<{ username: string }>(); diff --git a/FrontEnd/src/views/user/user.sass b/FrontEnd/src/views/user/user.sass deleted file mode 100644 index 63a28e05..00000000 --- a/FrontEnd/src/views/user/user.sass +++ /dev/null @@ -1,7 +0,0 @@ -.change-avatar-cropper-row - max-height: 400px - -.change-avatar-img - min-width: 50% - max-width: 100% - max-height: 400px -- cgit v1.2.3 From 8c56e3fd388005bcb7aced75b73d7018511ceac8 Mon Sep 17 00:00:00 2001 From: crupest Date: Tue, 15 Jun 2021 16:41:03 +0800 Subject: ... --- FrontEnd/src/index.css | 53 ------------------------- FrontEnd/src/palette.ts | 43 ++++++++++---------- FrontEnd/src/views/admin/UserAdmin.tsx | 20 +++++----- FrontEnd/src/views/center/TimelineBoard.tsx | 3 -- FrontEnd/src/views/common/button/FlatButton.tsx | 2 +- FrontEnd/src/views/common/button/TextButton.css | 36 +++++++++++++++++ FrontEnd/src/views/common/button/TextButton.tsx | 36 +++++++++++++++++ 7 files changed, 105 insertions(+), 88 deletions(-) create mode 100644 FrontEnd/src/views/common/button/TextButton.css create mode 100644 FrontEnd/src/views/common/button/TextButton.tsx (limited to 'FrontEnd/src/views/admin') diff --git a/FrontEnd/src/index.css b/FrontEnd/src/index.css index ca0d4829..ff8c1866 100644 --- a/FrontEnd/src/index.css +++ b/FrontEnd/src/index.css @@ -81,59 +81,6 @@ textarea { color: #ffc107; } -.text-button { - background: transparent; - border: none; -} -.text-button.primary { - color: #0d6efd; -} -.text-button.primary:hover { - color: #599bfe; -} -.text-button.secondary { - color: #6c757d; -} -.text-button.secondary:hover { - color: #939ba2; -} -.text-button.success { - color: #198754; -} -.text-button.success:hover { - color: #25c87c; -} -.text-button.info { - color: #0dcaf0; -} -.text-button.info:hover { - color: #54dbf6; -} -.text-button.warning { - color: #ffc107; -} -.text-button.warning:hover { - color: #ffd454; -} -.text-button.danger { - color: #dc3545; -} -.text-button.danger:hover { - color: #e77681; -} -.text-button.light { - color: #f8f9fa; -} -.text-button.light:hover { - color: white; -} -.text-button.dark { - color: #212529; -} -.text-button.dark:hover { - color: #434b53; -} - .touch-action-none { touch-action: none; } diff --git a/FrontEnd/src/palette.ts b/FrontEnd/src/palette.ts index 11b3de85..6385df66 100644 --- a/FrontEnd/src/palette.ts +++ b/FrontEnd/src/palette.ts @@ -19,18 +19,19 @@ export interface PaletteColor { [key: string]: string; } -export interface Palette { - primary: PaletteColor; - primaryEnhance: PaletteColor; - secondary: PaletteColor; - textPrimary: PaletteColor; - textOnPrimary: PaletteColor; - danger: PaletteColor; - success: PaletteColor; - [key: string]: PaletteColor; -} +const paletteColorList = [ + "primary", + "primary-enhance", + "secondary", + "text-primary", + "text-on-primary", + "danger", + "success", +] as const; + +export type PaletteColorType = typeof paletteColorList[number]; -export type PaletteColorType = keyof Palette; +export type Palette = Record; export function generatePaletteColor(color: string): PaletteColor { const c = Color(color); @@ -60,26 +61,24 @@ export function generatePalette(options: { return { primary: generatePaletteColor(p.toString()), - primaryEnhance: generatePaletteColor(pe.toString()), + "primary-enhance": generatePaletteColor(pe.toString()), secondary: generatePaletteColor(s.toString()), - textPrimary: generatePaletteColor("#111111"), - textOnPrimary: generatePaletteColor(p.lightness() > 60 ? "black" : "white"), + "text-primary": generatePaletteColor("#111111"), + "text-on-primary": generatePaletteColor( + p.lightness() > 60 ? "black" : "white" + ), danger: generatePaletteColor("red"), success: generatePaletteColor("green"), }; } export function generatePaletteCSS(palette: Palette): string { - function toSnakeCase(s: string): string { - return s.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`); - } - const colors: [string, string][] = []; - for (const paletteColorName in palette) { - const paletteColor = palette[paletteColorName]; + for (const colorType of paletteColorList) { + const paletteColor = palette[colorType]; for (const variant in paletteColor) { - let key = `--tl-${toSnakeCase(paletteColorName)}`; - if (variant !== "color") key += `-${toSnakeCase(variant)}`; + let key = `--tl-${colorType}`; + if (variant !== "color") key += `-${variant}`; key += "-color"; colors.push([key, paletteColor[variant]]); } diff --git a/FrontEnd/src/views/admin/UserAdmin.tsx b/FrontEnd/src/views/admin/UserAdmin.tsx index 558d3aee..eb141520 100644 --- a/FrontEnd/src/views/admin/UserAdmin.tsx +++ b/FrontEnd/src/views/admin/UserAdmin.tsx @@ -14,6 +14,7 @@ import { UserPermission, } from "@/http/user"; import { Trans, useTranslation } from "react-i18next"; +import TextButton from "../common/button/TextButton"; interface DialogProps { open: boolean; @@ -230,15 +231,16 @@ const UserItem: React.FC = ({ user, on }) => { className={classnames("edit-mask", !editMaskVisible && "d-none")} onClick={() => setEditMaskVisible(false)} > - - - + + + ); diff --git a/FrontEnd/src/views/center/TimelineBoard.tsx b/FrontEnd/src/views/center/TimelineBoard.tsx index a6a60b3d..e0a2d80e 100644 --- a/FrontEnd/src/views/center/TimelineBoard.tsx +++ b/FrontEnd/src/views/center/TimelineBoard.tsx @@ -1,7 +1,6 @@ import React from "react"; import classnames from "classnames"; import { Link } from "react-router-dom"; -import { useTranslation } from "react-i18next"; import { Spinner } from "react-bootstrap"; import { HttpTimelineInfo } from "@/http/timeline"; @@ -220,8 +219,6 @@ interface TimelineBoardUIProps { const TimelineBoardUI: React.FC = (props) => { const { title, timelines, className, editHandler } = props; - const { t } = useTranslation(); - const editable = editHandler != null; const [editing, setEditing] = React.useState(false); diff --git a/FrontEnd/src/views/common/button/FlatButton.tsx b/FrontEnd/src/views/common/button/FlatButton.tsx index 0727eb88..24f47785 100644 --- a/FrontEnd/src/views/common/button/FlatButton.tsx +++ b/FrontEnd/src/views/common/button/FlatButton.tsx @@ -1,11 +1,11 @@ import React from "react"; import { useTranslation } from "react-i18next"; +import classNames from "classnames"; import { convertI18nText, I18nText } from "@/common"; import { PaletteColorType } from "@/palette"; import "./FlatButton.css"; -import classNames from "classnames"; function _FlatButton( { diff --git a/FrontEnd/src/views/common/button/TextButton.css b/FrontEnd/src/views/common/button/TextButton.css new file mode 100644 index 00000000..dc5abaaa --- /dev/null +++ b/FrontEnd/src/views/common/button/TextButton.css @@ -0,0 +1,36 @@ +.cru-text-button { + background: transparent; + border: none; +} + +.cru-text-button.primary { + color: var(--tl-primary-color); +} + +.cru-text-button.primary:hover { + color: var(--tl-primary-lighter-color); +} + +.cru-text-button.secondary { + color: var(--tl-secondary-color); +} + +.cru-text-button.secondary:hover { + color: var(--tl-secondary-lighter-color); +} + +.cru-text-button.success { + color: var(--tl-success-color); +} + +.cru-text-button.success:hover { + color: var(--tl-success-lighter-color); +} + +.cru-text-button.danger { + color: var(--tl-danger-color); +} + +.cru-text-button.danger:hover { + color: var(--tl-danger-lighter-color); +} diff --git a/FrontEnd/src/views/common/button/TextButton.tsx b/FrontEnd/src/views/common/button/TextButton.tsx new file mode 100644 index 00000000..2014158a --- /dev/null +++ b/FrontEnd/src/views/common/button/TextButton.tsx @@ -0,0 +1,36 @@ +import React from "react"; +import { useTranslation } from "react-i18next"; +import classNames from "classnames"; + +import { convertI18nText, I18nText } from "@/common"; +import { PaletteColorType } from "@/palette"; + +import "./TextButton.css"; + +function _TextButton( + { + text, + color, + onClick, + }: { + text: I18nText; + color?: PaletteColorType; + onClick?: () => void; + }, + ref: React.ForwardedRef +): React.ReactElement | null { + const { t } = useTranslation(); + + return ( + + ); +} + +const TextButton = React.forwardRef(_TextButton); +export default TextButton; -- cgit v1.2.3