clubs, novel links
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
$mainColor: #efefef
|
$mainColor: #efefef
|
||||||
$secondaryColor: #aaaaaa
|
$secondaryColor: #aaaaaa
|
||||||
$linkHoverColor: #dfdfdf
|
$linkHoverColor: #efefef
|
||||||
|
|
||||||
// BLACK
|
// BLACK
|
||||||
$primaryBackgroundColor: #121212
|
$primaryBackgroundColor: #121212
|
||||||
|
|||||||
+8
-4
@@ -11,7 +11,7 @@ async function request<T>(url: string, method: string, body?: string | FormData)
|
|||||||
method, body,
|
method, body,
|
||||||
credentials: "include",
|
credentials: "include",
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Dev': /dev.(.+)/.test(window.location.href) ? '1' : '0'
|
'Dev': /(dev\..+)/.test(window.location.href) ? '1' : '0'
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -45,14 +45,18 @@ const TranslateStatus: { [id: string]: string } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TranslatePlatform: { [id: string]: string } = {
|
const TranslatePlatform: { [id: string]: string } = {
|
||||||
win: 'Windows'
|
win: 'Windows',
|
||||||
|
mac: 'macOS',
|
||||||
|
linux: 'Linux',
|
||||||
|
ios: 'IOS',
|
||||||
|
android: 'Andriod'
|
||||||
};
|
};
|
||||||
|
|
||||||
const TranslateExitStatus: { [id: string]: string } = {
|
const TranslateExitStatus: { [id: string]: string } = {
|
||||||
came_out: 'Вышла'
|
came_out: 'Вышла'
|
||||||
}
|
};
|
||||||
|
|
||||||
export const getVersion = () => get<VersionResponse>('version');
|
export const getVersion = () => get<VersionResponse>('version');
|
||||||
export const version = '1.1.1';
|
export const version = '1.2';
|
||||||
|
|
||||||
export { get, post, put, del, TranslateStatus, TranslatePlatform, TranslateExitStatus };
|
export { get, post, put, del, TranslateStatus, TranslatePlatform, TranslateExitStatus };
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
import { get } from "./api";
|
import { get } from "./api";
|
||||||
|
|
||||||
|
export const getClubs = () => get<ClubType[]>('/clubs');
|
||||||
export const getClub = (id: number) => get<ClubType>(`/clubs/${id}`);
|
export const getClub = (id: number) => get<ClubType>(`/clubs/${id}`);
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ body
|
|||||||
--error-bg-color: #{$errorBackgroundColor}
|
--error-bg-color: #{$errorBackgroundColor}
|
||||||
--success-bg-color: #{$successBackgroundColor}
|
--success-bg-color: #{$successBackgroundColor}
|
||||||
|
|
||||||
|
--accent-color: #f22
|
||||||
|
|
||||||
color: var(--main-color)
|
color: var(--main-color)
|
||||||
background: var(--main-bg-color)
|
background: var(--main-bg-color)
|
||||||
|
|
||||||
|
|||||||
+24
-4
@@ -33,6 +33,7 @@ import Snowfall from "react-snowfall";
|
|||||||
import eruda from 'eruda';
|
import eruda from 'eruda';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import parser from 'bbcode-to-react';
|
import parser from 'bbcode-to-react';
|
||||||
|
import Clubs from "../Clubs/Clubs";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
notifications: React.ReactNode[]
|
notifications: React.ReactNode[]
|
||||||
@@ -62,8 +63,21 @@ class App extends React.Component<Props, State> {
|
|||||||
const { setUser, setSettings } = this.props;
|
const { setUser, setSettings } = this.props;
|
||||||
|
|
||||||
const cachedTheme = localStorage.getItem('theme');
|
const cachedTheme = localStorage.getItem('theme');
|
||||||
if(cachedTheme)
|
if(cachedTheme) {
|
||||||
|
if(cachedTheme === 'custom') {
|
||||||
|
const customTheme = localStorage.getItem('customTheme') ?? '';
|
||||||
|
|
||||||
|
customTheme.split('\n').forEach(line => {
|
||||||
|
const [name, value] = line.split(':');
|
||||||
|
document.body.style.setProperty(name, value);
|
||||||
|
});
|
||||||
|
} else
|
||||||
document.body.setAttribute('theme', cachedTheme);
|
document.body.setAttribute('theme', cachedTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
const accentColor = localStorage.getItem('accentColor');
|
||||||
|
if(accentColor)
|
||||||
|
document.body.style.setProperty('--accent-color', accentColor);
|
||||||
|
|
||||||
fetchCurrentUser().then(user => {
|
fetchCurrentUser().then(user => {
|
||||||
setUser(user);
|
setUser(user);
|
||||||
@@ -85,7 +99,7 @@ class App extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
getVersion().then(res => {
|
getVersion().then(res => {
|
||||||
if(version !== res.version) {
|
if(version !== res.version && !/(dev\..+)/.test(window.location.href)) {
|
||||||
console.debug(`Current back-end version: ${res.version}. Clearing cache`);
|
console.debug(`Current back-end version: ${res.version}. Clearing cache`);
|
||||||
|
|
||||||
caches.keys().then(names => {
|
caches.keys().then(names => {
|
||||||
@@ -93,6 +107,10 @@ class App extends React.Component<Props, State> {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
});
|
});
|
||||||
|
} else if(/(dev\..+)/.test(window.location.href)) {
|
||||||
|
caches.keys().then(names => {
|
||||||
|
names.forEach(name => caches.delete(name).catch(console.error))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -100,7 +118,7 @@ class App extends React.Component<Props, State> {
|
|||||||
render() {
|
render() {
|
||||||
const { modal, snowfall } = this.props;
|
const { modal, snowfall } = this.props;
|
||||||
const { user } = this.state;
|
const { user } = this.state;
|
||||||
const isBanned = user?.is_banned || JSON.parse(localStorage.getItem('banned') || 'false');
|
const isBanned = user?.is_banned || JSON.parse(localStorage.getItem('banned') ?? 'false');
|
||||||
|
|
||||||
if(isBanned) {
|
if(isBanned) {
|
||||||
return (
|
return (
|
||||||
@@ -109,12 +127,13 @@ class App extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Navbar/>
|
||||||
{snowfall.snowfallStatus && (
|
{snowfall.snowfallStatus && (
|
||||||
<Snowfall color={'white'} snowflakeCount={snowfall.snowflakeCount} style={{ zIndex: 1000, position: "fixed" }}/>
|
<Snowfall color={'white'} snowflakeCount={snowfall.snowflakeCount} style={{ zIndex: 1000, position: "fixed" }}/>
|
||||||
)}
|
)}
|
||||||
<Navbar/>
|
|
||||||
<div className={'Container'}>
|
<div className={'Container'}>
|
||||||
<div className="Notifications">
|
<div className="Notifications">
|
||||||
{this.props.notifications.map((n, i) => (
|
{this.props.notifications.map((n, i) => (
|
||||||
@@ -132,6 +151,7 @@ class App extends React.Component<Props, State> {
|
|||||||
<Route exact path={'/novels'} component={Novels}/>
|
<Route exact path={'/novels'} component={Novels}/>
|
||||||
<Route exact path={'/novels/:novelId'} component={Novel}/>
|
<Route exact path={'/novels/:novelId'} component={Novel}/>
|
||||||
|
|
||||||
|
<Route exact path={'/clubs'} component={Clubs}/>
|
||||||
<Route exact path={'/clubs/:clubId'} component={Club}/>
|
<Route exact path={'/clubs/:clubId'} component={Club}/>
|
||||||
|
|
||||||
<Route exact path={'/users'} component={Users}/>
|
<Route exact path={'/users'} component={Users}/>
|
||||||
|
|||||||
@@ -17,20 +17,22 @@ class Club extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
getClub(this.props.match.params.clubId).then(club => {
|
const { match } = this.props;
|
||||||
|
|
||||||
|
getClub(match.params.clubId).then(club => {
|
||||||
this.setState({ club });
|
this.setState({ club });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
if(!this.state.club) return <Loading/>;
|
const { club } = this.state;
|
||||||
return (
|
if(!club) return <Loading/>;
|
||||||
<div>
|
|
||||||
<Group title={this.state.club.name}>
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Group title={club.name}>
|
||||||
|
<img src={club.avatar} width={96} height={96} style={{ borderRadius: '50%' }} alt=""/>
|
||||||
</Group>
|
</Group>
|
||||||
</div>
|
);
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
import { getClubs } from "../../api/clubs";
|
||||||
|
import { Link } from "react-router-dom";
|
||||||
|
|
||||||
|
type Props = {}
|
||||||
|
type State = {
|
||||||
|
clubs: ClubType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
class Clubs extends React.Component<Props, State> {
|
||||||
|
state: State = {
|
||||||
|
clubs: []
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
getClubs().then(clubs => {
|
||||||
|
this.setState({ clubs });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { clubs } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{clubs.map(club => (
|
||||||
|
<Link to={`/clubs/${club.id}`} key={club.id}>
|
||||||
|
<div>
|
||||||
|
<img src={club.avatar} width={96} height={96} style={{ borderRadius: '50%' }} alt=""/>
|
||||||
|
<h2>{club.name}</h2>
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Clubs;
|
||||||
@@ -30,7 +30,7 @@ const Footer: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<div className="Footer__Bottom">
|
<div className="Footer__Bottom">
|
||||||
Unmei © 2019-2020
|
Unmei © 2019-2021
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import React, { CSSProperties } from "react";
|
||||||
|
import Modal from "../../ui/Modal/Modal";
|
||||||
|
import Button from "../../ui/Button/Button";
|
||||||
|
import { useStore } from "react-redux";
|
||||||
|
import { hideModal } from "../../store/ducks/modal";
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
onConfirm: () => void
|
||||||
|
onDeny?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ConfirmModal = ({ onConfirm, onDeny }: Props) => {
|
||||||
|
const buttonStyle: CSSProperties = {
|
||||||
|
width: '50%', boxSizing: "border-box",
|
||||||
|
margin: 6, fontSize: '1.2rem'
|
||||||
|
};
|
||||||
|
|
||||||
|
const store = useStore<StoreState>();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal title={'Подтвердите действие'}>
|
||||||
|
<div style={{ display: "flex" }}>
|
||||||
|
<Button onClick={onConfirm} style={buttonStyle}>Да</Button>
|
||||||
|
<Button onClick={() => {
|
||||||
|
if(onDeny)
|
||||||
|
onDeny();
|
||||||
|
store.dispatch(hideModal());
|
||||||
|
|
||||||
|
}} style={buttonStyle}>Нет</Button>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ConfirmModal;
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
$hoverColor: #f22
|
|
||||||
|
|
||||||
.Minimized
|
.Minimized
|
||||||
height: 3rem
|
height: 3rem
|
||||||
|
|
||||||
@@ -42,7 +40,7 @@ $hoverColor: #f22
|
|||||||
bottom: 0
|
bottom: 0
|
||||||
left: 0
|
left: 0
|
||||||
height: 0
|
height: 0
|
||||||
background: $hoverColor
|
background: var(--accent-color)
|
||||||
transition: heigth .1s ease-in
|
transition: heigth .1s ease-in
|
||||||
-webkit-transition: height .1s ease-in
|
-webkit-transition: height .1s ease-in
|
||||||
|
|
||||||
@@ -97,7 +95,7 @@ $hoverColor: #f22
|
|||||||
bottom: 0
|
bottom: 0
|
||||||
left: 0
|
left: 0
|
||||||
height: 0
|
height: 0
|
||||||
background: $hoverColor
|
background: var(--accent-color)
|
||||||
transition: heigth .1s linear
|
transition: heigth .1s linear
|
||||||
-webkit-transition: height .1s linear
|
-webkit-transition: height .1s linear
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,12 @@ class Navbar extends React.Component<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logout = () => {
|
logout = () => {
|
||||||
this.props.logout();
|
const { logout } = this.props;
|
||||||
|
logout();
|
||||||
localStorage.removeItem('user');
|
localStorage.removeItem('user');
|
||||||
localStorage.removeItem('theme');
|
localStorage.removeItem('theme');
|
||||||
document.body.removeAttribute('theme');
|
document.body.removeAttribute('theme');
|
||||||
userLogout().catch(() => {});
|
userLogout().catch();
|
||||||
};
|
};
|
||||||
|
|
||||||
changeSize = () => {
|
changeSize = () => {
|
||||||
@@ -60,6 +61,9 @@ class Navbar extends React.Component<Props, State> {
|
|||||||
<Link to="/novels" className={'Navbar_Button'}>
|
<Link to="/novels" className={'Navbar_Button'}>
|
||||||
Новеллы
|
Новеллы
|
||||||
</Link>
|
</Link>
|
||||||
|
{/*<Link to="/clubs" className={'Navbar_Button'}>*/}
|
||||||
|
{/* Клубы*/}
|
||||||
|
{/*</Link>*/}
|
||||||
{expand && isMinimized && (
|
{expand && isMinimized && (
|
||||||
<div onClick={this.changeSize} className={'Navbar_Button'}
|
<div onClick={this.changeSize} className={'Navbar_Button'}
|
||||||
style={{ position: "absolute", right: 0, margin: '.5rem' }}>X</div>
|
style={{ position: "absolute", right: 0, margin: '.5rem' }}>X</div>
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class AllNews extends React.Component<{}, State> {
|
|||||||
<div className="News__Title">
|
<div className="News__Title">
|
||||||
Новости
|
Новости
|
||||||
</div>
|
</div>
|
||||||
{/*{<ProgressBar progress={50} alternative color={'red'}/>}*/}
|
{/*{<RoundProgressBar progress={50} alternative color={'red'}/>}*/}
|
||||||
{news.map(post => (
|
{news.map(post => (
|
||||||
<Link key={post.id} to={`/post/${post.id}`}>
|
<Link key={post.id} to={`/post/${post.id}`}>
|
||||||
<NewsPost {...post} />
|
<NewsPost {...post} />
|
||||||
|
|||||||
@@ -15,10 +15,12 @@ import {
|
|||||||
} from "../../api/novels";
|
} from "../../api/novels";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import Loading from "../../ui/Loading";
|
import Loading from "../../ui/Loading";
|
||||||
import { TranslateExitStatus, TranslateStatus } from "../../api/api";
|
import { TranslateExitStatus, TranslatePlatform, TranslateStatus } from "../../api/api";
|
||||||
import Comments from "../Comments/Comments";
|
import Comments from "../Comments/Comments";
|
||||||
import LoadingModal from "../Modals/LoadingModal";
|
import LoadingModal from "../Modals/LoadingModal";
|
||||||
import { hideModal, setModal } from "../../store/ducks/modal";
|
import { hideModal, setModal } from "../../store/ducks/modal";
|
||||||
|
import { hasPermission } from "../../utils";
|
||||||
|
import ConfirmModal from "../Modals/ConfirmModal";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
currentUser: UserType
|
currentUser: UserType
|
||||||
@@ -157,7 +159,7 @@ class Novel extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { errorCode, novel, comments, characters, genres, userData, statusExpanded, hasMoreComments } = this.state;
|
const { errorCode, novel, comments, characters, genres, userData, statusExpanded, hasMoreComments } = this.state;
|
||||||
const { currentUser } = this.props;
|
const { currentUser, setModal } = this.props;
|
||||||
|
|
||||||
if(errorCode === 100) return <NotFoundError/>;
|
if(errorCode === 100) return <NotFoundError/>;
|
||||||
if(!novel) return <Loading/>;
|
if(!novel) return <Loading/>;
|
||||||
@@ -207,6 +209,13 @@ class Novel extends React.Component<Props, State> {
|
|||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{hasPermission(currentUser, 'novel') && (
|
||||||
|
<div className="Novel__Main_Status_Mark">
|
||||||
|
<div className="Novel__Main_Status_Mark_Element">Изменить</div>
|
||||||
|
<div className="Novel__Main_Status_Mark_Element" onClick={() => setModal(<ConfirmModal onConfirm={() => console.log(123)}/>)}>Удалить</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>}
|
</div>}
|
||||||
</div>
|
</div>
|
||||||
<div className={'Novel__Info'}>
|
<div className={'Novel__Info'}>
|
||||||
@@ -225,10 +234,18 @@ class Novel extends React.Component<Props, State> {
|
|||||||
<div>
|
<div>
|
||||||
<strong>Статус</strong>: {TranslateExitStatus[novel.exit_status]}
|
<strong>Статус</strong>: {TranslateExitStatus[novel.exit_status]}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
{novel.duration > 0 && <div>
|
||||||
<strong>Продолжительность</strong>: {this.countNovelDuration(novel.duration)}
|
<strong>Продолжительность</strong>: {this.countNovelDuration(novel.duration)}
|
||||||
|
</div>}
|
||||||
|
{novel.platforms && <div>
|
||||||
|
<strong>Платформы</strong>: {novel.platforms.split(',').map(platform => TranslatePlatform[platform]).join(', ')}
|
||||||
|
</div>}
|
||||||
|
{novel.links.length > 0 && <div>
|
||||||
|
<strong>Ссылки</strong>: {novel.links.map(link => <a key={`${novel.id}_${link.name}`} style={{ margin: '0 .2rem' }} target='_blank' rel="noopener noreferrer" href={link.link}>{link.name}</a>)}
|
||||||
|
</div>}
|
||||||
|
<div className="Novel__Info_Rating">
|
||||||
|
<strong>Ср. оценка</strong>: {novel.rating.toFixed(2)}
|
||||||
</div>
|
</div>
|
||||||
<div className="Novel__Info_Rating"><strong>Ср. оценка</strong>: {novel.rating.toFixed(2)}</div>
|
|
||||||
<pre className={'Novel__Info_Description'}>{novel.description}</pre>
|
<pre className={'Novel__Info_Description'}>{novel.description}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import { updateUserAppearanceSettings } from "../../../api/users";
|
|||||||
import { connect } from "react-redux";
|
import { connect } from "react-redux";
|
||||||
import { setUser } from "../../../store/ducks/currentUser";
|
import { setUser } from "../../../store/ducks/currentUser";
|
||||||
import { setSnowflakeCount, setSnowfallStatus } from "../../../store/ducks/snowfall";
|
import { setSnowflakeCount, setSnowfallStatus } from "../../../store/ducks/snowfall";
|
||||||
|
import Input from "../../../ui/Input/Input";
|
||||||
|
import TextField from "../../../ui/TextField/TextField";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
setUser: SetUser
|
setUser: SetUser
|
||||||
@@ -17,13 +19,16 @@ type Props = {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
theme: Theme
|
theme: Theme
|
||||||
|
accentColor: string
|
||||||
|
customTheme: string
|
||||||
colorName: boolean
|
colorName: boolean
|
||||||
snowflakeCount: 0
|
snowflakeCount: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
class Appearance extends React.Component<Props, State> {
|
class Appearance extends React.Component<Props, State> {
|
||||||
state: State = {
|
state: State = {
|
||||||
theme: this.props.settings.theme,
|
theme: this.props.settings.theme, accentColor: localStorage.getItem('accentColor') ?? '#f22',
|
||||||
|
customTheme: localStorage.getItem('customTheme') ?? '',
|
||||||
colorName: JSON.parse(localStorage.getItem('color-name') || 'false'),
|
colorName: JSON.parse(localStorage.getItem('color-name') || 'false'),
|
||||||
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250')
|
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250')
|
||||||
}
|
}
|
||||||
@@ -42,12 +47,33 @@ class Appearance extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
saveAccentColor = (event: FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { accentColor } = this.state;
|
||||||
|
|
||||||
|
localStorage.setItem('accentColor', accentColor);
|
||||||
|
document.body.style.setProperty('--accent-color', accentColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCustomTheme = (event: FormEvent) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
const { customTheme } = this.state;
|
||||||
|
|
||||||
|
localStorage.setItem('customTheme', customTheme);
|
||||||
|
customTheme.split('\n').forEach(line => {
|
||||||
|
const [name, value] = line.split(':');
|
||||||
|
document.body.style.setProperty(name, value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { snowfallSettings, setSnowflakeCount, setSnowfallStatus } = this.props;
|
const { snowfallSettings, setSnowflakeCount, setSnowfallStatus } = this.props;
|
||||||
const { theme, colorName } = this.state;
|
const { theme, colorName, accentColor, customTheme } = this.state;
|
||||||
|
|
||||||
return (<>
|
return (<>
|
||||||
<Group title={'Тема'} className={'Settings_Group'}>
|
<Group title={'Тема'} className={'Settings_Group'} columns={3}>
|
||||||
<form onSubmit={this.saveTheme}>
|
<form onSubmit={this.saveTheme}>
|
||||||
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
||||||
<option value="dark">Dark</option>
|
<option value="dark">Dark</option>
|
||||||
@@ -55,9 +81,32 @@ class Appearance extends React.Component<Props, State> {
|
|||||||
<option value="red">Red</option>
|
<option value="red">Red</option>
|
||||||
<option value="green">Green</option>
|
<option value="green">Green</option>
|
||||||
<option value="light">Light</option>
|
<option value="light">Light</option>
|
||||||
|
<option value="custom">Custom</option>
|
||||||
</select>
|
</select>
|
||||||
<Button>Сохранить</Button>
|
<Button>Сохранить</Button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<form onSubmit={this.saveAccentColor}>
|
||||||
|
<label>
|
||||||
|
Акцентый цвет:
|
||||||
|
<Input
|
||||||
|
type="text"
|
||||||
|
value={accentColor}
|
||||||
|
onChange={e => this.setState({ accentColor: e.target.value })}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<Button>Сохранить</Button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
<form onSubmit={this.saveCustomTheme}>
|
||||||
|
<TextField
|
||||||
|
placeholder={'Переменные темы'}
|
||||||
|
style={{ height: '8rem', width: '100%' }}
|
||||||
|
value={customTheme}
|
||||||
|
onChange={e => this.setState({ customTheme: e.target.value })}
|
||||||
|
/>
|
||||||
|
<Button>Сохранить</Button>
|
||||||
|
</form>
|
||||||
</Group>
|
</Group>
|
||||||
<Group title={'Остальное'}>
|
<Group title={'Остальное'}>
|
||||||
<label>
|
<label>
|
||||||
|
|||||||
@@ -12,3 +12,6 @@
|
|||||||
|
|
||||||
input, .Button
|
input, .Button
|
||||||
font-size: 1.1rem
|
font-size: 1.1rem
|
||||||
|
|
||||||
|
.Button
|
||||||
|
margin: .5rem 0
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const SET_SNOWFALL_STATUS = 'unmei/snowfall/SET_SNOWFALL_STATUS';
|
|||||||
|
|
||||||
const initialState: Snowfall = {
|
const initialState: Snowfall = {
|
||||||
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250'),
|
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250'),
|
||||||
snowfallStatus: true
|
snowfallStatus: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const reducer = (state = initialState, action: Action<Snowfall>) => {
|
const reducer = (state = initialState, action: Action<Snowfall>) => {
|
||||||
|
|||||||
Vendored
+8
-1
@@ -1,4 +1,4 @@
|
|||||||
type Theme = 'dark' | 'blue' | 'red' | 'green' | 'light' | 'win95';
|
type Theme = 'dark' | 'blue' | 'red' | 'green' | 'light' | 'custom' | 'win95';
|
||||||
type ExitStatus = 'came_out';
|
type ExitStatus = 'came_out';
|
||||||
type NovelPlatform = 'win';
|
type NovelPlatform = 'win';
|
||||||
|
|
||||||
@@ -7,9 +7,16 @@ type ApiError = {
|
|||||||
text: string
|
text: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Pagination = {
|
||||||
|
offset?: number
|
||||||
|
limit?: number
|
||||||
|
total?: number
|
||||||
|
}
|
||||||
|
|
||||||
type ApiResponse<T> = {
|
type ApiResponse<T> = {
|
||||||
error: boolean
|
error: boolean
|
||||||
error_data?: ApiError | string
|
error_data?: ApiError | string
|
||||||
|
pagination?: Pagination
|
||||||
data?: T
|
data?: T
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+9
-1
@@ -1,3 +1,10 @@
|
|||||||
|
type NovelLinkType = {
|
||||||
|
novel_id: number
|
||||||
|
link: string
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
type UserType = {
|
type UserType = {
|
||||||
authorized: boolean
|
authorized: boolean
|
||||||
id: number
|
id: number
|
||||||
@@ -22,7 +29,8 @@ type NovelType = {
|
|||||||
release_date: Date
|
release_date: Date
|
||||||
exit_status: ExitStatus
|
exit_status: ExitStatus
|
||||||
duration: number
|
duration: number
|
||||||
platform: NovelPlatform
|
platforms: string
|
||||||
|
links: NovelLinkType[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type CharacterType = {
|
type CharacterType = {
|
||||||
|
|||||||
@@ -3,12 +3,14 @@ import classNames from "../../classNames";
|
|||||||
import './Group.sass'
|
import './Group.sass'
|
||||||
|
|
||||||
interface Props extends HTMLAttributes<HTMLElement> {
|
interface Props extends HTMLAttributes<HTMLElement> {
|
||||||
|
children: React.ReactNode[] | React.ReactNode
|
||||||
title?: string
|
title?: string
|
||||||
direction?: 'column' | 'row'
|
direction?: 'column' | 'row'
|
||||||
|
columns?: number
|
||||||
}
|
}
|
||||||
|
|
||||||
const Group = (restProps: Props) => {
|
const Group = (restProps: Props) => {
|
||||||
const { title, direction, className, children, ...props } = restProps;
|
const { title, direction, columns, className, children, ...props } = restProps;
|
||||||
let style: React.CSSProperties = {};
|
let style: React.CSSProperties = {};
|
||||||
if(direction === 'column') {
|
if(direction === 'column') {
|
||||||
style = {
|
style = {
|
||||||
@@ -16,6 +18,10 @@ const Group = (restProps: Props) => {
|
|||||||
flexDirection: 'column'
|
flexDirection: 'column'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if(!children) return null;
|
||||||
|
if(columns && columns > 1) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<div className={classNames('Group', className)} {...props}>
|
<div className={classNames('Group', className)} {...props}>
|
||||||
{title && <div className="Group__Title">{title}</div>}
|
{title && <div className="Group__Title">{title}</div>}
|
||||||
|
|||||||
@@ -25,8 +25,8 @@ class Modal extends React.Component<Props> {
|
|||||||
|
|
||||||
handleKeyUp = (e: KeyboardEvent) => {
|
handleKeyUp = (e: KeyboardEvent) => {
|
||||||
if(e.code === 'Escape') {
|
if(e.code === 'Escape') {
|
||||||
const { setModal } = this.props;
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
const { setModal } = this.props;
|
||||||
setModal(null);
|
setModal(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,121 +0,0 @@
|
|||||||
@import url(https://fonts.googleapis.com/css?family=Lato:700)
|
|
||||||
|
|
||||||
// -- vars
|
|
||||||
$bg-color: #34495e
|
|
||||||
$default-size: 1em
|
|
||||||
$label-font-size: $default-size / 4
|
|
||||||
$label-font-size-redo: $default-size * 4
|
|
||||||
|
|
||||||
// -- mixins
|
|
||||||
@mixin size($width, $height)
|
|
||||||
height: $height
|
|
||||||
width: $width
|
|
||||||
|
|
||||||
@mixin draw-progress--solid($progress, $color, $bg-color)
|
|
||||||
background: linear-gradient(to right, $color 50%, $bg-color 50%)
|
|
||||||
|
|
||||||
&:before
|
|
||||||
@if $progress <= 50
|
|
||||||
background: $bg-color
|
|
||||||
transform: rotate((100 - (50 - $progress)) / 100 * 360deg * -1)
|
|
||||||
@else
|
|
||||||
background: $color
|
|
||||||
transform: rotate((100 - $progress) / 100 * 360deg)
|
|
||||||
|
|
||||||
.charts-container
|
|
||||||
&:after
|
|
||||||
clear: both
|
|
||||||
content: ''
|
|
||||||
display: table
|
|
||||||
|
|
||||||
.pie-wrapper
|
|
||||||
@include size($default-size, $default-size)
|
|
||||||
|
|
||||||
font-size: 10em
|
|
||||||
box-sizing: border-box
|
|
||||||
margin: 15px
|
|
||||||
position: relative
|
|
||||||
|
|
||||||
*, *:before, *:after
|
|
||||||
box-sizing: border-box
|
|
||||||
|
|
||||||
&:nth-child(3n + 1)
|
|
||||||
clear: both
|
|
||||||
|
|
||||||
.pie
|
|
||||||
@include size(100%, 100%)
|
|
||||||
clip: rect(0, $default-size, $default-size, $default-size / 2)
|
|
||||||
left: 0
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
|
|
||||||
.half-circle
|
|
||||||
@include size(100%, 100%)
|
|
||||||
border: ($default-size / 10) solid #3498db
|
|
||||||
border-radius: 50%
|
|
||||||
clip: rect(0, $default-size / 2, $default-size, 0)
|
|
||||||
left: 0
|
|
||||||
position: absolute
|
|
||||||
top: 0
|
|
||||||
|
|
||||||
.label
|
|
||||||
display: block
|
|
||||||
position: absolute
|
|
||||||
color: #ecf0f1
|
|
||||||
background: $bg-color
|
|
||||||
border-radius: 50%
|
|
||||||
cursor: default
|
|
||||||
font-size: $label-font-size
|
|
||||||
text-align: center
|
|
||||||
|
|
||||||
line-height: $label-font-size-redo * .70
|
|
||||||
top: $label-font-size-redo / 10
|
|
||||||
right: $label-font-size-redo / 10
|
|
||||||
bottom: $label-font-size-redo / 10
|
|
||||||
left: $label-font-size-redo / 10
|
|
||||||
|
|
||||||
.smaller
|
|
||||||
font-size: .5em
|
|
||||||
|
|
||||||
.percent
|
|
||||||
color: #bdc3c7
|
|
||||||
font-size: .45em
|
|
||||||
padding-bottom: 20px
|
|
||||||
vertical-align: super
|
|
||||||
|
|
||||||
.shadow
|
|
||||||
@include size(100%, 100%)
|
|
||||||
border: $default-size / 10 solid #bdc3c7
|
|
||||||
border-radius: 50%
|
|
||||||
|
|
||||||
&.style-2
|
|
||||||
.label
|
|
||||||
background: none
|
|
||||||
color: #7f8c8d
|
|
||||||
|
|
||||||
.smaller
|
|
||||||
color: #bdc3c7
|
|
||||||
|
|
||||||
.pie-wrapper--solid
|
|
||||||
border-radius: 50%
|
|
||||||
overflow: hidden
|
|
||||||
|
|
||||||
&:before
|
|
||||||
border-radius: 0 100% 100% 0 / 50%
|
|
||||||
content: ''
|
|
||||||
display: block
|
|
||||||
height: 100%
|
|
||||||
margin-left: 50%
|
|
||||||
transform-origin: left
|
|
||||||
|
|
||||||
.label
|
|
||||||
background: transparent
|
|
||||||
|
|
||||||
&.progress-65
|
|
||||||
@include draw-progress--solid(65, #e67e22, $bg-color)
|
|
||||||
|
|
||||||
&.progress-25
|
|
||||||
@include draw-progress--solid(25, #9b59b6, $bg-color)
|
|
||||||
|
|
||||||
&.progress-88
|
|
||||||
@include draw-progress--solid(88, #3498db, $bg-color)
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import React, { CSSProperties } from "react";
|
|
||||||
import './ProgressBar.sass';
|
|
||||||
|
|
||||||
const ProgressBar = ({ progress, color, alternative }: { progress: number, color: string, alternative?: boolean }) => {
|
|
||||||
const rightSide: CSSProperties = progress <= 50 ? { display: "none" } : { transform: 'rotate(180deg)' };
|
|
||||||
const borderColor: CSSProperties = { borderColor: color };
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={alternative ? 'pie-wrapper progress style-2' : "pie-wrapper progress-75"}>
|
|
||||||
<span className="label">{progress}<span className="percent">%</span></span>
|
|
||||||
<div className="pie" style={progress <= 50 ? {} : { clip: 'rect(auto, auto, auto, auto)' }}>
|
|
||||||
<div className="left-side half-circle"
|
|
||||||
style={{ ...borderColor, transform: `rotate(${progress * 3.6}deg)` }}/>
|
|
||||||
<div className="right-side half-circle" style={{ ...borderColor, ...rightSide }}/>
|
|
||||||
</div>
|
|
||||||
{alternative && <div className="shadow"/>}
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ProgressBar;
|
|
||||||
Reference in New Issue
Block a user