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 { UserButton } from '$components/user/UserButton';
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 { connect } from 'react-redux';
import type { UserType } from '$constants/types';
@ -21,6 +21,7 @@ type Props = {
userLogout: Function,
setDialog: Function,
setDialogActive: Function,
gotVkUser: Function,
};
@ -59,7 +60,14 @@ export class Component extends React.PureComponent<Props, void> {
setMenuOpened = () => this.setState({ menuOpened: !this.state.menuOpened });
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 = () => {
@ -94,7 +102,7 @@ export class Component extends React.PureComponent<Props, void> {
}
{
(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>
@ -125,6 +133,7 @@ const mapDispatchToProps = dispatch => bindActionCreators({
takeAShot,
setDialog,
gotVkUser,
setDialogActive,
}, dispatch);
export const UserPanel = connect(mapStateToProps, mapDispatchToProps)(Component);

View file

@ -1,20 +1,24 @@
import React from 'react';
import { CLIENT } from '$config/frontend';
import { APP_INFO } from '$constants/app_info';
type Props = {
userLogout: Function,
openAppInfoDialog: Function,
}
export const UserMenu = ({ userLogout }: Props) => (
export const UserMenu = ({ userLogout, openAppInfoDialog }: Props) => (
<div className="user-panel-menu">
<div className="user-panel-title">
ORCHID
<br />
MAP
<span className="user-panel-ver">
- { CLIENT.VER }
- {(APP_INFO.VERSION || 1)}.{(APP_INFO.RELEASE.length || 0)}
</span>
</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">
Проект на github
</a>

View file

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

View file

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

View file

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

View file

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

View file

@ -38,6 +38,11 @@
display: flex;
flex-direction: column;
a {
color: white;
opacity: 0.8;
}
}
.dialog-shader {
@ -125,3 +130,71 @@
font-size: 20px;
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) {
.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 {
z-index: 1;
@media (max-width: @mobile_breakpoint) {
z-index: 1;
flex-direction: column-reverse;
.control-sep {