clubs, novel links

This commit is contained in:
2021-02-17 19:39:52 +03:00
parent ff23dcb5eb
commit fa13227e0b
23 changed files with 235 additions and 182 deletions
+2 -2
View File
@@ -6,7 +6,7 @@
$mainColor: #efefef $mainColor: #efefef
$secondaryColor: #aaaaaa $secondaryColor: #aaaaaa
$linkHoverColor: #dfdfdf $linkHoverColor: #efefef
// BLACK // BLACK
$primaryBackgroundColor: #121212 $primaryBackgroundColor: #121212
@@ -57,4 +57,4 @@ $blockPadding: .2rem
$borderRadius: 4px $borderRadius: 4px
$novelBlockSize: calc(100% / 4 - 1rem) $novelBlockSize: calc(100% / 4 - 1rem)
+8 -4
View File
@@ -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
View File
@@ -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}`);
+2
View File
@@ -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)
+25 -5
View File
@@ -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) {
document.body.setAttribute('theme', 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 => { 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}/>
+10 -8
View File
@@ -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}>
</Group> return (
</div> <Group title={club.name}>
) <img src={club.avatar} width={96} height={96} style={{ borderRadius: '50%' }} alt=""/>
</Group>
);
} }
} }
+39
View File
@@ -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;
+1 -1
View File
@@ -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>
); );
+35
View File
@@ -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;
+2 -4
View File
@@ -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
+6 -2
View File
@@ -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>
+1 -1
View File
@@ -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} />
+21 -4
View File
@@ -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>
+52 -3
View File
@@ -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>
+3
View File
@@ -12,3 +12,6 @@
input, .Button input, .Button
font-size: 1.1rem font-size: 1.1rem
.Button
margin: .5rem 0
+1 -1
View File
@@ -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>) => {
+8 -1
View File
@@ -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
} }
+9 -1
View File
@@ -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 = {
+7 -1
View File
@@ -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>}
+1 -1
View File
@@ -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 -1
View File
@@ -55,4 +55,4 @@
&__Status &__Status
font-weight: 300 font-weight: 300
display: flex display: flex
-121
View File
@@ -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)
-21
View File
@@ -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;