mirror of
https://github.com/casdoor/casdoor.git
synced 2025-07-04 05:10:19 +08:00
feat: support changing theme in antd 5 (#1430)
* feat: add global theme change function * feat: add icons * feat: in app theme changer * feat: use antd built-in themes * fix: multiple styling problem * fix: theme init from localstorage * feat: dark mode footer * feat: casdoor logo color theme * feat: select theme box icon adaptive to theme * fix: menu bar style * fix: language box style * feat: translation * feat: update translation of select theme box without reloading * fix: mobile view * fix: better structured select theme box * feat: add compact icon * fix: redundant theme fetch * fix: redundant theme fetch * fix: various styling problems
This commit is contained in:
175
web/src/App.js
175
web/src/App.js
@ -17,7 +17,7 @@ import "./App.less";
|
|||||||
import {Helmet} from "react-helmet";
|
import {Helmet} from "react-helmet";
|
||||||
import * as Setting from "./Setting";
|
import * as Setting from "./Setting";
|
||||||
import {BarsOutlined, DownOutlined, LogoutOutlined, SettingOutlined} from "@ant-design/icons";
|
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 {Link, Redirect, Route, Switch, withRouter} from "react-router-dom";
|
||||||
import OrganizationListPage from "./OrganizationListPage";
|
import OrganizationListPage from "./OrganizationListPage";
|
||||||
import OrganizationEditPage from "./OrganizationEditPage";
|
import OrganizationEditPage from "./OrganizationEditPage";
|
||||||
@ -69,6 +69,7 @@ import SystemInfo from "./SystemInfo";
|
|||||||
import AdapterListPage from "./AdapterListPage";
|
import AdapterListPage from "./AdapterListPage";
|
||||||
import AdapterEditPage from "./AdapterEditPage";
|
import AdapterEditPage from "./AdapterEditPage";
|
||||||
import {withTranslation} from "react-i18next";
|
import {withTranslation} from "react-i18next";
|
||||||
|
import SelectThemeBox from "./SelectThemeBox";
|
||||||
|
|
||||||
const {Header, Footer, Content} = Layout;
|
const {Header, Footer, Content} = Layout;
|
||||||
|
|
||||||
@ -81,6 +82,8 @@ class App extends Component {
|
|||||||
account: undefined,
|
account: undefined,
|
||||||
uri: null,
|
uri: null,
|
||||||
menuVisible: false,
|
menuVisible: false,
|
||||||
|
themeAlgorithm: null,
|
||||||
|
logo: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
Setting.initServerUrl();
|
Setting.initServerUrl();
|
||||||
@ -95,6 +98,16 @@ class App extends Component {
|
|||||||
this.getAccount();
|
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() {
|
componentDidUpdate() {
|
||||||
// eslint-disable-next-line no-restricted-globals
|
// eslint-disable-next-line no-restricted-globals
|
||||||
const uri = location.pathname;
|
const uri = location.pathname;
|
||||||
@ -184,6 +197,10 @@ class App extends Component {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTheme() {
|
||||||
|
return Setting.Themes.find(t => t.key === localStorage.getItem("theme"))["style"];
|
||||||
|
}
|
||||||
|
|
||||||
setLanguage(account) {
|
setLanguage(account) {
|
||||||
const language = account?.language;
|
const language = account?.language;
|
||||||
if (language !== "" && language !== i18next.language) {
|
if (language !== "" && language !== i18next.language) {
|
||||||
@ -465,54 +482,62 @@ class App extends Component {
|
|||||||
|
|
||||||
renderRouter() {
|
renderRouter() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<ConfigProvider theme={{
|
||||||
<Switch>
|
token: {
|
||||||
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
colorPrimary: "rgb(89,54,213)",
|
||||||
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
colorInfo: "rgb(89,54,213)",
|
||||||
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
},
|
||||||
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
algorithm: this.state.themeAlgorithm,
|
||||||
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
}}>
|
||||||
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
|
<div>
|
||||||
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
<Switch>
|
||||||
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/result" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||||
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
<Route exact path="/result/:applicationName" render={(props) => this.renderHomeIfLoggedIn(<ResultPage {...props} />)} />
|
||||||
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/" render={(props) => this.renderLoginIfNotLoggedIn(<HomePage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/account" render={(props) => this.renderLoginIfNotLoggedIn(<AccountPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/organizations" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/organizations/:organizationName" render={(props) => this.renderLoginIfNotLoggedIn(<OrganizationEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/organizations/:organizationName/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/users" render={(props) => this.renderLoginIfNotLoggedIn(<UserListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/users/:organizationName/:userName" render={(props) => <UserEditPage account={this.state.account} {...props} />} />
|
||||||
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/roles" render={(props) => this.renderLoginIfNotLoggedIn(<RoleListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/roles/:organizationName/:roleName" render={(props) => this.renderLoginIfNotLoggedIn(<RoleEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/permissions" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/permissions/:organizationName/:permissionName" render={(props) => this.renderLoginIfNotLoggedIn(<PermissionEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/models" render={(props) => this.renderLoginIfNotLoggedIn(<ModelListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/models/:organizationName/:modelName" render={(props) => this.renderLoginIfNotLoggedIn(<ModelEditPage account={this.state.account} {...props} />)} />
|
||||||
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
<Route exact path="/adapters" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/adapters/:organizationName/:adapterName" render={(props) => this.renderLoginIfNotLoggedIn(<AdapterEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/providers/:organizationName/:providerName" render={(props) => this.renderLoginIfNotLoggedIn(<ProviderEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/applications" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/applications/:organizationName/:applicationName" render={(props) => this.renderLoginIfNotLoggedIn(<ApplicationEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/resources" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
{/* <Route exact path="/resources/:resourceName" render={(props) => this.renderLoginIfNotLoggedIn(<ResourceEditPage account={this.state.account} {...props} />)}/>*/}
|
||||||
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/ldap/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/ldap/sync/:ldapId" render={(props) => this.renderLoginIfNotLoggedIn(<LdapSyncPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/tokens" render={(props) => this.renderLoginIfNotLoggedIn(<TokenListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/tokens/:tokenName" render={(props) => this.renderLoginIfNotLoggedIn(<TokenEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/webhooks" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
<Route exact path="/webhooks/:webhookName" render={(props) => this.renderLoginIfNotLoggedIn(<WebhookEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/syncers" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
<Route exact path="/syncers/:syncerName" render={(props) => this.renderLoginIfNotLoggedIn(<SyncerEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs" render={(props) => this.renderLoginIfNotLoggedIn(<CertListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
<Route exact path="/certs/:certName" render={(props) => this.renderLoginIfNotLoggedIn(<CertEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
<Route exact path="/products" render={(props) => this.renderLoginIfNotLoggedIn(<ProductListPage account={this.state.account} {...props} />)} />
|
||||||
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
<Route exact path="/products/:productName" render={(props) => this.renderLoginIfNotLoggedIn(<ProductEditPage account={this.state.account} {...props} />)} />
|
||||||
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
<Route exact path="/products/:productName/buy" render={(props) => this.renderLoginIfNotLoggedIn(<ProductBuyPage account={this.state.account} {...props} />)} />
|
||||||
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
<Route exact path="/payments" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentListPage account={this.state.account} {...props} />)} />
|
||||||
</Switch>
|
<Route exact path="/payments/:paymentName" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentEditPage account={this.state.account} {...props} />)} />
|
||||||
</div>
|
<Route exact path="/payments/:paymentName/result" render={(props) => this.renderLoginIfNotLoggedIn(<PaymentResultPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/records" render={(props) => this.renderLoginIfNotLoggedIn(<RecordListPage account={this.state.account} {...props} />)} />
|
||||||
|
<Route exact path="/.well-known/openid-configuration" render={(props) => <OdicDiscoveryPage />} />
|
||||||
|
<Route exact path="/sysinfo" render={(props) => this.renderLoginIfNotLoggedIn(<SystemInfo account={this.state.account} {...props} />)} />
|
||||||
|
<Route path="" render={() => <Result status="404" title="404 NOT FOUND" subTitle={i18next.t("general:Sorry, the page you visited does not exist.")}
|
||||||
|
extra={<a href="/"><Button type="primary">{i18next.t("general:Back Home")}</Button></a>} />} />
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -532,30 +557,27 @@ class App extends Component {
|
|||||||
if (!Setting.isMobile()) {
|
if (!Setting.isMobile()) {
|
||||||
return (
|
return (
|
||||||
<Layout id="parent-area">
|
<Layout id="parent-area">
|
||||||
<Header style={{marginBottom: "3px", paddingInline: 0}}>
|
<Header style={{marginBottom: "3px", paddingInline: 0, backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
|
||||||
{
|
{
|
||||||
Setting.isMobile() ? null : (
|
Setting.isMobile() ? null : (
|
||||||
<Link to={"/"}>
|
<Link to={"/"}>
|
||||||
<div className="logo" />
|
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
<div>
|
<Menu
|
||||||
<Menu
|
items={this.renderMenu()}
|
||||||
// theme="dark"
|
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
||||||
items={this.renderMenu()}
|
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
||||||
mode={(Setting.isMobile() && this.isStartPages()) ? "inline" : "horizontal"}
|
style={{position: "absolute", left: "145px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}
|
||||||
selectedKeys={[`${this.state.selectedMenuKey}`]}
|
/>
|
||||||
style={{lineHeight: "64px", position: "absolute", left: "145px", right: "200px"}}
|
{
|
||||||
>
|
this.renderAccount()
|
||||||
</Menu>
|
}
|
||||||
{
|
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
|
||||||
this.renderAccount()
|
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
||||||
}
|
|
||||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
|
||||||
</div>
|
|
||||||
</Header>
|
</Header>
|
||||||
<Content style={{backgroundColor: "#f5f5f5", alignItems: "stretch", display: "flex", flexDirection: "column"}}>
|
<Content style={{alignItems: "stretch", display: "flex", flexDirection: "column"}}>
|
||||||
<Card className="content-warp-card">
|
<Card className="content-warp-card">
|
||||||
{
|
{
|
||||||
this.renderRouter()
|
this.renderRouter()
|
||||||
@ -570,11 +592,11 @@ class App extends Component {
|
|||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
<Header style={{padding: "0", marginBottom: "3px"}}>
|
<Header style={{padding: "0", marginBottom: "3px", backgroundColor: this.state.themeAlgorithm === theme.darkAlgorithm ? "black" : "white"}}>
|
||||||
{
|
{
|
||||||
Setting.isMobile() ? null : (
|
Setting.isMobile() ? null : (
|
||||||
<Link to={"/"}>
|
<Link to={"/"}>
|
||||||
<div className="logo" />
|
<div className="logo" style={{background: `url(${this.state.logo})`}} />
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -592,12 +614,11 @@ class App extends Component {
|
|||||||
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
<Button icon={<BarsOutlined />} onClick={this.showMenu} type="text">
|
||||||
{i18next.t("general:Menu")}
|
{i18next.t("general:Menu")}
|
||||||
</Button>
|
</Button>
|
||||||
<div style = {{float: "right"}}>
|
{
|
||||||
{
|
this.renderAccount()
|
||||||
this.renderAccount()
|
}
|
||||||
}
|
{this.state.account && <SelectThemeBox themes={this.state.account.organization.themes} />}
|
||||||
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
{this.state.account && <SelectLanguageBox languages={this.state.account.organization.languages} />}
|
||||||
</div>
|
|
||||||
</Header>
|
</Header>
|
||||||
<Content style={{display: "flex", flexDirection: "column"}} >{
|
<Content style={{display: "flex", flexDirection: "column"}} >{
|
||||||
this.renderRouter()}
|
this.renderRouter()}
|
||||||
@ -617,12 +638,10 @@ class App extends Component {
|
|||||||
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
{!this.state.account ? null : <div style={{display: "none"}} id="CasdoorApplicationName" value={this.state.account.signupApplication} />}
|
||||||
<Footer id="footer" style={
|
<Footer id="footer" style={
|
||||||
{
|
{
|
||||||
borderTop: "1px solid #e8e8e8",
|
|
||||||
backgroundColor: "white",
|
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
}
|
}
|
||||||
}>
|
}>
|
||||||
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={`${Setting.StaticBaseUrl}/img/casdoor-logo_1185x256.png`} /></a>
|
Powered by <a target="_blank" href="https://casdoor.org" rel="noreferrer"><img style={{paddingBottom: "3px"}} height={"20px"} alt={"Casdoor"} src={this.state.logo} /></a>
|
||||||
</Footer>
|
</Footer>
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
@ -690,6 +709,7 @@ class App extends Component {
|
|||||||
colorPrimary: "rgb(89,54,213)",
|
colorPrimary: "rgb(89,54,213)",
|
||||||
colorInfo: "rgb(89,54,213)",
|
colorInfo: "rgb(89,54,213)",
|
||||||
},
|
},
|
||||||
|
algorithm: this.state.themeAlgorithm,
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
this.renderPage()
|
this.renderPage()
|
||||||
@ -711,6 +731,7 @@ class App extends Component {
|
|||||||
colorPrimary: "rgb(89,54,213)",
|
colorPrimary: "rgb(89,54,213)",
|
||||||
colorInfo: "rgb(89,54,213)",
|
colorInfo: "rgb(89,54,213)",
|
||||||
},
|
},
|
||||||
|
algorithm: this.state.themeAlgorithm,
|
||||||
}}>
|
}}>
|
||||||
{
|
{
|
||||||
this.renderPage()
|
this.renderPage()
|
||||||
|
@ -13,14 +13,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.App-header {
|
.App-header {
|
||||||
background-color: #282c34;
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: calc(10px + 2vmin);
|
font-size: calc(10px + 2vmin);
|
||||||
color: white;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.App-link {
|
.App-link {
|
||||||
@ -41,7 +39,6 @@ img {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.panel-logo {
|
.panel-logo {
|
||||||
@ -55,7 +52,7 @@ img {
|
|||||||
background-repeat: no-repeat;
|
background-repeat: no-repeat;
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
width: 45px;
|
width: 45px;
|
||||||
height: 65px;
|
height: 100%;
|
||||||
float: right;
|
float: right;
|
||||||
cursor: pointer;
|
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 {
|
.rightDropDown {
|
||||||
|
border-radius: 7px;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
|
color: black;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,3 +148,9 @@ img {
|
|||||||
background-size: 100% 100%;
|
background-size: 100% 100%;
|
||||||
background-attachment: fixed;
|
background-attachment: fixed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ant-menu-horizontal {
|
||||||
|
border-bottom: none !important;
|
||||||
|
margin-right: 30px;
|
||||||
|
right: 230px;
|
||||||
|
}
|
||||||
|
81
web/src/SelectThemeBox.js
Normal file
81
web/src/SelectThemeBox.js
Normal file
@ -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 <img width={24} alt={themeKey} src={getLogoURL(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 (
|
||||||
|
<Dropdown menu={{items: themeItems, onClick}} >
|
||||||
|
<div className="theme-box" style={{display: themeItems.length === 0 ? "none" : null, background: `url(${this.state.icon})`, ...this.props.style}} />
|
||||||
|
</Dropdown>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SelectThemeBox;
|
@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {Link} from "react-router-dom";
|
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 {QuestionCircleTwoTone} from "@ant-design/icons";
|
||||||
import {isMobile as isMobileDevice} from "react-device-detect";
|
import {isMobile as isMobileDevice} from "react-device-detect";
|
||||||
import "./i18n";
|
import "./i18n";
|
||||||
@ -43,6 +43,13 @@ export const Countries = [{label: "English", key: "en", country: "US", alt: "Eng
|
|||||||
{label: "Русский", key: "ru", country: "RU", alt: "Русский"},
|
{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 = {
|
export const OtherProviderInfo = {
|
||||||
SMS: {
|
SMS: {
|
||||||
"Aliyun SMS": {
|
"Aliyun SMS": {
|
||||||
@ -562,6 +569,14 @@ export function getAvatarColor(s) {
|
|||||||
return colorList[hash % 4];
|
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) {
|
export function getLanguageText(text) {
|
||||||
if (!text.includes("|")) {
|
if (!text.includes("|")) {
|
||||||
return text;
|
return text;
|
||||||
@ -587,6 +602,11 @@ export function setLanguage(language) {
|
|||||||
i18next.changeLanguage(language);
|
i18next.changeLanguage(language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setTheme(themeKey) {
|
||||||
|
localStorage.setItem("theme", themeKey);
|
||||||
|
dispatchEvent(new Event("themeChange"));
|
||||||
|
}
|
||||||
|
|
||||||
export function getAcceptLanguage() {
|
export function getAcceptLanguage() {
|
||||||
if (i18next.language === null || i18next.language === "") {
|
if (i18next.language === null || i18next.language === "") {
|
||||||
return "en;q=0.9,en;q=0.8";
|
return "en;q=0.9,en;q=0.8";
|
||||||
|
@ -28,7 +28,7 @@ code {
|
|||||||
|
|
||||||
.logo {
|
.logo {
|
||||||
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
|
background: url("https://cdn.casbin.org/img/casdoor-logo_1185x256.png");
|
||||||
background-size: 130px, 27px;
|
background-size: 130px, 27px !important;
|
||||||
width: 130px;
|
width: 130px;
|
||||||
height: 27px;
|
height: 27px;
|
||||||
margin: 17px 0 16px 15px;
|
margin: 17px 0 16px 15px;
|
||||||
@ -45,7 +45,3 @@ code {
|
|||||||
.ant-list-sm .ant-list-item {
|
.ant-list-sm .ant-list-item {
|
||||||
padding: 2px !important;
|
padding: 2px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ant-layout-header {
|
|
||||||
background-color: white !important;
|
|
||||||
}
|
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client-IP",
|
"Client IP": "Client-IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
"Compact": "Compact",
|
||||||
"Created time": "Erstellte Zeit",
|
"Created time": "Erstellte Zeit",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Standard Avatar",
|
"Default avatar": "Standard Avatar",
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
"Compact": "Compact",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "IP du client",
|
"Client IP": "IP du client",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
"Compact": "Compact",
|
||||||
"Created time": "Date de création",
|
"Created time": "Date de création",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Avatar par défaut",
|
"Default avatar": "Avatar par défaut",
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "クライアント IP",
|
"Client IP": "クライアント IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
"Compact": "Compact",
|
||||||
"Created time": "作成日時",
|
"Created time": "作成日時",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "デフォルトのアバター",
|
"Default avatar": "デフォルトのアバター",
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "Click to Upload",
|
"Click to Upload": "Click to Upload",
|
||||||
"Client IP": "Client IP",
|
"Client IP": "Client IP",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
"Compact": "Compact",
|
||||||
"Created time": "Created time",
|
"Created time": "Created time",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Default avatar",
|
"Default avatar": "Default avatar",
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
"Click to Upload": "Нажмите здесь, чтобы загрузить",
|
||||||
"Client IP": "IP клиента",
|
"Client IP": "IP клиента",
|
||||||
"Close": "Close",
|
"Close": "Close",
|
||||||
|
"Compact": "Compact",
|
||||||
"Created time": "Время создания",
|
"Created time": "Время создания",
|
||||||
|
"Dark": "Dark",
|
||||||
|
"Default": "Default",
|
||||||
"Default application": "Default application",
|
"Default application": "Default application",
|
||||||
"Default application - Tooltip": "Default application - Tooltip",
|
"Default application - Tooltip": "Default application - Tooltip",
|
||||||
"Default avatar": "Аватар по умолчанию",
|
"Default avatar": "Аватар по умолчанию",
|
||||||
|
@ -156,7 +156,10 @@
|
|||||||
"Click to Upload": "点击上传",
|
"Click to Upload": "点击上传",
|
||||||
"Client IP": "客户端IP",
|
"Client IP": "客户端IP",
|
||||||
"Close": "关闭",
|
"Close": "关闭",
|
||||||
|
"Compact": "紧凑",
|
||||||
"Created time": "创建时间",
|
"Created time": "创建时间",
|
||||||
|
"Dark": "黑暗",
|
||||||
|
"Default": "默认",
|
||||||
"Default application": "默认应用",
|
"Default application": "默认应用",
|
||||||
"Default application - Tooltip": "默认应用",
|
"Default application - Tooltip": "默认应用",
|
||||||
"Default avatar": "默认头像",
|
"Default avatar": "默认头像",
|
||||||
|
Reference in New Issue
Block a user