clubs, novel links
This commit is contained in:
+2
-2
@@ -6,7 +6,7 @@
|
||||
|
||||
$mainColor: #efefef
|
||||
$secondaryColor: #aaaaaa
|
||||
$linkHoverColor: #dfdfdf
|
||||
$linkHoverColor: #efefef
|
||||
|
||||
// BLACK
|
||||
$primaryBackgroundColor: #121212
|
||||
@@ -57,4 +57,4 @@ $blockPadding: .2rem
|
||||
|
||||
$borderRadius: 4px
|
||||
|
||||
$novelBlockSize: calc(100% / 4 - 1rem)
|
||||
$novelBlockSize: calc(100% / 4 - 1rem)
|
||||
|
||||
+8
-4
@@ -11,7 +11,7 @@ async function request<T>(url: string, method: string, body?: string | FormData)
|
||||
method, body,
|
||||
credentials: "include",
|
||||
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 } = {
|
||||
win: 'Windows'
|
||||
win: 'Windows',
|
||||
mac: 'macOS',
|
||||
linux: 'Linux',
|
||||
ios: 'IOS',
|
||||
android: 'Andriod'
|
||||
};
|
||||
|
||||
const TranslateExitStatus: { [id: string]: string } = {
|
||||
came_out: 'Вышла'
|
||||
}
|
||||
};
|
||||
|
||||
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 };
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { get } from "./api";
|
||||
|
||||
export const getClubs = () => get<ClubType[]>('/clubs');
|
||||
export const getClub = (id: number) => get<ClubType>(`/clubs/${id}`);
|
||||
|
||||
@@ -27,6 +27,8 @@ body
|
||||
--error-bg-color: #{$errorBackgroundColor}
|
||||
--success-bg-color: #{$successBackgroundColor}
|
||||
|
||||
--accent-color: #f22
|
||||
|
||||
color: var(--main-color)
|
||||
background: var(--main-bg-color)
|
||||
|
||||
|
||||
+25
-5
@@ -33,6 +33,7 @@ import Snowfall from "react-snowfall";
|
||||
import eruda from 'eruda';
|
||||
// @ts-ignore
|
||||
import parser from 'bbcode-to-react';
|
||||
import Clubs from "../Clubs/Clubs";
|
||||
|
||||
type Props = {
|
||||
notifications: React.ReactNode[]
|
||||
@@ -62,8 +63,21 @@ class App extends React.Component<Props, State> {
|
||||
const { setUser, setSettings } = this.props;
|
||||
|
||||
const cachedTheme = localStorage.getItem('theme');
|
||||
if(cachedTheme)
|
||||
document.body.setAttribute('theme', 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);
|
||||
}
|
||||
|
||||
const accentColor = localStorage.getItem('accentColor');
|
||||
if(accentColor)
|
||||
document.body.style.setProperty('--accent-color', accentColor);
|
||||
|
||||
fetchCurrentUser().then(user => {
|
||||
setUser(user);
|
||||
@@ -85,7 +99,7 @@ class App extends React.Component<Props, State> {
|
||||
});
|
||||
|
||||
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`);
|
||||
|
||||
caches.keys().then(names => {
|
||||
@@ -93,6 +107,10 @@ class App extends React.Component<Props, State> {
|
||||
}).then(() => {
|
||||
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() {
|
||||
const { modal, snowfall } = this.props;
|
||||
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) {
|
||||
return (
|
||||
@@ -109,12 +127,13 @@ class App extends React.Component<Props, State> {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Navbar/>
|
||||
{snowfall.snowfallStatus && (
|
||||
<Snowfall color={'white'} snowflakeCount={snowfall.snowflakeCount} style={{ zIndex: 1000, position: "fixed" }}/>
|
||||
)}
|
||||
<Navbar/>
|
||||
<div className={'Container'}>
|
||||
<div className="Notifications">
|
||||
{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/:novelId'} component={Novel}/>
|
||||
|
||||
<Route exact path={'/clubs'} component={Clubs}/>
|
||||
<Route exact path={'/clubs/:clubId'} component={Club}/>
|
||||
|
||||
<Route exact path={'/users'} component={Users}/>
|
||||
|
||||
+10
-8
@@ -17,20 +17,22 @@ class Club extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
getClub(this.props.match.params.clubId).then(club => {
|
||||
const { match } = this.props;
|
||||
|
||||
getClub(match.params.clubId).then(club => {
|
||||
this.setState({ club });
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
if(!this.state.club) return <Loading/>;
|
||||
return (
|
||||
<div>
|
||||
<Group title={this.state.club.name}>
|
||||
const { club } = this.state;
|
||||
if(!club) return <Loading/>;
|
||||
|
||||
</Group>
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<Group title={club.name}>
|
||||
<img src={club.avatar} width={96} height={96} style={{ borderRadius: '50%' }} alt=""/>
|
||||
</Group>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
</footer>
|
||||
<div className="Footer__Bottom">
|
||||
Unmei © 2019-2020
|
||||
Unmei © 2019-2021
|
||||
</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
|
||||
height: 3rem
|
||||
|
||||
@@ -42,7 +40,7 @@ $hoverColor: #f22
|
||||
bottom: 0
|
||||
left: 0
|
||||
height: 0
|
||||
background: $hoverColor
|
||||
background: var(--accent-color)
|
||||
transition: heigth .1s ease-in
|
||||
-webkit-transition: height .1s ease-in
|
||||
|
||||
@@ -97,7 +95,7 @@ $hoverColor: #f22
|
||||
bottom: 0
|
||||
left: 0
|
||||
height: 0
|
||||
background: $hoverColor
|
||||
background: var(--accent-color)
|
||||
transition: heigth .1s linear
|
||||
-webkit-transition: height .1s linear
|
||||
|
||||
|
||||
@@ -28,11 +28,12 @@ class Navbar extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
logout = () => {
|
||||
this.props.logout();
|
||||
const { logout } = this.props;
|
||||
logout();
|
||||
localStorage.removeItem('user');
|
||||
localStorage.removeItem('theme');
|
||||
document.body.removeAttribute('theme');
|
||||
userLogout().catch(() => {});
|
||||
userLogout().catch();
|
||||
};
|
||||
|
||||
changeSize = () => {
|
||||
@@ -60,6 +61,9 @@ class Navbar extends React.Component<Props, State> {
|
||||
<Link to="/novels" className={'Navbar_Button'}>
|
||||
Новеллы
|
||||
</Link>
|
||||
{/*<Link to="/clubs" className={'Navbar_Button'}>*/}
|
||||
{/* Клубы*/}
|
||||
{/*</Link>*/}
|
||||
{expand && isMinimized && (
|
||||
<div onClick={this.changeSize} className={'Navbar_Button'}
|
||||
style={{ position: "absolute", right: 0, margin: '.5rem' }}>X</div>
|
||||
|
||||
@@ -30,7 +30,7 @@ class AllNews extends React.Component<{}, State> {
|
||||
<div className="News__Title">
|
||||
Новости
|
||||
</div>
|
||||
{/*{<ProgressBar progress={50} alternative color={'red'}/>}*/}
|
||||
{/*{<RoundProgressBar progress={50} alternative color={'red'}/>}*/}
|
||||
{news.map(post => (
|
||||
<Link key={post.id} to={`/post/${post.id}`}>
|
||||
<NewsPost {...post} />
|
||||
|
||||
@@ -15,10 +15,12 @@ import {
|
||||
} from "../../api/novels";
|
||||
import { Link } from "react-router-dom";
|
||||
import Loading from "../../ui/Loading";
|
||||
import { TranslateExitStatus, TranslateStatus } from "../../api/api";
|
||||
import { TranslateExitStatus, TranslatePlatform, TranslateStatus } from "../../api/api";
|
||||
import Comments from "../Comments/Comments";
|
||||
import LoadingModal from "../Modals/LoadingModal";
|
||||
import { hideModal, setModal } from "../../store/ducks/modal";
|
||||
import { hasPermission } from "../../utils";
|
||||
import ConfirmModal from "../Modals/ConfirmModal";
|
||||
|
||||
type Props = {
|
||||
currentUser: UserType
|
||||
@@ -157,7 +159,7 @@ class Novel extends React.Component<Props, State> {
|
||||
|
||||
render() {
|
||||
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(!novel) return <Loading/>;
|
||||
@@ -207,6 +209,13 @@ class Novel extends React.Component<Props, State> {
|
||||
</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 className={'Novel__Info'}>
|
||||
@@ -225,10 +234,18 @@ class Novel extends React.Component<Props, State> {
|
||||
<div>
|
||||
<strong>Статус</strong>: {TranslateExitStatus[novel.exit_status]}
|
||||
</div>
|
||||
<div>
|
||||
{novel.duration > 0 && <div>
|
||||
<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 className="Novel__Info_Rating"><strong>Ср. оценка</strong>: {novel.rating.toFixed(2)}</div>
|
||||
<pre className={'Novel__Info_Description'}>{novel.description}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,8 @@ import { updateUserAppearanceSettings } from "../../../api/users";
|
||||
import { connect } from "react-redux";
|
||||
import { setUser } from "../../../store/ducks/currentUser";
|
||||
import { setSnowflakeCount, setSnowfallStatus } from "../../../store/ducks/snowfall";
|
||||
import Input from "../../../ui/Input/Input";
|
||||
import TextField from "../../../ui/TextField/TextField";
|
||||
|
||||
type Props = {
|
||||
setUser: SetUser
|
||||
@@ -17,13 +19,16 @@ type Props = {
|
||||
|
||||
type State = {
|
||||
theme: Theme
|
||||
accentColor: string
|
||||
customTheme: string
|
||||
colorName: boolean
|
||||
snowflakeCount: 0
|
||||
}
|
||||
|
||||
class Appearance extends React.Component<Props, 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'),
|
||||
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() {
|
||||
const { snowfallSettings, setSnowflakeCount, setSnowfallStatus } = this.props;
|
||||
const { theme, colorName } = this.state;
|
||||
const { theme, colorName, accentColor, customTheme } = this.state;
|
||||
|
||||
return (<>
|
||||
<Group title={'Тема'} className={'Settings_Group'}>
|
||||
<Group title={'Тема'} className={'Settings_Group'} columns={3}>
|
||||
<form onSubmit={this.saveTheme}>
|
||||
<select onChange={e => this.setState({ theme: e.target.value as Theme })} value={theme}>
|
||||
<option value="dark">Dark</option>
|
||||
@@ -55,9 +81,32 @@ class Appearance extends React.Component<Props, State> {
|
||||
<option value="red">Red</option>
|
||||
<option value="green">Green</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="custom">Custom</option>
|
||||
</select>
|
||||
<Button>Сохранить</Button>
|
||||
</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 title={'Остальное'}>
|
||||
<label>
|
||||
|
||||
@@ -12,3 +12,6 @@
|
||||
|
||||
input, .Button
|
||||
font-size: 1.1rem
|
||||
|
||||
.Button
|
||||
margin: .5rem 0
|
||||
|
||||
@@ -3,7 +3,7 @@ const SET_SNOWFALL_STATUS = 'unmei/snowfall/SET_SNOWFALL_STATUS';
|
||||
|
||||
const initialState: Snowfall = {
|
||||
snowflakeCount: JSON.parse(localStorage.getItem('snowflakeCount') ?? '250'),
|
||||
snowfallStatus: true
|
||||
snowfallStatus: false
|
||||
};
|
||||
|
||||
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 NovelPlatform = 'win';
|
||||
|
||||
@@ -7,9 +7,16 @@ type ApiError = {
|
||||
text: string
|
||||
}
|
||||
|
||||
type Pagination = {
|
||||
offset?: number
|
||||
limit?: number
|
||||
total?: number
|
||||
}
|
||||
|
||||
type ApiResponse<T> = {
|
||||
error: boolean
|
||||
error_data?: ApiError | string
|
||||
pagination?: Pagination
|
||||
data?: T
|
||||
}
|
||||
|
||||
|
||||
Vendored
+9
-1
@@ -1,3 +1,10 @@
|
||||
type NovelLinkType = {
|
||||
novel_id: number
|
||||
link: string
|
||||
name: string
|
||||
}
|
||||
|
||||
|
||||
type UserType = {
|
||||
authorized: boolean
|
||||
id: number
|
||||
@@ -22,7 +29,8 @@ type NovelType = {
|
||||
release_date: Date
|
||||
exit_status: ExitStatus
|
||||
duration: number
|
||||
platform: NovelPlatform
|
||||
platforms: string
|
||||
links: NovelLinkType[]
|
||||
}
|
||||
|
||||
type CharacterType = {
|
||||
|
||||
@@ -3,12 +3,14 @@ import classNames from "../../classNames";
|
||||
import './Group.sass'
|
||||
|
||||
interface Props extends HTMLAttributes<HTMLElement> {
|
||||
children: React.ReactNode[] | React.ReactNode
|
||||
title?: string
|
||||
direction?: 'column' | 'row'
|
||||
columns?: number
|
||||
}
|
||||
|
||||
const Group = (restProps: Props) => {
|
||||
const { title, direction, className, children, ...props } = restProps;
|
||||
const { title, direction, columns, className, children, ...props } = restProps;
|
||||
let style: React.CSSProperties = {};
|
||||
if(direction === 'column') {
|
||||
style = {
|
||||
@@ -16,6 +18,10 @@ const Group = (restProps: Props) => {
|
||||
flexDirection: 'column'
|
||||
}
|
||||
}
|
||||
if(!children) return null;
|
||||
if(columns && columns > 1) {
|
||||
// TODO
|
||||
}
|
||||
return (
|
||||
<div className={classNames('Group', className)} {...props}>
|
||||
{title && <div className="Group__Title">{title}</div>}
|
||||
|
||||
@@ -25,8 +25,8 @@ class Modal extends React.Component<Props> {
|
||||
|
||||
handleKeyUp = (e: KeyboardEvent) => {
|
||||
if(e.code === 'Escape') {
|
||||
const { setModal } = this.props;
|
||||
e.preventDefault();
|
||||
const { setModal } = this.props;
|
||||
setModal(null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -55,4 +55,4 @@
|
||||
|
||||
&__Status
|
||||
font-weight: 300
|
||||
display: flex
|
||||
display: flex
|
||||
|
||||
@@ -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