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 @@
"private": true,
"dependencies": {
"@material-ui/core": "latest",
"@material-ui/icons": "^4.2.1",
"@types/react": "latest",
"@types/react-dom": "latest",
"@types/react-redux": "^7.1.1",
......
......@@ -10,31 +10,23 @@ import {MainAsideMenu} from "./component/MainAsideMenu";
import { Provider } from "react-redux";
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<{}, {}> {
render() {
return (
<Provider store={store}>
<Container maxWidth="xl">
<Grid container direction="row" justify="flex-start" alignItems="stretch">
<Box my={4}>
<MainAsideMenu/>
<Grid item xs={3}>
<MainAsideMenu/>
</Grid >
<Grid item xs={9}>
<Box my={4}>
<Container maxWidth={"xl"}>
<Content/>
</Container>
</Box>
<Box my={4}>
<Content/>
{/*<Typography variant="h4" component="h1" gutterBottom>
Create React App v4-beta example with TypeScript
</Typography>
......@@ -45,9 +37,8 @@ export class App extends React.Component<{}, {}> {
<Button variant="contained">12345</Button>
<Button variant="contained" color="primary">12345</Button>
<Button variant="contained" color="secondary">12345</Button>*/}
</Box>
</Grid >
</Grid>
</Container>
</Provider>
);
}
......
......@@ -13,6 +13,7 @@ export const addLocale = Action('ADD_LOCALE', function (state: IMainStorage, {pa
...state.locale,
[payload.id]: {
id: payload.id,
collapsed: true,
values: []
}
}};
......
import {Action, ILocaleTemplate, IMainStorage} from './index';
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) {
if(payload.id in state.locale)
return {...state, locale: {
...state.locale,
if(payload.id in state.locale) {
const locale = state.locale[payload.id];
return {
...state, locale: {
...state.locale,
[payload.id]: {
id: payload.id,
collapsed: locale.collapsed,
values: state.locale[payload.id].values.concat(payload.template)
}
}
}};
else
return {...state, locale: {
};
}else {
return {
...state, locale: {
...state.locale,
[payload.id]: {
id: payload.id,
collapsed: payload.collapsed || true,
values: [payload.template]
}
}};
}
};
}
});
export interface IPredef {
export interface IPredefinedTemplates {
[key: string]: string
}
const predef: IPredef = {
const predef: IPredefinedTemplates = {
"ban": "Пользователь {{$first_name}} {{$last_name}} был забанен лопатой.",
"warn": "Пользователь {{$first_name}} {{$last_name}} получает {{$count}} предупреждение из {{$maxWarns}}, осталось {{$diff}} {{plural:$diff,предупреждение,предупреждения,предупреждений}}",
"unban": "Пользователя {{$full_name}} пришлось разбанить.",
"unbanGreeting": "Ты разбанены в {{$title}}",
"unbanGreeting": "Ты разбанен в {{$title}}",
"pidor": "{{$first_name}}, ты — {{$condition?пацак:чатланин}}, а эта планета {{$condition?чатланская:пацакская}}.\n\nСчитай до {{$count}}.",
"pidorDoNotCount1": "Считай, кому говорят",
......@@ -59,7 +67,7 @@ setTimeout(()=>{
addLocaleTemplate({
id: key,
template: {uuid: key, text: predef[key]}
template: {uuid: key, text: predef[key], enabled: true}
});
}
}, 100)
\ No newline at end of file
......@@ -2,10 +2,12 @@
import store from "../store";
export interface ILocaleTemplate {
uuid: string,
text: string
text: string,
enabled: boolean
}
export interface ILocale {
id: string,
collapsed: boolean,
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 {connect} from "react-redux";
import {ILocale, IMainStorage} from "../action-reducers";
import {Button, List, ListItem, ListSubheader, TextField} from "@material-ui/core";
import {ILocale} from "../action-reducers";
import {
List,
ListItem,
ListItemText,
Collapse,
Switch,
ListItemSecondaryAction
} from "@material-ui/core";
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 {
item: ILocale
......@@ -11,26 +21,41 @@ export interface IConstant {
class ConstantView extends React.Component<IConstant, IConstant> {
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
value={val.text}
margin="normal"
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}/>
</List>
</Collapse>];
</List>;
}
}
/*const mapConstant = (store: IMainStorage): ILocale =>*/
export const Constant = ConstantView;
export const Constant = /*connect(mapConstant)(*/ConstantView/*)*/;
import React from "react";
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 {connect} from "react-redux";
import {IMainStorage} from "../action-reducers";
......@@ -13,12 +13,17 @@ class MainAsideMenuView extends React.Component<IMainAsideMenu, IMainAsideMenu>
const {activeMenu} = this.props;
return <List>
{mainMenu.map((item) => (
<ListItem button selected={(activeMenu === item.id)} key={item.id} onClick={() => {
setActiveMenu(item.id)}}>
<ListItemText color={(activeMenu === item.id) ? "primary" : "secondary"} primary={item.text}/>
{mainMenu.map((item) => {
const Icon = item.icon;
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}/>
</ListItem>
))}
})}
</List>;
}
}
......
import React from "react";
import {connect} from "react-redux";
import {IMainStorage} from "../action-reducers";
import React, {KeyboardEvent, ChangeEvent} from "react";
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";
export interface INewLocaleTemplate {
......@@ -12,29 +10,43 @@ export interface INewLocaleTemplate {
class NewLocaleTemplateView extends React.Component<INewLocaleTemplate, {}> {
state = {
expanded: false,
text: ''
text: '',
invalid: false
};
addAction() {
addLocaleTemplate({id: this.props.id, template: {uuid: getUUID(), text: this.state.text}});
this.setState({expanded: false, 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: ''})
}else{
this.setState({invalid: true})
}
}
render(): React.ReactNode {
const {invalid} = this.state;
return <div>
{this.state.expanded ?
<div>
<TextField
error={invalid}
value={this.state.text}
onKeyDown={(e: KeyboardEvent) => {
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"
fullWidth
placeholder={'Шаблон вводить сюда'}
inputProps={{'aria-label': 'bare'}}
/>
<Grid spacing={1} container>
<Grid item>
<TextField
value={this.state.text}
onKeyDown={(e) => e.key === 'Enter' && e.ctrlKey && this.addAction()}
onChange={(e) => this.setState({text: e.target.value})}
margin="normal"
inputProps={{'aria-label': 'bare'}}
/>
</Grid>
<Grid item>
<Button variant="contained" color="secondary" onClick={() => this.addAction()}>
+ Сохранить (Ctrl+enter)
......
......@@ -5,6 +5,9 @@ import {Info} from "../view/Info";
import {setActiveMenu} from "../action-reducers/setActiveMenu";
import {AccountCircle, Help, HotTub} from '@material-ui/icons';
import {SvgIconComponent} from "@material-ui/icons";
export interface IComponentLambda {
(): React.ComponentClass
}
......@@ -12,26 +15,32 @@ export interface menuItem {
id: string,
text: string,
active ?: boolean,
view: IComponentLambda
view: IComponentLambda,
icon: SvgIconComponent
}
export const mainMenu: menuItem[] = [
{
id: 'info',
text: 'Info',
active: true,
view: ()=>Info
},
{
id: 'constants',
text: 'Constants',
view: ()=>Constants
active: true,
view: ()=>Constants,
icon: HotTub
},
{
id: 'profile',
text: 'Profile',
active: true,
view: ()=>Profile
}];
view: ()=>Profile,
icon: AccountCircle
},
{
id: 'info',
text: 'Info',
view: ()=>Info,
icon: Help
}
];
setActiveMenu(mainMenu.filter((el)=>el.active)[0].id)
......@@ -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'));
return <Container>
<NewLocale/>
<List>
{/*<NewLocale/>*/}
{localeList.map((item)=><Constant key={item.id} item={item}/>)}
</List>
</Container>;
}
}
......
import React from "react";
import {connect} from "react-redux";
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<{}, {}> {
render(): React.ReactNode {
return <div>
Этот бот может расширять свой лексикон
</div>;
return <Container>
<Typography variant="h4" gutterBottom>Информация о боте, его умениях и остальном</Typography>
<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 @@
"@babel/helper-plugin-utils" "^7.0.0"
"@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"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.5.tgz#74fba56d35efbeca444091c7850ccd494fd2f132"
integrity sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==
......@@ -1024,6 +1024,13 @@
react-transition-group "^4.0.0"
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":
version "4.3.0"
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