Commit b2f2b2be by Иван Кубота

add locale template

toggle constants and templates redesign Info page reducers
parent 9ae7812c
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@material-ui/core": "latest", "@material-ui/core": "latest",
"@material-ui/icons": "^4.2.1",
"@types/react": "latest", "@types/react": "latest",
"@types/react-dom": "latest", "@types/react-dom": "latest",
"@types/react-redux": "^7.1.1", "@types/react-redux": "^7.1.1",
......
...@@ -10,31 +10,23 @@ import {MainAsideMenu} from "./component/MainAsideMenu"; ...@@ -10,31 +10,23 @@ import {MainAsideMenu} from "./component/MainAsideMenu";
import { Provider } from "react-redux"; import { Provider } from "react-redux";
import {Content} from "./component/Content"; import {Content} from "./component/Content";
function MadeWithLove() {
return (
<Typography variant="body2" color="textSecondary" align="right">
{'Built with love by the '}
<Link color="inherit" href="https://material-ui.com/">
Material-UI
</Link>
{' team.'}
</Typography>
);
}
export class App extends React.Component<{}, {}> { export class App extends React.Component<{}, {}> {
render() { render() {
return ( return (
<Provider store={store}> <Provider store={store}>
<Container maxWidth="xl">
<Grid container direction="row" justify="flex-start" alignItems="stretch"> <Grid container direction="row" justify="flex-start" alignItems="stretch">
<Box my={4}> <Grid item xs={3}>
<MainAsideMenu/> <MainAsideMenu/>
</Box> </Grid >
<Grid item xs={9}>
<Box my={4}> <Box my={4}>
<Container maxWidth={"xl"}>
<Content/> <Content/>
</Container>
</Box>
{/*<Typography variant="h4" component="h1" gutterBottom> {/*<Typography variant="h4" component="h1" gutterBottom>
Create React App v4-beta example with TypeScript Create React App v4-beta example with TypeScript
</Typography> </Typography>
...@@ -45,9 +37,8 @@ export class App extends React.Component<{}, {}> { ...@@ -45,9 +37,8 @@ export class App extends React.Component<{}, {}> {
<Button variant="contained">12345</Button> <Button variant="contained">12345</Button>
<Button variant="contained" color="primary">12345</Button> <Button variant="contained" color="primary">12345</Button>
<Button variant="contained" color="secondary">12345</Button>*/} <Button variant="contained" color="secondary">12345</Button>*/}
</Box> </Grid >
</Grid> </Grid>
</Container>
</Provider> </Provider>
); );
} }
......
...@@ -13,6 +13,7 @@ export const addLocale = Action('ADD_LOCALE', function (state: IMainStorage, {pa ...@@ -13,6 +13,7 @@ export const addLocale = Action('ADD_LOCALE', function (state: IMainStorage, {pa
...state.locale, ...state.locale,
[payload.id]: { [payload.id]: {
id: payload.id, id: payload.id,
collapsed: true,
values: [] values: []
} }
}}; }};
......
import {Action, ILocaleTemplate, IMainStorage} from './index'; import {Action, ILocaleTemplate, IMainStorage} from './index';
export interface IAddLocaleTemplate { export interface IAddLocaleTemplate {
payload: {id: string, template: ILocaleTemplate} payload: {id: string, template: ILocaleTemplate, collapsed?: boolean}
} }
export const addLocaleTemplate = Action('ADD_LOCALE_TEMPLATE', function (state: IMainStorage, {payload}: IAddLocaleTemplate) { export const addLocaleTemplate = Action('ADD_LOCALE_TEMPLATE', function (state: IMainStorage, {payload}: IAddLocaleTemplate) {
if(payload.id in state.locale) if(payload.id in state.locale) {
return {...state, locale: { const locale = state.locale[payload.id];
return {
...state, locale: {
...state.locale, ...state.locale,
[payload.id]: { [payload.id]: {
id: payload.id, id: payload.id,
collapsed: locale.collapsed,
values: state.locale[payload.id].values.concat(payload.template) values: state.locale[payload.id].values.concat(payload.template)
} }
}}; }
else };
return {...state, locale: { }else {
return {
...state, locale: {
...state.locale, ...state.locale,
[payload.id]: { [payload.id]: {
id: payload.id, id: payload.id,
collapsed: payload.collapsed || true,
values: [payload.template] values: [payload.template]
} }
}}; }
};
}
}); });
export interface IPredef { export interface IPredefinedTemplates {
[key: string]: string [key: string]: string
} }
const predef: IPredef = { const predef: IPredefinedTemplates = {
"ban": "Пользователь {{$first_name}} {{$last_name}} был забанен лопатой.", "ban": "Пользователь {{$first_name}} {{$last_name}} был забанен лопатой.",
"warn": "Пользователь {{$first_name}} {{$last_name}} получает {{$count}} предупреждение из {{$maxWarns}}, осталось {{$diff}} {{plural:$diff,предупреждение,предупреждения,предупреждений}}", "warn": "Пользователь {{$first_name}} {{$last_name}} получает {{$count}} предупреждение из {{$maxWarns}}, осталось {{$diff}} {{plural:$diff,предупреждение,предупреждения,предупреждений}}",
"unban": "Пользователя {{$full_name}} пришлось разбанить.", "unban": "Пользователя {{$full_name}} пришлось разбанить.",
"unbanGreeting": "Ты разбанены в {{$title}}", "unbanGreeting": "Ты разбанен в {{$title}}",
"pidor": "{{$first_name}}, ты — {{$condition?пацак:чатланин}}, а эта планета {{$condition?чатланская:пацакская}}.\n\nСчитай до {{$count}}.", "pidor": "{{$first_name}}, ты — {{$condition?пацак:чатланин}}, а эта планета {{$condition?чатланская:пацакская}}.\n\nСчитай до {{$count}}.",
"pidorDoNotCount1": "Считай, кому говорят", "pidorDoNotCount1": "Считай, кому говорят",
...@@ -59,7 +67,7 @@ setTimeout(()=>{ ...@@ -59,7 +67,7 @@ setTimeout(()=>{
addLocaleTemplate({ addLocaleTemplate({
id: key, id: key,
template: {uuid: key, text: predef[key]} template: {uuid: key, text: predef[key], enabled: true}
}); });
} }
}, 100) }, 100)
\ No newline at end of file
...@@ -2,10 +2,12 @@ ...@@ -2,10 +2,12 @@
import store from "../store"; import store from "../store";
export interface ILocaleTemplate { export interface ILocaleTemplate {
uuid: string, uuid: string,
text: string text: string,
enabled: boolean
} }
export interface ILocale { export interface ILocale {
id: string, id: string,
collapsed: boolean,
values: ILocaleTemplate[] values: ILocaleTemplate[]
} }
......
import {Action, IMainStorage} from './index';
export interface IToggleConstant {
payload: string
}
export const toggleConstant = Action('TOGGLE_CONSTANT', function (state: IMainStorage, {payload}: IToggleConstant) {
const id = payload;
if(!(id in state.locale)) {
return state;
} else {
const tpl = state.locale[id];
return {
...state, locale: {
...state.locale,
[id]: {
...tpl,
collapsed: !tpl.collapsed
}
}
};
}
});
\ No newline at end of file
import {Action, IMainStorage} from './index';
export interface IToggleTemplate {
payload: {
id: string,
uuid: string
}
}
export const toggleTemplate = Action('TOGGLE_TEMPLATE', function (state: IMainStorage, {payload}: IToggleTemplate) {
if(!(payload.id in state.locale)) {
console.error('No such constant id', payload.id);
return state;
} else {
const tpl = state.locale[payload.id];
const newState = {
...state, locale: {
...state.locale,
[payload.id]: {
...tpl,
values: tpl.values.map((el)=>el.uuid === payload.uuid ? {...el, enabled: !el.enabled} : el)
}
}
};
return newState;
}
});
\ No newline at end of file
import React from "react"; import React from "react";
import {connect} from "react-redux"; import {ILocale} from "../action-reducers";
import {ILocale, IMainStorage} from "../action-reducers"; import {
import {Button, List, ListItem, ListSubheader, TextField} from "@material-ui/core"; List,
ListItem,
ListItemText,
Collapse,
Switch,
ListItemSecondaryAction
} from "@material-ui/core";
import {NewLocaleTemplate} from "./NewLocaleTemplate"; import {NewLocaleTemplate} from "./NewLocaleTemplate";
import ExpandLess from '@material-ui/icons/ExpandLess';
import ExpandMore from '@material-ui/icons/ExpandMore';
import {toggleConstant} from "../action-reducers/toggleConstant";
import {toggleTemplate} from "../action-reducers/toggleTemplate";
export interface IConstant { export interface IConstant {
item: ILocale item: ILocale
...@@ -11,26 +21,41 @@ export interface IConstant { ...@@ -11,26 +21,41 @@ export interface IConstant {
class ConstantView extends React.Component<IConstant, IConstant> { class ConstantView extends React.Component<IConstant, IConstant> {
render(): React.ReactNode { render(): React.ReactNode {
const {id, values} = this.props.item; const {id, values, collapsed} = this.props.item;
return [<ListItem button onClick={()=>toggleConstant(id)} key={id+'-list'}>
<ListItemText primary={id} />
{!collapsed ? <ExpandLess /> : <ExpandMore />}
</ListItem>,
<Collapse in={!collapsed} timeout="auto" unmountOnExit key={id+'-collapse'}>
<List component="div" disablePadding>
{values.map((val)=>{
return <ListItem key={val.uuid} >
return <List subheader={<ListSubheader>{id}</ListSubheader>}>
{values.map((val)=>
<ListItem key={val.uuid}>
{val.text}
{/*<TextField {/*<TextField
value={val.text} value={val.text}
margin="normal" margin="normal"
inputProps={{ 'aria-label': 'bare' }} inputProps={{ 'aria-label': 'bare' }}
/>*/} />*/}
</ListItem> <ListItemText id="switch-list-label-wifi" primary={val.text} />
<ListItemSecondaryAction>
<Switch
edge="end"
onChange={()=>toggleTemplate({id: id, uuid: val.uuid})}
checked={val.enabled}
/>
</ListItemSecondaryAction>
</ListItem>}
)} )}
<NewLocaleTemplate id={id}/> <NewLocaleTemplate id={id}/>
</List>
</Collapse>];
</List>;
} }
} }
/*const mapConstant = (store: IMainStorage): ILocale =>*/
export const Constant = /*connect(mapConstant)(*/ConstantView/*)*/;
export const Constant = ConstantView;
import React from "react"; import React from "react";
import {setActiveMenu} from "../action-reducers/setActiveMenu"; import {setActiveMenu} from "../action-reducers/setActiveMenu";
import {List, ListItem, ListItemText} from "@material-ui/core"; import {List, ListItem, ListItemText, ListItemIcon} from "@material-ui/core";
import {mainMenu} from "../model/mainMenu"; import {mainMenu} from "../model/mainMenu";
import {connect} from "react-redux"; import {connect} from "react-redux";
import {IMainStorage} from "../action-reducers"; import {IMainStorage} from "../action-reducers";
...@@ -13,12 +13,17 @@ class MainAsideMenuView extends React.Component<IMainAsideMenu, IMainAsideMenu> ...@@ -13,12 +13,17 @@ class MainAsideMenuView extends React.Component<IMainAsideMenu, IMainAsideMenu>
const {activeMenu} = this.props; const {activeMenu} = this.props;
return <List> return <List>
{mainMenu.map((item) => ( {mainMenu.map((item) => {
<ListItem button selected={(activeMenu === item.id)} key={item.id} onClick={() => { const Icon = item.icon;
setActiveMenu(item.id)}}> return <ListItem button selected={(activeMenu === item.id)} key={item.id} onClick={() => {
setActiveMenu(item.id)
}}>
<ListItemIcon>
<Icon/>
</ListItemIcon>
<ListItemText color={(activeMenu === item.id) ? "primary" : "secondary"} primary={item.text}/> <ListItemText color={(activeMenu === item.id) ? "primary" : "secondary"} primary={item.text}/>
</ListItem> </ListItem>
))} })}
</List>; </List>;
} }
} }
......
import React from "react"; import React, {KeyboardEvent, ChangeEvent} from "react";
import {connect} from "react-redux";
import {IMainStorage} from "../action-reducers";
import {Button, Grid, TextField} from "@material-ui/core"; import {Button, Grid, TextField} from "@material-ui/core";
import {addLocaleTemplate, IAddLocaleTemplate} from "../action-reducers/addLocaleTemplate"; import {addLocaleTemplate} from "../action-reducers/addLocaleTemplate";
import {getUUID} from "../helpers"; import {getUUID} from "../helpers";
export interface INewLocaleTemplate { export interface INewLocaleTemplate {
...@@ -12,29 +10,43 @@ export interface INewLocaleTemplate { ...@@ -12,29 +10,43 @@ export interface INewLocaleTemplate {
class NewLocaleTemplateView extends React.Component<INewLocaleTemplate, {}> { class NewLocaleTemplateView extends React.Component<INewLocaleTemplate, {}> {
state = { state = {
expanded: false, expanded: false,
text: '' text: '',
invalid: false
}; };
addAction() { addAction() {
addLocaleTemplate({id: this.props.id, template: {uuid: getUUID(), text: this.state.text}}); const text = this.state.text.trim();
if(text.length > 0) {
addLocaleTemplate({id: this.props.id, template: {uuid: getUUID(), text: text, enabled: true}});
this.setState({expanded: false, text: ''}) this.setState({expanded: false, text: ''})
}else{
this.setState({invalid: true})
}
} }
render(): React.ReactNode { render(): React.ReactNode {
const {invalid} = this.state;
return <div> return <div>
{this.state.expanded ? {this.state.expanded ?
<div> <div>
<Grid spacing={1} container>
<Grid item>
<TextField <TextField
error={invalid}
value={this.state.text} value={this.state.text}
onKeyDown={(e) => e.key === 'Enter' && e.ctrlKey && this.addAction()} onKeyDown={(e: KeyboardEvent) => {
onChange={(e) => this.setState({text: e.target.value})} if(e.key === 'Enter' && e.ctrlKey) {
this.addAction();
}
if(invalid)
this.setState({invalid: false});
}}
onChange={(e: ChangeEvent) => this.setState({text: (e.target as HTMLInputElement).value})}
margin="normal" margin="normal"
fullWidth
placeholder={'Шаблон вводить сюда'}
inputProps={{'aria-label': 'bare'}} inputProps={{'aria-label': 'bare'}}
/> />
</Grid> <Grid spacing={1} container>
<Grid item> <Grid item>
<Button variant="contained" color="secondary" onClick={() => this.addAction()}> <Button variant="contained" color="secondary" onClick={() => this.addAction()}>
+ Сохранить (Ctrl+enter) + Сохранить (Ctrl+enter)
......
...@@ -5,6 +5,9 @@ import {Info} from "../view/Info"; ...@@ -5,6 +5,9 @@ import {Info} from "../view/Info";
import {setActiveMenu} from "../action-reducers/setActiveMenu"; import {setActiveMenu} from "../action-reducers/setActiveMenu";
import {AccountCircle, Help, HotTub} from '@material-ui/icons';
import {SvgIconComponent} from "@material-ui/icons";
export interface IComponentLambda { export interface IComponentLambda {
(): React.ComponentClass (): React.ComponentClass
} }
...@@ -12,26 +15,32 @@ export interface menuItem { ...@@ -12,26 +15,32 @@ export interface menuItem {
id: string, id: string,
text: string, text: string,
active ?: boolean, active ?: boolean,
view: IComponentLambda view: IComponentLambda,
icon: SvgIconComponent
} }
export const mainMenu: menuItem[] = [ export const mainMenu: menuItem[] = [
{ {
id: 'info',
text: 'Info',
active: true,
view: ()=>Info
},
{
id: 'constants', id: 'constants',
text: 'Constants', text: 'Constants',
view: ()=>Constants active: true,
view: ()=>Constants,
icon: HotTub
}, },
{ {
id: 'profile', id: 'profile',
text: 'Profile', text: 'Profile',
active: true, active: true,
view: ()=>Profile view: ()=>Profile,
}]; icon: AccountCircle
},
{
id: 'info',
text: 'Info',
view: ()=>Info,
icon: Help
}
];
setActiveMenu(mainMenu.filter((el)=>el.active)[0].id) setActiveMenu(mainMenu.filter((el)=>el.active)[0].id)
...@@ -16,9 +16,11 @@ class ConstantsView extends React.Component<IConstants, IConstants> { ...@@ -16,9 +16,11 @@ class ConstantsView extends React.Component<IConstants, IConstants> {
localeList = Object.keys(locale).filter((key)=>key.charAt(0)!=='_').map((key)=>locale[key]).sort(AZ('id')); localeList = Object.keys(locale).filter((key)=>key.charAt(0)!=='_').map((key)=>locale[key]).sort(AZ('id'));
return <Container> return <Container>
<NewLocale/> <List>
{/*<NewLocale/>*/}
{localeList.map((item)=><Constant key={item.id} item={item}/>)} {localeList.map((item)=><Constant key={item.id} item={item}/>)}
</List>
</Container>; </Container>;
} }
} }
......
import React from "react"; import React from "react";
import {connect} from "react-redux"; import {connect} from "react-redux";
import {IMainStorage} from "../action-reducers"; import {IMainStorage} from "../action-reducers";
import {
Warning,
Delete,
SvgIconComponent,
HotTub,
AccessibleForward,
FilterList,
Code,
CloudQueue, Build,
Storage, Language
} from '@material-ui/icons';
import {Box, Container, Link, List, ListItem, ListItemIcon, ListItemText, Typography} from "@material-ui/core";
export interface IListItem {
text: string,
icon: SvgIconComponent,
comment ?: string,
link ?: string
}
const doList = (arr: IListItem[])=><List dense={true}>
{arr.map((item, i)=>{
const Icon = item.icon;
return <ListItem key={i}>
<ListItemIcon>
<Icon/>
</ListItemIcon>
<ListItemText>
{item.link?<Link href={item.link} color="secondary">{item.text}</Link>:item.text}
{item.comment? <Typography display="inline" color="textSecondary">&nbsp;{item.comment}</Typography>:''}
</ListItemText>
</ListItem>
})}
</List>;
class InfoView extends React.Component<{}, {}> { class InfoView extends React.Component<{}, {}> {
render(): React.ReactNode { render(): React.ReactNode {
return <div> return <Container>
Этот бот может расширять свой лексикон <Typography variant="h4" gutterBottom>Информация о боте, его умениях и остальном</Typography>
</div>;
<Typography variant="subtitle1">Telegram Bot со стандартным функционалом:</Typography>
{doList([
{text: 'фильтрация контента', icon: FilterList},
{text: 'предупреждения', icon: Warning},
{text: 'бан', icon: Delete}
])}
<Typography variant="subtitle1">Нестандартным:</Typography>
{doList([
{text: 'чмырение', icon: AccessibleForward},
{text: 'расширение лексикона', icon: HotTub}
])}
<Box mt={4}>
<Typography variant="subtitle2" gutterBottom>Причина создания</Typography>
<Typography variant="caption">Тёркин захотел бота для увеселения кипрского комьюнити тестировщиков,
я поддурился потому что давно хотел поиграться с tg-bot-api и всё не было повода.</Typography>
</Box>
<Box mt={4}>
<Typography variant="subtitle2" gutterBottom>Stack:</Typography>
{doList([
{text: 'Node.js', icon: Code, comment: '>= 8.7.0', link: 'https://nodejs.org/'},
{text: 'Typescript', icon: Code, link: 'https://www.typescriptlang.org/'},
{text: 'localize.js', icon: Language, link: 'https://www.npmjs.com/package/localize.js'},
{text: 'React', icon: Code, link: 'https://reactjs.org/'},
{text: 'Redux', icon: Code, link: 'https://redux.js.org/'},
{text: 'Redux-thunk', icon: Code, link: 'https://github.com/reduxjs/redux-thunk'},
{text: 'Material-ui', icon: Code, link: 'https://material-ui.com/'},
{text: 'Material-ui-icons', icon: Code, link: 'https://material-ui.com/components/material-icons/'},
{text: 'Maria DB', icon: Storage, link: 'https://mariadb.org/'},
{text: 'db-migrate', icon: Storage, comment: '— использовать базу в 2k19 без миграций запрещено', link: 'https://db-migrate.readthedocs.io/en/latest/'},
{text: 'Telegram Bot API', icon: CloudQueue, link: 'https://core.telegram.org/bots/api'},
{text: 'Yarn', icon: Build, link: 'https://yarnpkg.com/'},
{text: 'Webpack', icon: Build, link: 'https://webpack.js.org/'}
])}
</Box>
</Container>;
} }
} }
......
...@@ -756,7 +756,7 @@ ...@@ -756,7 +756,7 @@
"@babel/helper-plugin-utils" "^7.0.0" "@babel/helper-plugin-utils" "^7.0.0"
"@babel/plugin-transform-typescript" "^7.3.2" "@babel/plugin-transform-typescript" "^7.3.2"
"@babel/runtime@7.5.5", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5": "@babel/runtime@7.5.5", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.3.4", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5":
version "7.5.5" version "7.5.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ== integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
...@@ -1024,6 +1024,13 @@ ...@@ -1024,6 +1024,13 @@
react-transition-group "^4.0.0" react-transition-group "^4.0.0"
warning "^4.0.1" warning "^4.0.1"
"@material-ui/icons@^4.2.1":
version "4.2.1"
resolved "https://registry.yarnpkg.com/@material-ui/icons/-/icons-4.2.1.tgz#fe2f1c4f60c24256d244a69d86d0c00e8ed4037e"
integrity sha512-FvSD5lUBJ66frI4l4AYAPy2CH14Zs2Dgm0o3oOMr33BdQtOAjCgbdOcvPBeaD1w6OQl31uNW3CKOE8xfPNxvUQ==
dependencies:
"@babel/runtime" "^7.2.0"
"@material-ui/styles@^4.3.0": "@material-ui/styles@^4.3.0":
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.3.0.tgz#27f11fbf061d8a20ad5703acb0dbb0e69cc00345" resolved "https://registry.yarnpkg.com/@material-ui/styles/-/styles-4.3.0.tgz#27f11fbf061d8a20ad5703acb0dbb0e69cc00345"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment