diff --git a/web/src/App.js b/web/src/App.js index c6ca541c..4e89e794 100644 --- a/web/src/App.js +++ b/web/src/App.js @@ -17,7 +17,7 @@ import "./App.less"; import {Helmet} from "react-helmet"; import * as Setting from "./Setting"; import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons"; -import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result} from "antd"; +import {Avatar, Button, Card, ConfigProvider, Drawer, Dropdown, FloatButton, Layout, Menu, Result, theme} from "antd"; import {Link, Redirect, Route, Switch, withRouter} from "react-router-dom"; import OrganizationListPage from "./OrganizationListPage"; import OrganizationEditPage from "./OrganizationEditPage"; @@ -69,6 +69,7 @@ import SystemInfo from "./SystemInfo"; import AdapterListPage from "./AdapterListPage"; import AdapterEditPage from "./AdapterEditPage"; import {withTranslation} from "react-i18next"; +import SelectThemeBox from "./SelectThemeBox"; const {Header, Footer, Content} = Layout; @@ -81,6 +82,8 @@ class App extends Component { account: undefined, uri: null, menuVisible: false, + themeAlgorithm: null, + logo: null, }; Setting.initServerUrl(); @@ -95,6 +98,16 @@ class App extends Component { this.getAccount(); } + componentDidMount() { + localStorage.getItem("theme") ? + this.setState({"themeAlgorithm": this.getTheme()}) : this.setState({"themeAlgorithm": theme.defaultAlgorithm}); + this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))}); + addEventListener("themeChange", (e) => { + this.setState({"themeAlgorithm": this.getTheme()}); + this.setState({"logo": Setting.getLogo(localStorage.getItem("theme"))}); + }); + } + componentDidUpdate() { // eslint-disable-next-line no-restricted-globals const uri = location.pathname; @@ -184,6 +197,10 @@ class App extends Component { return ""; } + getTheme() { + return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"]; + } + setLanguage(account) { const language = account?.language; if (language !== "" && language !== i18next.language) { @@ -465,54 +482,62 @@ class App extends Component { renderRouter() { return ( -
- - this.renderHomeIfLoggedIn()} /> - this.renderHomeIfLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - } /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - {/* this.renderLoginIfNotLoggedIn()}/>*/} - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - this.renderLoginIfNotLoggedIn()} /> - } /> - this.renderLoginIfNotLoggedIn()} /> - } />} /> - -
+ +
+ + this.renderHomeIfLoggedIn()} /> + this.renderHomeIfLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + } /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + {/* this.renderLoginIfNotLoggedIn()}/>*/} + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + this.renderLoginIfNotLoggedIn()} /> + } /> + this.renderLoginIfNotLoggedIn()} /> + } />} /> + +
+
); } @@ -532,30 +557,27 @@ class App extends Component { if (!Setting.isMobile()) { return ( -
+
{ Setting.isMobile() ? null : ( -
+
) } -
- - - { - this.renderAccount() - } - {this.state.account && } -
+ + { + this.renderAccount() + } + {this.state.account && } + {this.state.account && }
- + { this.renderRouter() @@ -570,11 +592,11 @@ class App extends Component { } else { return ( -
+
{ Setting.isMobile() ? null : ( -
+
) } @@ -592,12 +614,11 @@ class App extends Component { -
- { - this.renderAccount() - } - {this.state.account && } -
+ { + this.renderAccount() + } + {this.state.account && } + {this.state.account && }
{ this.renderRouter()} @@ -617,12 +638,10 @@ class App extends Component { {!this.state.account ? null :
}
- Powered by {"Casdoor"} + Powered by {"Casdoor"}
); @@ -690,6 +709,7 @@ class App extends Component { colorPrimary: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)", }, + algorithm: this.state.themeAlgorithm, }}> { this.renderPage() @@ -711,6 +731,7 @@ class App extends Component { colorPrimary: "rgb(89,54,213)", colorInfo: "rgb(89,54,213)", }, + algorithm: this.state.themeAlgorithm, }}> { this.renderPage() diff --git a/web/src/App.less b/web/src/App.less index f6662b79..26dc87d2 100644 --- a/web/src/App.less +++ b/web/src/App.less @@ -13,14 +13,12 @@ } .App-header { - background-color: #282c34; min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; font-size: calc(10px + 2vmin); - color: white; } .App-link { @@ -41,7 +39,6 @@ img { flex-direction: column; height: 100%; min-height: 100vh; - background-color: #f5f5f5; } .panel-logo { @@ -55,7 +52,7 @@ img { background-repeat: no-repeat; border-radius: 5px; width: 45px; - height: 65px; + height: 100%; float: right; cursor: pointer; @@ -64,9 +61,32 @@ img { } } +.login-form .language-box { + height: 65px; +} + +.theme-box { + background: url("@{StaticBaseUrl}/img/muti_language.svg"); + background-size: 25px, 25px; + background-position: center !important; + background-repeat: no-repeat !important; + border-radius: 5px; + width: 45px; + height: 100%; + float: right; + cursor: pointer; + + &:hover { + background-color: #f5f5f5 !important; + } +} + .rightDropDown { + border-radius: 7px; + &:hover { background-color: #f5f5f5; + color: black; } } @@ -128,3 +148,9 @@ img { background-size: 100% 100%; background-attachment: fixed; } + +.ant-menu-horizontal { + border-bottom: none !important; + margin-right: 30px; + right: 230px; +} diff --git a/web/src/SelectThemeBox.js b/web/src/SelectThemeBox.js new file mode 100644 index 00000000..1fdea580 --- /dev/null +++ b/web/src/SelectThemeBox.js @@ -0,0 +1,81 @@ +// Copyright 2021 The Casdoor Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import React from "react"; +import * as Setting from "./Setting"; +import {Dropdown} from "antd"; +import "./App.less"; +import i18next from "i18next"; + +function themeIcon(themeKey) { + return {themeKey}; +} + +function getLogoURL(themeKey) { + if (themeKey) { + return Setting.Themes.find(t => t.key === themeKey)["selectThemeLogo"]; + } else { + return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["selectThemeLogo"]; + } +} + +class SelectThemeBox extends React.Component { + constructor(props) { + super(props); + this.state = { + classes: props, + themes: props.theme ?? ["Default", "Dark", "Compact"], + icon: null, + }; + } + + items = this.getThemes(); + + componentDidMount() { + i18next.on("languageChanged", () => { + this.items = this.getThemes(); + }); + localStorage.getItem("theme") ? this.setState({"icon": getLogoURL()}) : this.setState({"icon": getLogoURL("Default")}); + addEventListener("themeChange", (e) => { + this.setState({"icon": getLogoURL()}); + }); + } + + getThemes() { + return Setting.Themes.map((theme) => Setting.getItem(i18next.t(`general:${theme.label}`), theme.key, themeIcon(theme.key))); + } + + getOrganizationThemes(themes) { + const select = []; + for (const theme of themes) { + this.items.map((item, index) => item.key === theme ? select.push(item) : null); + } + return select; + } + + render() { + const themeItems = this.getOrganizationThemes(this.state.themes); + const onClick = (e) => { + Setting.setTheme(e.key); + }; + + return ( + +
+ + ); + } +} + +export default SelectThemeBox; diff --git a/web/src/Setting.js b/web/src/Setting.js index 26fa2172..3e24d564 100644 --- a/web/src/Setting.js +++ b/web/src/Setting.js @@ -14,7 +14,7 @@ import React from "react"; import {Link} from "react-router-dom"; -import {Tag, Tooltip, message} from "antd"; +import {Tag, Tooltip, message, theme} from "antd"; import {QuestionCircleTwoTone} from "@ant-design/icons"; import {isMobile as isMobileDevice} from "react-device-detect"; import "./i18n"; @@ -43,6 +43,13 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng {label: "Русский", key: "ru", country: "RU", alt: "Русский"}, ]; +const {defaultAlgorithm, darkAlgorithm, compactAlgorithm} = theme; + +export const Themes = [{label: "Dark", key: "Dark", style: darkAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/dark.svg`}, + {label: "Compact", key: "Compact", style: compactAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/compact.svg`}, + {label: "Default", key: "Default", style: defaultAlgorithm, selectThemeLogo: `${StaticBaseUrl}/img/light.svg`}, +]; + export const OtherProviderInfo = { SMS: { "Aliyun SMS": { @@ -562,6 +569,14 @@ export function getAvatarColor(s) { return colorList[hash % 4]; } +export function getLogo(theme) { + if (theme === "Dark") { + return `${StaticBaseUrl}/img/casdoor-logo_1185x256_dark.png`; + } else { + return `${StaticBaseUrl}/img/casdoor-logo_1185x256.png`; + } +} + export function getLanguageText(text) { if (!text.includes("|")) { return text; @@ -587,6 +602,11 @@ export function setLanguage(language) { i18next.changeLanguage(language); } +export function setTheme(themeKey) { + localStorage.setItem("theme", themeKey); + dispatchEvent(new Event("themeChange")); +} + export function getAcceptLanguage() { if (i18next.language === null || i18next.language === "") { return "en;q=0.9,en;q=0.8"; diff --git a/web/src/index.css b/web/src/index.css index 4b155412..979af4eb 100644 --- a/web/src/index.css +++ b/web/src/index.css @@ -28,7 +28,7 @@ code { .logo { background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png"); - background-size: 130px, 27px; + background-size: 130px, 27px !important; width: 130px; height: 27px; margin: 17px 0 16px 15px; @@ -45,7 +45,3 @@ code { .ant-list-sm .ant-list-item { padding: 2px !important; } - -.ant-layout-header { - background-color: white !important; -} diff --git a/web/src/locales/de/data.json b/web/src/locales/de/data.json index 8b06d2f9..b67cc7c5 100644 --- a/web/src/locales/de/data.json +++ b/web/src/locales/de/data.json @@ -156,7 +156,10 @@ "Click to Upload": "Click to Upload", "Client IP": "Client-IP", "Close": "Close", + "Compact": "Compact", "Created time": "Erstellte Zeit", + "Dark": "Dark", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application - Tooltip", "Default avatar": "Standard Avatar", diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json index c717b548..0a727937 100644 --- a/web/src/locales/en/data.json +++ b/web/src/locales/en/data.json @@ -156,7 +156,10 @@ "Click to Upload": "Click to Upload", "Client IP": "Client IP", "Close": "Close", + "Compact": "Compact", "Created time": "Created time", + "Dark": "Dark", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application - Tooltip", "Default avatar": "Default avatar", diff --git a/web/src/locales/fr/data.json b/web/src/locales/fr/data.json index 1896d5f7..5867b761 100644 --- a/web/src/locales/fr/data.json +++ b/web/src/locales/fr/data.json @@ -156,7 +156,10 @@ "Click to Upload": "Click to Upload", "Client IP": "IP du client", "Close": "Close", + "Compact": "Compact", "Created time": "Date de création", + "Dark": "Dark", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application - Tooltip", "Default avatar": "Avatar par défaut", diff --git a/web/src/locales/ja/data.json b/web/src/locales/ja/data.json index 19a22812..85fd0f46 100644 --- a/web/src/locales/ja/data.json +++ b/web/src/locales/ja/data.json @@ -156,7 +156,10 @@ "Click to Upload": "Click to Upload", "Client IP": "クライアント IP", "Close": "Close", + "Compact": "Compact", "Created time": "作成日時", + "Dark": "Dark", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application - Tooltip", "Default avatar": "デフォルトのアバター", diff --git a/web/src/locales/ko/data.json b/web/src/locales/ko/data.json index 4576bf80..f4c4a006 100644 --- a/web/src/locales/ko/data.json +++ b/web/src/locales/ko/data.json @@ -156,7 +156,10 @@ "Click to Upload": "Click to Upload", "Client IP": "Client IP", "Close": "Close", + "Compact": "Compact", "Created time": "Created time", + "Dark": "Dark", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application - Tooltip", "Default avatar": "Default avatar", diff --git a/web/src/locales/ru/data.json b/web/src/locales/ru/data.json index ca03b82f..7f2316ca 100644 --- a/web/src/locales/ru/data.json +++ b/web/src/locales/ru/data.json @@ -156,7 +156,10 @@ "Click to Upload": "Нажмите здесь, чтобы загрузить", "Client IP": "IP клиента", "Close": "Close", + "Compact": "Compact", "Created time": "Время создания", + "Dark": "Dark", + "Default": "Default", "Default application": "Default application", "Default application - Tooltip": "Default application - Tooltip", "Default avatar": "Аватар по умолчанию", diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json index 330c682e..066e88b8 100644 --- a/web/src/locales/zh/data.json +++ b/web/src/locales/zh/data.json @@ -156,7 +156,10 @@ "Click to Upload": "点击上传", "Client IP": "客户端IP", "Close": "关闭", + "Compact": "紧凑", "Created time": "创建时间", + "Dark": "黑暗", + "Default": "默认", "Default application": "默认应用", "Default application - Tooltip": "默认应用", "Default avatar": "默认头像",