changelog: dialog for changelog

This commit is contained in:
muerwre 2018-12-07 17:38:49 +07:00
parent b719b3f1a2
commit 65e549a5af
12 changed files with 218 additions and 34 deletions

View file

@ -0,0 +1,74 @@
// @flow
import React from 'react';
import { Scroll } from '$components/Scroll';
import { APP_INFO } from '$constants/app_info';
export const AppInfoDialog = () => (
<div className="dialog-content">
<div className="dialog-head">
<div className="dialog-head-title">
Orchid Map
</div>
<div className="small gray">
версия{' '}
{(APP_INFO.VERSION || 1)}.
{(APP_INFO.CHANGELOG[APP_INFO.VERSION].length || 0)}
</div>
<div className="small app-info-list">
<div>
Исходный код:{' '}
<a href="//github.com/muerwre/orchidMap" target="_blank">github.com/muerwre/orchidMap</a>
</div>
</div>
<div className="small app-info-list">
<div>
Frontend:{' '}
<a href="//reactjs.org/" target="_blank">ReactJS</a>,{' '}
<a href="//leafletjs.com" target="_blank">Leaflet</a>,{' '}
<a href="//www.liedman.net/leaflet-routing-machine/" target="_blank">Leaflet Routing Machine</a>{' '}
</div>
<div>
Backend:{' '}
<a href="//project-osrm.org/" target="_blank">OSRM</a>,{' '}
<a href="//nodejs.org/" target="_blank">NodeJS</a>,{' '}
<a href="//expressjs.com/" target="_blank">ExpressJS</a>,{' '}
<a href="//mongodb.com/" target="_blank">MongoDB</a>
</div>
</div>
</div>
<Scroll className="dialog-shader">
<div>
<div className="app-info-changelog">
<h2>История изменений</h2>
{
[...Object.keys(APP_INFO.CHANGELOG)].reverse().map((version, i) => (
<div className="app-info-changelog-item" key={version}>
<div className="app-info-number">{version}.</div>
<div className="app-info-version">
{
APP_INFO.CHANGELOG[version].map((release, y) => (
<div className="app-info-release" key={release}>
<div className="app-info-number">{APP_INFO.CHANGELOG[version].length - y}.</div>
<div className="app-info-build">
{
APP_INFO.CHANGELOG[version][y].map((build, z) => (
<div className="app-info-change" key={build}>
<div className="app-info-number">{(z)}.</div>
<span>{APP_INFO.CHANGELOG[version][y][z]}</span>
</div>
))
}
</div>
</div>
))
}
</div>
</div>
))
}
</div>
</div>
</Scroll>
</div>
);

View file

@ -4,7 +4,7 @@ import { GuestButton } from '$components/user/GuestButton';
import { DEFAULT_USER, ROLES } from '$constants/auth'; import { DEFAULT_USER, ROLES } from '$constants/auth';
import { UserButton } from '$components/user/UserButton'; import { UserButton } from '$components/user/UserButton';
import { UserMenu } from '$components/user/UserMenu'; import { UserMenu } from '$components/user/UserMenu';
import { setUser, userLogout, takeAShot, setDialog, gotVkUser } from '$redux/user/actions'; import { setUser, userLogout, takeAShot, setDialog, gotVkUser, setDialogActive } from '$redux/user/actions';
import { bindActionCreators } from 'redux'; import { bindActionCreators } from 'redux';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import type { UserType } from '$constants/types'; import type { UserType } from '$constants/types';
@ -21,6 +21,7 @@ type Props = {
userLogout: Function, userLogout: Function,
setDialog: Function, setDialog: Function,
setDialogActive: Function,
gotVkUser: Function, gotVkUser: Function,
}; };
@ -59,7 +60,14 @@ export class Component extends React.PureComponent<Props, void> {
setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened }); setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened });
openMapsDialog = () => { openMapsDialog = () => {
this.props.setDialog({ dialog: DIALOGS.MAP_LIST }); this.props.setDialog(DIALOGS.MAP_LIST);
this.props.setDialogActive(this.props.dialog !== DIALOGS.MAP_LIST);
};
openAppInfoDialog = () => {
this.setMenuOpened();
this.props.setDialog(DIALOGS.APP_INFO);
this.props.setDialogActive(this.props.dialog !== DIALOGS.APP_INFO);
}; };
openOauthFrame = () => { openOauthFrame = () => {
@ -94,7 +102,7 @@ export class Component extends React.PureComponent<Props, void> {
} }
{ {
(user && user.role && user.role !== 'guest' && menuOpened) && (user && user.role && user.role !== 'guest' && menuOpened) &&
<UserMenu user={user} userLogout={this.props.userLogout} /> <UserMenu user={user} userLogout={this.props.userLogout} openAppInfoDialog={this.openAppInfoDialog} />
} }
</div> </div>
@ -125,6 +133,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({
takeAShot, takeAShot,
setDialog, setDialog,
gotVkUser, gotVkUser,
setDialogActive,
}, dispatch); }, dispatch);
export const UserPanel = connect(mapStateToProps, mapDispatchToProps)(Component); export const UserPanel = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -1,20 +1,24 @@
import React from 'react'; import React from 'react';
import { CLIENT } from '$config/frontend'; import { APP_INFO } from '$constants/app_info';
type Props = { type Props = {
userLogout: Function, userLogout: Function,
openAppInfoDialog: Function,
} }
export const UserMenu = ({ userLogout }: Props) => ( export const UserMenu = ({ userLogout, openAppInfoDialog }: Props) => (
<div className="user-panel-menu"> <div className="user-panel-menu">
<div className="user-panel-title"> <div className="user-panel-title">
ORCHID ORCHID
<br /> <br />
MAP MAP
<span className="user-panel-ver"> <span className="user-panel-ver">
- { CLIENT.VER } - {(APP_INFO.VERSION || 1)}.{(APP_INFO.RELEASE.length || 0)}
</span> </span>
</div> </div>
<div className="user-panel-item" onClick={openAppInfoDialog}>
О редакторе карт
</div>
<a className="user-panel-item" href="https://github.com/muerwre/orchidMap" target="_blank" rel="noopener noreferrer"> <a className="user-panel-item" href="https://github.com/muerwre/orchidMap" target="_blank" rel="noopener noreferrer">
Проект на github Проект на github
</a> </a>

View file

@ -3,28 +3,28 @@ export const APP_INFO = {
RELEASE: 1, RELEASE: 1,
CHANGELOG: { CHANGELOG: {
2: { 2: [
0: [ [
'15.08.18 Первый коммит', 'Redux, redux-saga', // [26.11.18]
'15.08.18 ReactJS для управления интерфейсом', 'Рисование карт на стороне клиента', // [28.11.18]
'16.08.18 Карта, роутер, стикеры, панели редактора', 'Backend на expressjs + mongoose', // [30.11.18]
'27.08.18 Выбор логотипа и стиля карты', 'Импорт данных из старых версий карт', // [06.12.18]
'29.08.18 Переключение режимов, сохранение', 'Диалог со списком карт пользователя', // [07.12.18]
'04.09.18 Загрузка карт, перерисовка данных, маршруты', 'Мобильный интерфейс', // [07.12.18]
],
[
'Первый коммит', // [15.08.18]
'ReactJS для управления интерфейсом', // [15.08.18]
'Карта, роутер, стикеры, панели редактора', // [16.08.18]
'Выбор логотипа и стиля карты', // [27.08.18]
'Переключение режимов, сохранение', // [29.08.18]
'Загрузка карт, перерисовка данных, маршруты', // [04.09.18]
],
], ],
1: [ 1: [
'26.11.18 Redux, redux-saga', [
'28.11.18 Рисование карт на стороне клиента',
'30.11.18 Backend на expressjs + mongoose',
'04.12.18 Backend на expressjs + mongoose',
'06.12.18 Импорт данных из старых версий карт',
'07.12.18 Диалог со списком карт пользователя',
]
},
1: {
0: [
'Первый работающий редактор карт' 'Первый работающий редактор карт'
] ]
} ]
} }
}; };

View file

@ -2,4 +2,5 @@
export const DIALOGS = ({ export const DIALOGS = ({
NONE: 'NONE', NONE: 'NONE',
MAP_LIST: 'MAP_LIST', MAP_LIST: 'MAP_LIST',
APP_INFO: 'APP_INFO',
}: { [key: String]: String }); }: { [key: String]: String });

View file

@ -3,14 +3,20 @@ import React from 'react';
import { DIALOGS } from '$constants/dialogs'; import { DIALOGS } from '$constants/dialogs';
import { MapListDialog } from '$components/dialogs/MapListDialog'; import { MapListDialog } from '$components/dialogs/MapListDialog';
import classnames from 'classnames'; import classnames from 'classnames';
import { AppInfoDialog } from '$components/dialogs/AppInfoDialog';
type Props = { type Props = {
dialog: String, dialog: String,
dialog_active: Boolean, dialog_active: Boolean,
} }
const LEFT_DIALOGS = {
[DIALOGS.MAP_LIST]: MapListDialog,
[DIALOGS.APP_INFO]: AppInfoDialog,
};
export const LeftDialog = ({ dialog, dialog_active }: Props) => ( export const LeftDialog = ({ dialog, dialog_active }: Props) => (
<div className={classnames('dialog', { active: dialog_active })}> <div className={classnames('dialog', { active: dialog_active })}>
{ dialog === DIALOGS.MAP_LIST && <MapListDialog /> } { dialog && LEFT_DIALOGS[dialog] && React.createElement(LEFT_DIALOGS[dialog]) }
</div> </div>
); );

View file

@ -40,9 +40,10 @@ export const cropAShot = payload => ({ type: ACTIONS.CROP_A_SHOT, ...payload });
export const setProvider = provider => ({ type: ACTIONS.SET_PROVIDER, provider }); export const setProvider = provider => ({ type: ACTIONS.SET_PROVIDER, provider });
export const setDialog = ({ dialog, dialog_active }) => ({ type: ACTIONS.SET_DIALOG, dialog }); export const setDialog = dialog => ({ type: ACTIONS.SET_DIALOG, dialog });
export const setDialogActive = dialog_active => ({ type: ACTIONS.SET_DIALOG_ACTIVE, dialog_active });
export const locationChanged = location => ({ type: ACTIONS.LOCATION_CHANGED, location }); export const locationChanged = location => ({ type: ACTIONS.LOCATION_CHANGED, location });
export const setReady = ready => ({ type: ACTIONS.SET_READY, ready }); export const setReady = ready => ({ type: ACTIONS.SET_READY, ready });
export const gotVkUser = user => ({ type: ACTIONS.GOT_VK_USER, user }); export const gotVkUser = user => ({ type: ACTIONS.GOT_VK_USER, user });

View file

@ -42,6 +42,7 @@ export const ACTIONS = ({
SET_PROVIDER: 'SET_PROVIDER', SET_PROVIDER: 'SET_PROVIDER',
SET_DIALOG: 'SET_DIALOG', SET_DIALOG: 'SET_DIALOG',
SET_DIALOG_ACTIVE: 'SET_DIALOG_ACTIVE',
LOCATION_CHANGED: 'LOCATION_CHANGED', LOCATION_CHANGED: 'LOCATION_CHANGED',
SET_READY: 'SET_READY', SET_READY: 'SET_READY',

View file

@ -78,10 +78,16 @@ const setRenderer = (state, { payload }) => ({
const setProvider = (state, { provider }) => ({ ...state, provider }); const setProvider = (state, { provider }) => ({ ...state, provider });
const setDialog = (state, { dialog, dialog_active }) => ({ const setDialog = (state, { dialog }) => ({
...state, ...state,
dialog: dialog || state.dialog, dialog,
dialog_active: typeof dialog_active !== 'undefined' ? dialog_active : !state.dialog_active, // dialog_active: typeof dialog_active !== 'undefined' ? dialog_active : !state.dialog_active,
// dialog_active,
});
const setDialogActive = (state, { dialog_active }) => ({
...state,
dialog_active: dialog_active || !state.dialog_active,
}); });
const setReady = (state, { ready = true }) => ({ const setReady = (state, { ready = true }) => ({
@ -114,6 +120,7 @@ const HANDLERS = ({
[ACTIONS.SET_PROVIDER]: setProvider, [ACTIONS.SET_PROVIDER]: setProvider,
[ACTIONS.SET_DIALOG]: setDialog, [ACTIONS.SET_DIALOG]: setDialog,
[ACTIONS.SET_DIALOG_ACTIVE]: setDialogActive,
[ACTIONS.SET_READY]: setReady, [ACTIONS.SET_READY]: setReady,
}: { [key: String]: Function }); }: { [key: String]: Function });

View file

@ -38,6 +38,11 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
a {
color: white;
opacity: 0.8;
}
} }
.dialog-shader { .dialog-shader {
@ -125,3 +130,71 @@
font-size: 20px; font-size: 20px;
text-transform: uppercase; text-transform: uppercase;
} }
.app-info-changelog {
color: white;
padding: 10px;
font-size: 0.8em;
div {
opacity: 0.8;
}
}
.app-info-number {
width: 16px;
}
.app-info-changelog-item {
text-transform: uppercase;
display: flex;
flex: 1;
.app-info-current {
font-size: 0.9em;
opacity: 0.3;
display: inline;
padding-left: 10px;
}
}
.app-info-version {
padding-bottom: 5px;
flex: 1;
flex-direction: column-reverse;
}
.app-info-release {
padding-bottom: 5px;
display: flex;
flex: 1;
}
.app-info-build {
padding-bottom: 5px;
display: flex;
flex-direction: column-reverse;
flex: 1;
}
.app-info-change {
display: flex;
flex-direction: row;
padding-bottom: 5px;
.app-info-number {
width: 20px;
}
span {
flex: 1;
}
}
.app-info-list {
padding: 10px 0;
div {
padding: 2.5px 0;
}
}

View file

@ -117,3 +117,12 @@ body {
@media (max-width: @mobile_breakpoint) { @media (max-width: @mobile_breakpoint) {
.desktop-only { display: none; } .desktop-only { display: none; }
} }
h2 {
font: inherit;
font-size: 18px;
font-weight: 400;
text-transform: uppercase;
margin: 10px 0;
}

View file

@ -164,9 +164,8 @@
} }
.panel-user { .panel-user {
z-index: 1;
@media (max-width: @mobile_breakpoint) { @media (max-width: @mobile_breakpoint) {
z-index: 1;
flex-direction: column-reverse; flex-direction: column-reverse;
.control-sep { .control-sep {