2
This commit is contained in:
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
*.lock
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
package-lock.json
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Prod Bundle
|
||||||
|
/build
|
||||||
|
|
||||||
|
.vscode
|
||||||
|
.idea
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2019 Ivan Salvatore
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
93
README.md
Normal file
93
README.md
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
# VK Mini App Boilerplate
|
||||||
|
**Стартовый кит для создания сервиса на платформе VK Mini Apps.**
|
||||||
|
|
||||||
|
Чтобы понимать, что здесь происходит вы должны знать что такое VK Mini Apps, ReactJS и React Redux.
|
||||||
|
|
||||||
|
[Документация по VK Mini Apps](https://vk.com/dev/vk_apps_docs)
|
||||||
|
|
||||||
|
[Документация по ReactJS](https://ru.reactjs.org/docs/getting-started.html)
|
||||||
|
|
||||||
|
[Документация по React Redux](https://rajdee.gitbooks.io/redux-in-russian/content/)
|
||||||
|
|
||||||
|
## Установка:
|
||||||
|
`git clone git://github.com/iSa1vatore/vk-mini-app-boilerplate.git <folder name>`
|
||||||
|
|
||||||
|
Перейдите в созданную папку и выполните команды: `npm install` и `npm start`, последняя запустит сервер для разработки на `localhost:10888`
|
||||||
|
|
||||||
|
Отлично, теперь перед нами демонстративное приложение, можно править код и все изменения сразу будут видны на нашем сервере при помощи "Hot Reloading".
|
||||||
|
|
||||||
|
## KIT:
|
||||||
|
#### Что реализовано:
|
||||||
|
- Поддержка темы нативного клиента
|
||||||
|
- Поддержка iOS swipe back для панелей
|
||||||
|
- Обработка хардверной кнопки "назад" для Android
|
||||||
|
- Сохранение позиции скролла для панелей и элементов
|
||||||
|
- Scroll To Top при нажатии на иконку в Epic`e
|
||||||
|
- Получение токена пользователя
|
||||||
|
- Запросы к API ВКонтакте
|
||||||
|
- Сохранение данных в форме при смене панели
|
||||||
|
- Роутер
|
||||||
|
|
||||||
|
#### Роутер:
|
||||||
|
Действия которые роутер может выполнить:
|
||||||
|
- `setStory(story, initial_panel)` - Устанавливает активную Story у Epic'a, View и активную панель.
|
||||||
|
- `setPage(view, panel)` - Устанавливает активный View и Panel
|
||||||
|
- `goBack(from)` - Совершает действие назад, будь то закрытие модального окна, переход на прошлую панель, закрытие попапа и т.п;
|
||||||
|
- `openPopout(popout)` - Открывает поппап.
|
||||||
|
- `closePopout()` - Закрывает поппап.
|
||||||
|
- `openModal(id)` - Открывает модальную страницу по её ID.
|
||||||
|
- `closeModal()` - Закрывает модальную страницу или открывает прошлую страницу.
|
||||||
|
|
||||||
|
Примеры того как это все работает смотрите в исходниках ¯\_(ツ)_/¯, там все просто.
|
||||||
|
|
||||||
|
#### Сохранение позиции скролла:
|
||||||
|
Для сохранения позиции горизонтального скоролла нужно:
|
||||||
|
|
||||||
|
- Указать ID для элемента HorizontalScroll
|
||||||
|
```javascript
|
||||||
|
<HorizontalScroll id="EXAMPLE_TABS_LIST">
|
||||||
|
...
|
||||||
|
</HorizontalScroll>
|
||||||
|
```
|
||||||
|
- Сохранить позицию при демонтировании
|
||||||
|
```javascript
|
||||||
|
componentWillUnmount() {
|
||||||
|
const {setScrollPositionByID} = this.props;
|
||||||
|
|
||||||
|
setScrollPositionByID("EXAMPLE_TABS_LIST");
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Восстановить позицию при монтировании
|
||||||
|
```javascript
|
||||||
|
componentDidMount() {
|
||||||
|
restoreScrollPosition();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
Пример находится в файле: [`/src/js/panels/more/base.js`](https://github.com/iSa1vatore/vk-mini-app-boilerplate/blob/master/src/js/panels/more/base.js)
|
||||||
|
#### Важно:
|
||||||
|
В файле index.js на 24 стороке указывается стартовая панель приложения:
|
||||||
|
```javascript
|
||||||
|
store.dispatch(setStory('home', 'base'));
|
||||||
|
```
|
||||||
|
В данном случае это значит, что приложение запустится с:
|
||||||
|
|
||||||
|
`activeStory: home`
|
||||||
|
|
||||||
|
`activeView: home`
|
||||||
|
|
||||||
|
`activePanel: base`
|
||||||
|
|
||||||
|
Как вы поняли значение ID у Root и стартового View должны совпадать.
|
||||||
|
|
||||||
|
В проекте есть 2 файла: "App" и "AppWithoutEpic", первый идет с навигационной панелью Epic, второй без, он подойдет для простых приложений.
|
||||||
|
```javascript
|
||||||
|
import App from './App';
|
||||||
|
```
|
||||||
|
По умолчанию для примера выбран вариант с Epic навигацией.
|
||||||
|
|
||||||
|
В файле по пути [`/src/js/services/VK.js`](https://github.com/iSa1vatore/vk-mini-app-boilerplate/blob/master/src/js/services/VK.js) нужно заменить значение константы `APP_ID` на ID вашего приложения
|
||||||
|
|
||||||
|
|
||||||
|
Демо: [vk.com/app6984089](https://vk.com/app6984089)
|
||||||
|
|
||||||
|
Мой VK (вопросы, предложения): [Ivan Salvatore](https://vk.com/s9008)
|
||||||
45
package.json
Normal file
45
package.json
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
{
|
||||||
|
"name": "vkminiappboilerplate",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"homepage": "https://andrewkydev.github.io/2",
|
||||||
|
"scripts": {
|
||||||
|
|
||||||
|
"predeploy": "export NODE_OPTIONS=--openssl-legacy-provider && npm run build",
|
||||||
|
"deploy": "export NODE_OPTIONS=--openssl-legacy-provider && rm -rf node_modules/.cache/gh-pages && gh-pages -d build",
|
||||||
|
"start": "export NODE_OPTIONS=--openssl-legacy-provider && cross-env PORT=10888 HTTPS=true react-scripts start",
|
||||||
|
"build": "react-scripts build"
|
||||||
|
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT",
|
||||||
|
"devDependencies": {
|
||||||
|
"cross-env": "^5.2.1",
|
||||||
|
"react-hot-loader": "^4.12.19",
|
||||||
|
"react-scripts": "^2.1.8",
|
||||||
|
"redux-devtools-extension": "^2.13.8"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@vkontakte/icons": "^1.16.1",
|
||||||
|
"@vkontakte/vk-connect": "^1.8.7",
|
||||||
|
"@vkontakte/vkui": "^3.2.0",
|
||||||
|
"babel-eslint": "^9.0.0",
|
||||||
|
"chalk": "^2.4.2",
|
||||||
|
"core-js": "^2.6.11",
|
||||||
|
"gh-pages": "^5.0.0",
|
||||||
|
"prop-types": "^15.7.2",
|
||||||
|
"react": "^16.12.0",
|
||||||
|
"react-dom": "^16.12.0",
|
||||||
|
"react-redux": "^7.1.3",
|
||||||
|
"redux": "^4.0.5",
|
||||||
|
"redux-thunk": "^2.3.0"
|
||||||
|
},
|
||||||
|
"browserslist": [
|
||||||
|
">0.2%",
|
||||||
|
"not dead",
|
||||||
|
"not ie <= 11",
|
||||||
|
"not op_mini all"
|
||||||
|
]
|
||||||
|
}
|
||||||
13
public/index.html
Normal file
13
public/index.html
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ru">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no, viewport-fit=cover">
|
||||||
|
<meta name="theme-color" content="#000000">
|
||||||
|
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
|
||||||
|
<title>VK Mini App Boilerplate by iSa1vatore</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
154
src/App.js
Normal file
154
src/App.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {bindActionCreators} from 'redux'
|
||||||
|
import {goBack, closeModal, setStory} from "./js/store/router/actions";
|
||||||
|
import {getActivePanel} from "./js/services/_functions";
|
||||||
|
import * as VK from './js/services/VK';
|
||||||
|
|
||||||
|
import {Epic, View, Root, Tabbar, ModalRoot, TabbarItem, ConfigProvider} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
import Icon28Newsfeed from '@vkontakte/icons/dist/28/newsfeed';
|
||||||
|
import Icon28More from '@vkontakte/icons/dist/28/more';
|
||||||
|
|
||||||
|
import HomePanelBase from './js/panels/home/base';
|
||||||
|
import HomePanelGroups from './js/panels/home/groups';
|
||||||
|
|
||||||
|
import MorePanelBase from './js/panels/more/base';
|
||||||
|
import MorePanelExample from './js/panels/more/example';
|
||||||
|
|
||||||
|
import HomeBotsListModal from './js/components/modals/HomeBotsListModal';
|
||||||
|
import HomeBotInfoModal from './js/components/modals/HomeBotInfoModal';
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.lastAndroidBackAction = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const {goBack, dispatch} = this.props;
|
||||||
|
|
||||||
|
dispatch(VK.initApp());
|
||||||
|
|
||||||
|
window.onpopstate = () => {
|
||||||
|
let timeNow = +new Date();
|
||||||
|
|
||||||
|
if (timeNow - this.lastAndroidBackAction > 500) {
|
||||||
|
this.lastAndroidBackAction = timeNow;
|
||||||
|
|
||||||
|
goBack();
|
||||||
|
} else {
|
||||||
|
window.history.pushState(null, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
const {activeView, activeStory, activePanel, scrollPosition} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
prevProps.activeView !== activeView ||
|
||||||
|
prevProps.activePanel !== activePanel ||
|
||||||
|
prevProps.activeStory !== activeStory
|
||||||
|
) {
|
||||||
|
let pageScrollPosition = scrollPosition[activeStory + "_" + activeView + "_" + activePanel] || 0;
|
||||||
|
|
||||||
|
window.scroll(0, pageScrollPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {goBack, setStory, closeModal, popouts, activeView, activeStory, activeModals, panelsHistory, colorScheme} = this.props;
|
||||||
|
|
||||||
|
let history = (panelsHistory[activeView] === undefined) ? [activeView] : panelsHistory[activeView];
|
||||||
|
let popout = (popouts[activeView] === undefined) ? null : popouts[activeView];
|
||||||
|
let activeModal = (activeModals[activeView] === undefined) ? null : activeModals[activeView];
|
||||||
|
|
||||||
|
const homeModals = (
|
||||||
|
<ModalRoot activeModal={activeModal}>
|
||||||
|
<HomeBotsListModal
|
||||||
|
id="MODAL_PAGE_BOTS_LIST"
|
||||||
|
onClose={() => closeModal()}
|
||||||
|
/>
|
||||||
|
<HomeBotInfoModal
|
||||||
|
id="MODAL_PAGE_BOT_INFO"
|
||||||
|
onClose={() => closeModal()}
|
||||||
|
/>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider isWebView={true} scheme={colorScheme}>
|
||||||
|
<Epic activeStory={activeStory} tabbar={
|
||||||
|
<Tabbar>
|
||||||
|
<TabbarItem
|
||||||
|
onClick={() => setStory('home', 'base')}
|
||||||
|
selected={activeStory === 'home'}
|
||||||
|
><Icon28Newsfeed/></TabbarItem>
|
||||||
|
<TabbarItem
|
||||||
|
onClick={() => setStory('more', 'callmodal')}
|
||||||
|
selected={activeStory === 'more'}
|
||||||
|
><Icon28More/></TabbarItem>
|
||||||
|
</Tabbar>
|
||||||
|
}>
|
||||||
|
<Root id="home" activeView={activeView} popout={popout}>
|
||||||
|
<View
|
||||||
|
id="home"
|
||||||
|
modal={homeModals}
|
||||||
|
activePanel={getActivePanel("home")}
|
||||||
|
history={history}
|
||||||
|
onSwipeBack={() => goBack()}
|
||||||
|
>
|
||||||
|
<HomePanelBase id="base" withoutEpic={false}/>
|
||||||
|
<HomePanelGroups id="groups"/>
|
||||||
|
</View>
|
||||||
|
</Root>
|
||||||
|
<Root id="more" activeView={activeView} popout={popout}>
|
||||||
|
<View
|
||||||
|
id="more"
|
||||||
|
modal={homeModals}
|
||||||
|
activePanel={getActivePanel("more")}
|
||||||
|
history={history}
|
||||||
|
onSwipeBack={() => goBack()}
|
||||||
|
>
|
||||||
|
<MorePanelBase id="callmodal"/>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
id="modal"
|
||||||
|
modal={homeModals}
|
||||||
|
activePanel={getActivePanel("modal")}
|
||||||
|
history={history}
|
||||||
|
onSwipeBack={() => goBack()}
|
||||||
|
>
|
||||||
|
<MorePanelExample id="filters"/>
|
||||||
|
</View>
|
||||||
|
</Root>
|
||||||
|
</Epic>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
activeView: state.router.activeView,
|
||||||
|
activeStory: state.router.activeStory,
|
||||||
|
panelsHistory: state.router.panelsHistory,
|
||||||
|
activeModals: state.router.activeModals,
|
||||||
|
popouts: state.router.popouts,
|
||||||
|
scrollPosition: state.router.scrollPosition,
|
||||||
|
|
||||||
|
colorScheme: state.vkui.colorScheme
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
dispatch,
|
||||||
|
...bindActionCreators({setStory, goBack, closeModal}, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
126
src/AppWithoutEpic.js
Normal file
126
src/AppWithoutEpic.js
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {bindActionCreators} from 'redux'
|
||||||
|
import {goBack, closeModal} from "./js/store/router/actions";
|
||||||
|
import {getActivePanel} from "./js/services/_functions";
|
||||||
|
import * as VK from './js/services/VK';
|
||||||
|
|
||||||
|
import {View, Root, ModalRoot, ConfigProvider} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
import HomePanelProfile from './js/panels/home/base';
|
||||||
|
import HomePanelGroups from './js/panels/home/groups';
|
||||||
|
|
||||||
|
import HomeBotsListModal from './js/components/modals/HomeBotsListModal';
|
||||||
|
import HomeBotInfoModal from './js/components/modals/HomeBotInfoModal';
|
||||||
|
|
||||||
|
import MorePanelExample from './js/panels/more/example';
|
||||||
|
|
||||||
|
class App extends React.Component {
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.lastAndroidBackAction = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
const {goBack, dispatch} = this.props;
|
||||||
|
|
||||||
|
dispatch(VK.initApp());
|
||||||
|
|
||||||
|
window.onpopstate = () => {
|
||||||
|
let timeNow = +new Date();
|
||||||
|
|
||||||
|
if (timeNow - this.lastAndroidBackAction > 500) {
|
||||||
|
this.lastAndroidBackAction = timeNow;
|
||||||
|
|
||||||
|
goBack();
|
||||||
|
} else {
|
||||||
|
window.history.pushState(null, null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
const {activeView, activeStory, activePanel, scrollPosition} = this.props;
|
||||||
|
|
||||||
|
if (
|
||||||
|
prevProps.activeView !== activeView ||
|
||||||
|
prevProps.activePanel !== activePanel ||
|
||||||
|
prevProps.activeStory !== activeStory
|
||||||
|
) {
|
||||||
|
let pageScrollPosition = scrollPosition[activeStory + "_" + activeView + "_" + activePanel] || 0;
|
||||||
|
|
||||||
|
window.scroll(0, pageScrollPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {goBack, closeModal, popouts, activeView, activeModals, panelsHistory, colorScheme} = this.props;
|
||||||
|
|
||||||
|
let history = (panelsHistory[activeView] === undefined) ? [activeView] : panelsHistory[activeView];
|
||||||
|
let popout = (popouts[activeView] === undefined) ? null : popouts[activeView];
|
||||||
|
let activeModal = (activeModals[activeView] === undefined) ? null : activeModals[activeView];
|
||||||
|
|
||||||
|
const homeModals = (
|
||||||
|
<ModalRoot activeModal={activeModal}>
|
||||||
|
<HomeBotsListModal
|
||||||
|
id="MODAL_PAGE_BOTS_LIST"
|
||||||
|
onClose={() => closeModal()}
|
||||||
|
/>
|
||||||
|
<HomeBotInfoModal
|
||||||
|
id="MODAL_PAGE_BOT_INFO"
|
||||||
|
onClose={() => closeModal()}
|
||||||
|
/>
|
||||||
|
</ModalRoot>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigProvider isWebView={true} scheme={colorScheme}>
|
||||||
|
<Root activeView={activeView} popout={popout}>
|
||||||
|
<View
|
||||||
|
id="home"
|
||||||
|
modal={homeModals}
|
||||||
|
activePanel={getActivePanel("home")}
|
||||||
|
history={history}
|
||||||
|
onSwipeBack={() => goBack()}
|
||||||
|
>
|
||||||
|
<HomePanelProfile id="base" withoutEpic={true}/>
|
||||||
|
<HomePanelGroups id="groups"/>
|
||||||
|
</View>
|
||||||
|
<View
|
||||||
|
id="modal"
|
||||||
|
modal={homeModals}
|
||||||
|
activePanel={getActivePanel("modal")}
|
||||||
|
history={history}
|
||||||
|
onSwipeBack={() => goBack()}
|
||||||
|
>
|
||||||
|
<MorePanelExample id="filters"/>
|
||||||
|
</View>
|
||||||
|
</Root>
|
||||||
|
</ConfigProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
activeView: state.router.activeView,
|
||||||
|
activeStory: state.router.activeStory,
|
||||||
|
panelsHistory: state.router.panelsHistory,
|
||||||
|
activeModals: state.router.activeModals,
|
||||||
|
popouts: state.router.popouts,
|
||||||
|
scrollPosition: state.router.scrollPosition,
|
||||||
|
|
||||||
|
colorScheme: state.vkui.colorScheme
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
dispatch,
|
||||||
|
...bindActionCreators({goBack, closeModal}, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
||||||
23
src/css/main.css
Normal file
23
src/css/main.css
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
.div-center {
|
||||||
|
display: table;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.div-center img {
|
||||||
|
width: 10rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-group {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttons-group button:first-child {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a, div, img, p, button {
|
||||||
|
user-select: none;
|
||||||
|
-webkit-user-select: none;
|
||||||
|
-webkit-touch-callout: none;
|
||||||
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
}
|
||||||
31
src/index.js
Normal file
31
src/index.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import 'core-js/es6/map';
|
||||||
|
import 'core-js/es6/set';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
|
||||||
|
import {applyMiddleware, createStore} from "redux";
|
||||||
|
import thunk from 'redux-thunk';
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
|
import rootReducer from './js/store/reducers';
|
||||||
|
import {composeWithDevTools} from 'redux-devtools-extension';
|
||||||
|
|
||||||
|
import {setStory} from "./js/store/router/actions";
|
||||||
|
|
||||||
|
import '@vkontakte/vkui/dist/vkui.css';
|
||||||
|
import './css/main.css';
|
||||||
|
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
export const store = createStore(rootReducer, composeWithDevTools(
|
||||||
|
applyMiddleware(thunk),
|
||||||
|
));
|
||||||
|
|
||||||
|
store.dispatch(setStory('home', 'base'));
|
||||||
|
|
||||||
|
ReactDOM.render(
|
||||||
|
<Provider store={store}>
|
||||||
|
<App/>
|
||||||
|
</Provider>,
|
||||||
|
document.getElementById('root')
|
||||||
|
);
|
||||||
24
src/js/components/GroupCell.js
Normal file
24
src/js/components/GroupCell.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import React, {PureComponent} from 'react';
|
||||||
|
|
||||||
|
import {Cell, Avatar} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
class GroupCell extends PureComponent {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {description, photo, name} = this.props;
|
||||||
|
|
||||||
|
let desc = description.length > 0 ? description : "Описание отсутствует";
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Cell
|
||||||
|
description={desc}
|
||||||
|
before={<Avatar size={40} src={photo}/>}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Cell>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default GroupCell;
|
||||||
59
src/js/components/modals/HomeBotInfoModal.js
Normal file
59
src/js/components/modals/HomeBotInfoModal.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import {Cell, List, Avatar, InfoRow, ModalPage, ModalPageHeader, PanelHeaderButton, withPlatform, IOS} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
import Icon24Dismiss from '@vkontakte/icons/dist/24/dismiss';
|
||||||
|
import Icon24Cancel from '@vkontakte/icons/dist/24/cancel';
|
||||||
|
|
||||||
|
class HomeBotsListModal extends React.Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {id, onClose, platform} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalPage
|
||||||
|
id={id}
|
||||||
|
header={
|
||||||
|
<ModalPageHeader
|
||||||
|
left={platform !== IOS &&
|
||||||
|
<PanelHeaderButton onClick={onClose}><Icon24Cancel/></PanelHeaderButton>}
|
||||||
|
right={platform === IOS &&
|
||||||
|
<PanelHeaderButton onClick={onClose}><Icon24Dismiss/></PanelHeaderButton>}
|
||||||
|
>
|
||||||
|
Информация о боте
|
||||||
|
</ModalPageHeader>
|
||||||
|
}
|
||||||
|
onClose={onClose}
|
||||||
|
settlingHeight={80}
|
||||||
|
>
|
||||||
|
<Cell
|
||||||
|
description="Описание"
|
||||||
|
before={<Avatar size={40} src="https://vk.com/images/community_100.png?ava=1"/>}
|
||||||
|
>
|
||||||
|
Название
|
||||||
|
</Cell>
|
||||||
|
<List>
|
||||||
|
<Cell>
|
||||||
|
<InfoRow header="Подписчиков">
|
||||||
|
8800
|
||||||
|
</InfoRow>
|
||||||
|
</Cell>
|
||||||
|
<Cell>
|
||||||
|
<InfoRow header="Записей">
|
||||||
|
555
|
||||||
|
</InfoRow>
|
||||||
|
</Cell>
|
||||||
|
<Cell>
|
||||||
|
<InfoRow header="Рейтинг">
|
||||||
|
3535
|
||||||
|
</InfoRow>
|
||||||
|
</Cell>
|
||||||
|
</List>
|
||||||
|
</ModalPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withPlatform(connect()(HomeBotsListModal));
|
||||||
74
src/js/components/modals/HomeBotsListModal.js
Normal file
74
src/js/components/modals/HomeBotsListModal.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import {openModal} from "../../store/router/actions";
|
||||||
|
|
||||||
|
import {List, Cell, Avatar, ModalPage, ModalPageHeader, PanelHeaderButton, withPlatform, IOS} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
import Icon24Dismiss from '@vkontakte/icons/dist/24/dismiss';
|
||||||
|
import Icon24Cancel from '@vkontakte/icons/dist/24/cancel';
|
||||||
|
import Icon24Chevron from '@vkontakte/icons/dist/24/chevron';
|
||||||
|
|
||||||
|
const bots = [
|
||||||
|
{
|
||||||
|
name: 'VKS',
|
||||||
|
avatar: 'https://pp.userapi.com/c851520/v851520442/48ce/Sik7V4c58qw.jpg',
|
||||||
|
desc: 'Нет, меня не роняли в детстве'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Недобот',
|
||||||
|
avatar: 'https://pp.userapi.com/c854420/v854420431/da51/X8ohw4-4Fk4.jpg',
|
||||||
|
desc: 'Я ни разу не пил кокосовое молоко'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Realm of War',
|
||||||
|
avatar: 'https://sun9-72.userapi.com/c853520/v853520591/1f167d/avj_z9yFtQA.jpg?ava=1',
|
||||||
|
desc: 'Ненавижу спойлеры.'
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
class HomeBotsListModal extends React.Component {
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {id, onClose, openModal, platform} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ModalPage
|
||||||
|
id={id}
|
||||||
|
header={
|
||||||
|
<ModalPageHeader
|
||||||
|
left={platform !== IOS &&
|
||||||
|
<PanelHeaderButton onClick={onClose}><Icon24Cancel/></PanelHeaderButton>}
|
||||||
|
right={platform === IOS &&
|
||||||
|
<PanelHeaderButton onClick={onClose}><Icon24Dismiss/></PanelHeaderButton>}
|
||||||
|
>
|
||||||
|
/appbots на минималках
|
||||||
|
</ModalPageHeader>
|
||||||
|
}
|
||||||
|
onClose={onClose}
|
||||||
|
settlingHeight={80}
|
||||||
|
>
|
||||||
|
<List>
|
||||||
|
{bots.map((bot, index) => (
|
||||||
|
<Cell
|
||||||
|
key={index}
|
||||||
|
description={bot.desc}
|
||||||
|
before={<Avatar size={40} src={bot.avatar}/>}
|
||||||
|
onClick={() => openModal('MODAL_PAGE_BOT_INFO')}
|
||||||
|
asideContent={<Icon24Chevron fill="#528bcc"/>}
|
||||||
|
>
|
||||||
|
{bot.name}
|
||||||
|
</Cell>
|
||||||
|
))}
|
||||||
|
</List>
|
||||||
|
</ModalPage>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
openModal
|
||||||
|
};
|
||||||
|
|
||||||
|
export default withPlatform(connect(null, mapDispatchToProps)(HomeBotsListModal));
|
||||||
87
src/js/panels/home/base.js
Normal file
87
src/js/panels/home/base.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { connect } from 'react-redux';
|
||||||
|
|
||||||
|
import { closePopout, goBack, openModal, openPopout, setPage } from '../../store/router/actions';
|
||||||
|
|
||||||
|
import { Div, Panel, Alert, Group, Button, PanelHeader } from "@vkontakte/vkui"
|
||||||
|
|
||||||
|
class HomePanelBase extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
showImg: false
|
||||||
|
};
|
||||||
|
|
||||||
|
showImg = () => {
|
||||||
|
this.setState({ showImg: true });
|
||||||
|
};
|
||||||
|
|
||||||
|
openPopout() {
|
||||||
|
this.props.openPopout(
|
||||||
|
<Alert
|
||||||
|
actions={[{
|
||||||
|
title: 'Нет',
|
||||||
|
autoclose: true,
|
||||||
|
style: 'cancel',
|
||||||
|
}, {
|
||||||
|
title: 'Да',
|
||||||
|
autoclose: true,
|
||||||
|
action: this.showImg
|
||||||
|
}]}
|
||||||
|
onClose={() => this.props.closePopout()}
|
||||||
|
>
|
||||||
|
<h2>Вопрос значит</h2>
|
||||||
|
<p>Вас роняли в детстве?</p>
|
||||||
|
</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const { id, setPage, withoutEpic } = this.props;
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
const payload = async () => {
|
||||||
|
if (url.searchParams.get('payload') != null) return await url.searchParams.get('payload');
|
||||||
|
else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel id={id}>
|
||||||
|
<PanelHeader>Examples</PanelHeader>
|
||||||
|
<Group>
|
||||||
|
{/* <Div>
|
||||||
|
<Button mode="secondary" size="l" stretched={true} onClick={() => setPage('home', 'groups')}>Список моих
|
||||||
|
групп</Button>
|
||||||
|
</Div> */}
|
||||||
|
<Div>
|
||||||
|
<p>
|
||||||
|
{new URL(window.location.href).searchParams.get('payload') ? ('ваш ник: ' + new URL(window.location.href).searchParams.get('payload')) : 'ВЫ НЕ АВТОРИЗОВАНЫ'}
|
||||||
|
</p>
|
||||||
|
</Div>
|
||||||
|
{/* <Div>
|
||||||
|
<p>{params.get('payload')}</p>
|
||||||
|
</Div> */}
|
||||||
|
<Div>
|
||||||
|
<Button mode="secondary" size="l" stretched={true} onClick={() => this.openPopout()}>Открыть алерт</Button>
|
||||||
|
</Div>
|
||||||
|
<Div>
|
||||||
|
<Button mode="secondary" size="l" stretched={true} onClick={() => this.props.openModal("MODAL_PAGE_BOTS_LIST")}>Открыть
|
||||||
|
модальную страницу</Button>
|
||||||
|
</Div>
|
||||||
|
|
||||||
|
</Group>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setPage,
|
||||||
|
goBack,
|
||||||
|
openPopout,
|
||||||
|
closePopout,
|
||||||
|
openModal
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(null, mapDispatchToProps)(HomePanelBase);
|
||||||
145
src/js/panels/home/groups.js
Normal file
145
src/js/panels/home/groups.js
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
import {bindActionCreators} from "redux";
|
||||||
|
|
||||||
|
import {goBack, openPopout, closePopout, openModal} from "../../store/router/actions";
|
||||||
|
import * as VK from '../../services/VK';
|
||||||
|
|
||||||
|
import {renderGroupsList} from '../../services/renderers';
|
||||||
|
|
||||||
|
import {Div, List, Panel, Group, Button, PanelHeader, PanelSpinner, PanelHeaderBack, Header} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
class HomePanelGroups extends React.Component {
|
||||||
|
|
||||||
|
state = {
|
||||||
|
groups: {
|
||||||
|
admined: [],
|
||||||
|
other: [],
|
||||||
|
},
|
||||||
|
loading: true,
|
||||||
|
errorGetAuthToken: false
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
if (this.props.accessToken === undefined) {
|
||||||
|
this.getAuthToken();
|
||||||
|
} else {
|
||||||
|
this.getGroupsList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAuthToken() {
|
||||||
|
this.props.dispatch(VK.getAuthToken(['groups']));
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState, snapshot) {
|
||||||
|
if (this.props !== prevProps) {
|
||||||
|
if (this.props.accessToken === null) {
|
||||||
|
this.setState({
|
||||||
|
loading: false,
|
||||||
|
errorGetAuthToken: true
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.setState({
|
||||||
|
loading: true,
|
||||||
|
errorGetAuthToken: false
|
||||||
|
});
|
||||||
|
|
||||||
|
this.getGroupsList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroupsList() {
|
||||||
|
if (localStorage.getItem('userGroupsAdmined') && localStorage.getItem('userGroupsOther')) {
|
||||||
|
this.setState({
|
||||||
|
groups: {
|
||||||
|
admined: JSON.parse(localStorage.getItem('userGroupsAdmined')),
|
||||||
|
other: JSON.parse(localStorage.getItem('userGroupsOther')),
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let groups = await VK.groupsGet();
|
||||||
|
console.log(groups);
|
||||||
|
|
||||||
|
let adminedGroups = groups.items.filter(function (item) {
|
||||||
|
return item.is_admin === 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
let otherGroups = groups.items.filter(function (item) {
|
||||||
|
return item.is_admin === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
localStorage.setItem('userGroupsAdmined', JSON.stringify(adminedGroups));
|
||||||
|
localStorage.setItem('userGroupsOther', JSON.stringify(otherGroups));
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
groups: {
|
||||||
|
admined: adminedGroups,
|
||||||
|
other: otherGroups
|
||||||
|
},
|
||||||
|
loading: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {id, goBack} = this.props;
|
||||||
|
|
||||||
|
let adminedGroupsList = renderGroupsList(this.state.groups.admined);
|
||||||
|
let otherGroupsList = renderGroupsList(this.state.groups.other);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel id={id}>
|
||||||
|
<PanelHeader
|
||||||
|
left={<PanelHeaderBack onClick={() => goBack()}/>}
|
||||||
|
>
|
||||||
|
Группы
|
||||||
|
</PanelHeader>
|
||||||
|
{this.state.loading && <PanelSpinner/>}
|
||||||
|
{!this.state.loading && this.state.errorGetAuthToken && <Group>
|
||||||
|
<Div>Возникла ошибка при получении данных, возможно Вы не выдали права на список групп.</Div>
|
||||||
|
<Div>
|
||||||
|
<Button size="l" stretched={true} onClick={() => this.getAuthToken()}>Запросить
|
||||||
|
повторно</Button>
|
||||||
|
</Div>
|
||||||
|
</Group>}
|
||||||
|
{!this.state.loading && !this.state.errorGetAuthToken && adminedGroupsList &&
|
||||||
|
<Group header={
|
||||||
|
<Header mode="secondary">Администрируемые</Header>
|
||||||
|
}>
|
||||||
|
<List>
|
||||||
|
{adminedGroupsList}
|
||||||
|
</List>
|
||||||
|
</Group>}
|
||||||
|
{!this.state.loading && !this.state.errorGetAuthToken && otherGroupsList &&
|
||||||
|
<Group header={
|
||||||
|
<Header mode="secondary">Остальные</Header>
|
||||||
|
}>
|
||||||
|
<List>
|
||||||
|
{otherGroupsList}
|
||||||
|
</List>
|
||||||
|
</Group>}
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapDispatchToProps(dispatch) {
|
||||||
|
return {
|
||||||
|
dispatch,
|
||||||
|
...bindActionCreators({goBack, openPopout, closePopout, openModal}, dispatch)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
accessToken: state.vkui.accessToken
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(HomePanelGroups);
|
||||||
109
src/js/panels/more/base.js
Normal file
109
src/js/panels/more/base.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import {setPage} from "../../store/router/actions";
|
||||||
|
import {setActiveTab, setScrollPositionByID} from "../../store/vk/actions";
|
||||||
|
import {restoreScrollPosition} from "../../services/_functions";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Div,
|
||||||
|
Panel,
|
||||||
|
Group,
|
||||||
|
CellButton,
|
||||||
|
PanelHeader,
|
||||||
|
FixedLayout,
|
||||||
|
HorizontalScroll,
|
||||||
|
TabsItem,
|
||||||
|
Tabs
|
||||||
|
} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
class HomePanelProfile extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
activeTab: props.activeTab["EXAMPLE"] || "modal"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
setTab(tab) {
|
||||||
|
this.setState({
|
||||||
|
activeTab: tab
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const {setScrollPositionByID, setActiveTab} = this.props;
|
||||||
|
|
||||||
|
setActiveTab("EXAMPLE", this.state.activeTab);
|
||||||
|
setScrollPositionByID("EXAMPLE_TABS_LIST");
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
restoreScrollPosition();
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {id, setPage} = this.props;
|
||||||
|
const boxStyle = {marginTop: 56};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel id={id}>
|
||||||
|
<PanelHeader noShadow={true}>Examples 2</PanelHeader>
|
||||||
|
<FixedLayout vertical="top">
|
||||||
|
<Tabs theme="header" mode="default">
|
||||||
|
<HorizontalScroll id="EXAMPLE_TABS_LIST">
|
||||||
|
<TabsItem
|
||||||
|
onClick={() => this.setTab('modal')}
|
||||||
|
selected={this.state.activeTab === 'modal'}
|
||||||
|
>
|
||||||
|
Модальное окно
|
||||||
|
</TabsItem>
|
||||||
|
<TabsItem
|
||||||
|
onClick={() => this.setTab('test')}
|
||||||
|
selected={this.state.activeTab === 'test'}
|
||||||
|
>
|
||||||
|
Для теста
|
||||||
|
</TabsItem>
|
||||||
|
<TabsItem
|
||||||
|
onClick={() => this.setTab('test2')}
|
||||||
|
selected={this.state.activeTab === 'test2'}
|
||||||
|
>
|
||||||
|
Для теста 2
|
||||||
|
</TabsItem>
|
||||||
|
<TabsItem
|
||||||
|
onClick={() => this.setTab('test3')}
|
||||||
|
selected={this.state.activeTab === 'test3'}
|
||||||
|
>
|
||||||
|
Для теста 3
|
||||||
|
</TabsItem>
|
||||||
|
</HorizontalScroll>
|
||||||
|
</Tabs>
|
||||||
|
</FixedLayout>
|
||||||
|
<Group style={boxStyle}>
|
||||||
|
{this.state.activeTab === 'modal' && <CellButton onClick={() => setPage('modal', 'filters')}>
|
||||||
|
Открыть модальное окно
|
||||||
|
</CellButton>}
|
||||||
|
|
||||||
|
{this.state.activeTab !== 'modal' && <Div>{this.state.activeTab}</Div>}
|
||||||
|
</Group>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
activeTab: state.vkui.activeTab,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setPage,
|
||||||
|
setActiveTab,
|
||||||
|
setScrollPositionByID
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(HomePanelProfile);
|
||||||
120
src/js/panels/more/example.js
Normal file
120
src/js/panels/more/example.js
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import {connect} from 'react-redux';
|
||||||
|
|
||||||
|
import {goBack} from '../../store/router/actions';
|
||||||
|
import {setFormData} from "../../store/formData/actions";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Div,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Input,
|
||||||
|
FormLayout,
|
||||||
|
FormLayoutGroup,
|
||||||
|
Panel,
|
||||||
|
Group,
|
||||||
|
PanelHeader,
|
||||||
|
PanelHeaderBack
|
||||||
|
} from "@vkontakte/vkui";
|
||||||
|
|
||||||
|
class HomePanelProfile extends React.Component {
|
||||||
|
|
||||||
|
constructor(props) {
|
||||||
|
super(props);
|
||||||
|
|
||||||
|
let defaultInputData = {
|
||||||
|
workplace: '',
|
||||||
|
workposition: '',
|
||||||
|
|
||||||
|
checkboxPhoto: 0,
|
||||||
|
checkboxOnline: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
inputData: props.inputData['example_form'] || defaultInputData
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleInput = (e) => {
|
||||||
|
let value = e.currentTarget.value;
|
||||||
|
|
||||||
|
if (e.currentTarget.type === 'checkbox') {
|
||||||
|
value = e.currentTarget.checked;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({
|
||||||
|
inputData: {
|
||||||
|
...this.state.inputData,
|
||||||
|
[e.currentTarget.name]: value
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
this.clearForm = () => {
|
||||||
|
this.setState({
|
||||||
|
inputData: defaultInputData
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
this.props.setFormData('example_form', this.state.inputData);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {id, goBack} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel id={id}>
|
||||||
|
<PanelHeader
|
||||||
|
left={<PanelHeaderBack onClick={() => goBack()}/>}
|
||||||
|
>
|
||||||
|
Модальное окно
|
||||||
|
</PanelHeader>
|
||||||
|
<Group>
|
||||||
|
<FormLayout>
|
||||||
|
<FormLayoutGroup top="Работа">
|
||||||
|
<Input value={this.state.inputData.workplace}
|
||||||
|
onChange={this.handleInput}
|
||||||
|
name="workplace"
|
||||||
|
placeholder="Место работы"
|
||||||
|
autoComplete="off"/>
|
||||||
|
<Input value={this.state.inputData.workposition}
|
||||||
|
onChange={this.handleInput}
|
||||||
|
name="workposition"
|
||||||
|
placeholder="Должность"
|
||||||
|
autoComplete="off"/>
|
||||||
|
</FormLayoutGroup>
|
||||||
|
<FormLayoutGroup top="Дополнительно">
|
||||||
|
<Checkbox checked={this.state.inputData.checkboxPhoto}
|
||||||
|
onChange={this.handleInput}
|
||||||
|
name="checkboxPhoto"
|
||||||
|
>С фотографией</Checkbox>
|
||||||
|
<Checkbox checked={this.state.inputData.checkboxOnline}
|
||||||
|
onChange={this.handleInput}
|
||||||
|
name="checkboxOnline"
|
||||||
|
>Сейчас на сайте</Checkbox>
|
||||||
|
</FormLayoutGroup>
|
||||||
|
</FormLayout>
|
||||||
|
<Div className="buttons-group">
|
||||||
|
<Button size="l" stretched={true} onClick={() => goBack()}>Сохранить</Button>
|
||||||
|
<Button size="l" stretched={true} onClick={this.clearForm}>Очистить</Button>
|
||||||
|
</Div>
|
||||||
|
</Group>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapStateToProps = (state) => {
|
||||||
|
return {
|
||||||
|
inputData: state.formData.forms,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapDispatchToProps = {
|
||||||
|
setFormData,
|
||||||
|
goBack
|
||||||
|
};
|
||||||
|
|
||||||
|
export default connect(mapStateToProps, mapDispatchToProps)(HomePanelProfile);
|
||||||
84
src/js/services/VK.js
Normal file
84
src/js/services/VK.js
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
import VKConnect from "@vkontakte/vk-connect";
|
||||||
|
|
||||||
|
import {store} from "../../index";
|
||||||
|
|
||||||
|
import {setColorScheme, setAccessToken} from "../store/vk/actions";
|
||||||
|
|
||||||
|
const APP_ID = 6984089;
|
||||||
|
const API_VERSION = '5.92';
|
||||||
|
|
||||||
|
export const initApp = () => (dispatch) => {
|
||||||
|
const VKConnectCallback = (e) => {
|
||||||
|
if (e.detail.type === 'VKWebAppUpdateConfig') {
|
||||||
|
VKConnect.unsubscribe(VKConnectCallback);
|
||||||
|
|
||||||
|
dispatch(setColorScheme(e.detail.data.scheme));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
VKConnect.subscribe(VKConnectCallback);
|
||||||
|
return VKConnect.send('VKWebAppInit', {}).then(data => {
|
||||||
|
return data;
|
||||||
|
}).catch(error => {
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAuthToken = (scope) => (dispatch) => {
|
||||||
|
VKConnect.send("VKWebAppGetAuthToken", {
|
||||||
|
"app_id": APP_ID,
|
||||||
|
"scope": scope.join(',')
|
||||||
|
}).then(data => {
|
||||||
|
dispatch(setAccessToken(data.access_token));
|
||||||
|
}).catch(() => {
|
||||||
|
dispatch(setAccessToken(null));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const closeApp = () => {
|
||||||
|
return VKConnect.send("VKWebAppClose", {
|
||||||
|
"status": "success"
|
||||||
|
}).then(data => {
|
||||||
|
return data;
|
||||||
|
}).catch(error => {
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const swipeBackOn = () => {
|
||||||
|
return VKConnect.send("VKWebAppEnableSwipeBack", {}).then(data => {
|
||||||
|
return data;
|
||||||
|
}).catch(error => {
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const swipeBackOff = () => {
|
||||||
|
return VKConnect.send("VKWebAppDisableSwipeBack", {}).then(data => {
|
||||||
|
return data;
|
||||||
|
}).catch(error => {
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const groupsGet = () => {
|
||||||
|
return APICall('groups.get', {
|
||||||
|
"extended": "1",
|
||||||
|
"fields": "description",
|
||||||
|
"count": "100"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const APICall = (method, params) => {
|
||||||
|
params['access_token'] = store.getState().vkui.accessToken;
|
||||||
|
params['v'] = params['v'] === undefined ? API_VERSION : params['v'];
|
||||||
|
|
||||||
|
return VKConnect.send("VKWebAppCallAPIMethod", {
|
||||||
|
"method": method,
|
||||||
|
"params": params
|
||||||
|
}).then(data => {
|
||||||
|
return data.response;
|
||||||
|
}).catch(error => {
|
||||||
|
return error;
|
||||||
|
});
|
||||||
|
};
|
||||||
42
src/js/services/_functions.js
Normal file
42
src/js/services/_functions.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import {store} from "../../index";
|
||||||
|
|
||||||
|
export const smoothScrollToTop = () => {
|
||||||
|
const c = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
|
|
||||||
|
if (c > 30) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c > 0) {
|
||||||
|
window.requestAnimationFrame(smoothScrollToTop);
|
||||||
|
window.scrollTo(0, c - c / 8);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const restoreScrollPosition = () => {
|
||||||
|
let scrolls = store.getState().vkui.componentScroll;
|
||||||
|
|
||||||
|
Object.keys(scrolls).forEach((component) => {
|
||||||
|
let componentData = scrolls[component];
|
||||||
|
|
||||||
|
let element = document.getElementById(component);
|
||||||
|
|
||||||
|
if (element) {
|
||||||
|
element = element.getElementsByClassName("HorizontalScroll__in")[0];
|
||||||
|
|
||||||
|
element.scrollLeft = componentData.x;
|
||||||
|
element.scrollTop = componentData.y;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getActivePanel = (view) => {
|
||||||
|
let panel = store.getState().router.activePanel;
|
||||||
|
|
||||||
|
let panelsHistory = store.getState().router.panelsHistory;
|
||||||
|
if (typeof panelsHistory[view] !== "undefined") {
|
||||||
|
panel = panelsHistory[view][panelsHistory[view].length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return panel;
|
||||||
|
};
|
||||||
16
src/js/services/renderers.js
Normal file
16
src/js/services/renderers.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import GroupCell from '../components/GroupCell';
|
||||||
|
|
||||||
|
export const renderGroupsList = (items) => {
|
||||||
|
let groups = null;
|
||||||
|
if (items !== undefined && items !== null && items.length !== 0) {
|
||||||
|
groups = items.map((group) => (
|
||||||
|
<GroupCell
|
||||||
|
key={group.id}
|
||||||
|
description={group.description}
|
||||||
|
photo={group.photo_100}
|
||||||
|
name={group.name}/>
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return groups;
|
||||||
|
};
|
||||||
1
src/js/store/formData/actionTypes.js
Normal file
1
src/js/store/formData/actionTypes.js
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const SET_FORM_DATA = 'SAVE_FORM_DATA';
|
||||||
11
src/js/store/formData/actions.js
Normal file
11
src/js/store/formData/actions.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import {SET_FORM_DATA} from './actionTypes';
|
||||||
|
|
||||||
|
export const setFormData = (formName, inputData) => (
|
||||||
|
{
|
||||||
|
type: SET_FORM_DATA,
|
||||||
|
payload: {
|
||||||
|
form: formName,
|
||||||
|
data: inputData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
27
src/js/store/formData/reducers.js
Normal file
27
src/js/store/formData/reducers.js
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
import {SET_FORM_DATA} from './actionTypes';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
forms: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export const formDataReducer = (state = initialState, action) => {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case SET_FORM_DATA: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
forms: {
|
||||||
|
...state.forms,
|
||||||
|
[action.payload.form]: action.payload.data
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
10
src/js/store/reducers.js
Normal file
10
src/js/store/reducers.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import {combineReducers} from "redux";
|
||||||
|
import {routerReducer} from './router/reducers';
|
||||||
|
import {vkuiReducer} from './vk/reducers';
|
||||||
|
import {formDataReducer} from "./formData/reducers";
|
||||||
|
|
||||||
|
export default combineReducers({
|
||||||
|
vkui: vkuiReducer,
|
||||||
|
router: routerReducer,
|
||||||
|
formData: formDataReducer
|
||||||
|
});
|
||||||
7
src/js/store/router/actionTypes.js
Normal file
7
src/js/store/router/actionTypes.js
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const SET_PAGE = 'SET_PAGE';
|
||||||
|
export const SET_STORY = 'SET_STORY';
|
||||||
|
export const GO_BACK = 'GO_BACK';
|
||||||
|
export const OPEN_POPOUT = 'OPEN_POPOUT';
|
||||||
|
export const CLOSE_POPOUT = 'CLOSE_POPOUT';
|
||||||
|
export const OPEN_MODAL = 'OPEN_MODAL';
|
||||||
|
export const CLOSE_MODAL = 'CLOSE_MODAL';
|
||||||
57
src/js/store/router/actions.js
Normal file
57
src/js/store/router/actions.js
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import {SET_PAGE, SET_STORY, GO_BACK, OPEN_POPOUT, CLOSE_POPOUT, OPEN_MODAL, CLOSE_MODAL} from './actionTypes';
|
||||||
|
|
||||||
|
export const setStory = (story, initial_panel) => (
|
||||||
|
{
|
||||||
|
type: SET_STORY,
|
||||||
|
payload: {
|
||||||
|
story: story,
|
||||||
|
initial_panel: initial_panel,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setPage = (view, panel) => (
|
||||||
|
{
|
||||||
|
type: SET_PAGE,
|
||||||
|
payload: {
|
||||||
|
view: view,
|
||||||
|
panel: panel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const goBack = () => (
|
||||||
|
{
|
||||||
|
type: GO_BACK
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const openPopout = (popout) => (
|
||||||
|
{
|
||||||
|
type: OPEN_POPOUT,
|
||||||
|
payload: {
|
||||||
|
popout: popout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const closePopout = () => (
|
||||||
|
{
|
||||||
|
type: CLOSE_POPOUT
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const openModal = (id) => (
|
||||||
|
{
|
||||||
|
type: OPEN_MODAL,
|
||||||
|
payload: {
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const closeModal = () => (
|
||||||
|
{
|
||||||
|
type: CLOSE_MODAL
|
||||||
|
}
|
||||||
|
);
|
||||||
313
src/js/store/router/reducers.js
Normal file
313
src/js/store/router/reducers.js
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
import {
|
||||||
|
SET_PAGE,
|
||||||
|
GO_BACK,
|
||||||
|
OPEN_POPOUT,
|
||||||
|
CLOSE_POPOUT,
|
||||||
|
OPEN_MODAL,
|
||||||
|
CLOSE_MODAL,
|
||||||
|
SET_STORY
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
import * as VK from "../../services/VK";
|
||||||
|
import {smoothScrollToTop} from "../../services/_functions";
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
activeStory: null,
|
||||||
|
activeView: null,
|
||||||
|
activePanel: null,
|
||||||
|
|
||||||
|
storiesHistory: [],
|
||||||
|
viewsHistory: [],
|
||||||
|
panelsHistory: [],
|
||||||
|
|
||||||
|
activeModals: [],
|
||||||
|
modalHistory: [],
|
||||||
|
popouts: [],
|
||||||
|
|
||||||
|
scrollPosition: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export const routerReducer = (state = initialState, action) => {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case SET_PAGE: {
|
||||||
|
let View = action.payload.view;
|
||||||
|
let Panel = action.payload.panel;
|
||||||
|
|
||||||
|
window.history.pushState(null, null);
|
||||||
|
|
||||||
|
let panelsHistory = state.panelsHistory[View] || [];
|
||||||
|
let viewsHistory = state.viewsHistory[state.activeStory] || [];
|
||||||
|
|
||||||
|
const viewIndexInHistory = viewsHistory.indexOf(View);
|
||||||
|
|
||||||
|
if (viewIndexInHistory !== -1) {
|
||||||
|
viewsHistory.splice(viewIndexInHistory, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelsHistory.indexOf(Panel) === -1) {
|
||||||
|
panelsHistory = [...panelsHistory, Panel];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelsHistory.length > 1) {
|
||||||
|
VK.swipeBackOn();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeView: View,
|
||||||
|
activePanel: Panel,
|
||||||
|
|
||||||
|
panelsHistory: {
|
||||||
|
...state.panelsHistory,
|
||||||
|
[View]: panelsHistory,
|
||||||
|
},
|
||||||
|
viewsHistory: {
|
||||||
|
...state.viewsHistory,
|
||||||
|
[state.activeStory]: [...viewsHistory, View]
|
||||||
|
},
|
||||||
|
scrollPosition: {
|
||||||
|
...state.scrollPosition,
|
||||||
|
[state.activeStory + "_" + state.activeView + "_" + state.activePanel]: window.pageYOffset
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_STORY: {
|
||||||
|
window.history.pushState(null, null);
|
||||||
|
|
||||||
|
let viewsHistory = state.viewsHistory[action.payload.story] || [action.payload.story];
|
||||||
|
|
||||||
|
let storiesHistory = state.storiesHistory;
|
||||||
|
let activeView = viewsHistory[viewsHistory.length - 1];
|
||||||
|
let panelsHistory = state.panelsHistory[activeView] || [action.payload.initial_panel];
|
||||||
|
let activePanel = panelsHistory[panelsHistory.length - 1];
|
||||||
|
|
||||||
|
if (action.payload.story === state.activeStory) {
|
||||||
|
if (panelsHistory.length > 1) {
|
||||||
|
let firstPanel = panelsHistory.shift();
|
||||||
|
panelsHistory = [firstPanel];
|
||||||
|
|
||||||
|
activePanel = panelsHistory[panelsHistory.length - 1];
|
||||||
|
} else if (viewsHistory.length > 1) {
|
||||||
|
let firstView = viewsHistory.shift();
|
||||||
|
viewsHistory = [firstView];
|
||||||
|
|
||||||
|
activeView = viewsHistory[viewsHistory.length - 1];
|
||||||
|
panelsHistory = state.panelsHistory[activeView];
|
||||||
|
activePanel = panelsHistory[panelsHistory.length - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (action.payload.story === state.activeStory && panelsHistory.length === 1 && window.pageYOffset > 0) {
|
||||||
|
window.scrollTo(0, 30);
|
||||||
|
|
||||||
|
smoothScrollToTop();
|
||||||
|
}
|
||||||
|
|
||||||
|
const storiesIndexInHistory = storiesHistory.indexOf(action.payload.story);
|
||||||
|
|
||||||
|
if (storiesIndexInHistory === -1 || (storiesHistory[0] === action.payload.story && storiesHistory[storiesHistory.length - 1] !== action.payload.story)) {
|
||||||
|
storiesHistory = [...storiesHistory, action.payload.story];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeStory: action.payload.story,
|
||||||
|
activeView: activeView,
|
||||||
|
activePanel: activePanel,
|
||||||
|
|
||||||
|
storiesHistory: storiesHistory,
|
||||||
|
viewsHistory: {
|
||||||
|
...state.viewsHistory,
|
||||||
|
[activeView]: viewsHistory
|
||||||
|
},
|
||||||
|
panelsHistory: {
|
||||||
|
...state.panelsHistory,
|
||||||
|
[activeView]: panelsHistory
|
||||||
|
},
|
||||||
|
|
||||||
|
scrollPosition: {
|
||||||
|
...state.scrollPosition,
|
||||||
|
[state.activeStory + "_" + state.activeView + "_" + state.activePanel]: window.pageYOffset
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case GO_BACK: {
|
||||||
|
let setView = state.activeView;
|
||||||
|
let setPanel = state.activePanel;
|
||||||
|
let setStory = state.activeStory;
|
||||||
|
|
||||||
|
let popoutsData = state.popouts;
|
||||||
|
|
||||||
|
if (popoutsData[setView]) {
|
||||||
|
popoutsData[setView] = null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
popouts: {
|
||||||
|
...state.popouts, popoutsData
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let viewModalsHistory = state.modalHistory[setView];
|
||||||
|
|
||||||
|
if (viewModalsHistory !== undefined && viewModalsHistory.length !== 0) {
|
||||||
|
let activeModal = viewModalsHistory[viewModalsHistory.length - 2] || null;
|
||||||
|
|
||||||
|
if (activeModal === null) {
|
||||||
|
viewModalsHistory = [];
|
||||||
|
} else if (viewModalsHistory.indexOf(activeModal) !== -1) {
|
||||||
|
viewModalsHistory = viewModalsHistory.splice(0, viewModalsHistory.indexOf(activeModal) + 1);
|
||||||
|
} else {
|
||||||
|
viewModalsHistory.push(activeModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeModals: {
|
||||||
|
...state.activeModals,
|
||||||
|
[setView]: activeModal
|
||||||
|
},
|
||||||
|
modalHistory: {
|
||||||
|
...state.modalHistory,
|
||||||
|
[setView]: viewModalsHistory
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let panelsHistory = state.panelsHistory[setView] || [];
|
||||||
|
let viewsHistory = state.viewsHistory[state.activeStory] || [];
|
||||||
|
let storiesHistory = state.storiesHistory;
|
||||||
|
|
||||||
|
if (panelsHistory.length > 1) {
|
||||||
|
panelsHistory.pop();
|
||||||
|
|
||||||
|
setPanel = panelsHistory[panelsHistory.length - 1];
|
||||||
|
} else if (viewsHistory.length > 1) {
|
||||||
|
viewsHistory.pop();
|
||||||
|
|
||||||
|
setView = viewsHistory[viewsHistory.length - 1];
|
||||||
|
let panelsHistoryNew = state.panelsHistory[setView];
|
||||||
|
|
||||||
|
setPanel = panelsHistoryNew[panelsHistoryNew.length - 1];
|
||||||
|
} else if (storiesHistory.length > 1) {
|
||||||
|
storiesHistory.pop();
|
||||||
|
|
||||||
|
setStory = storiesHistory[storiesHistory.length - 1];
|
||||||
|
setView = state.viewsHistory[setStory][state.viewsHistory[setStory].length - 1];
|
||||||
|
|
||||||
|
let panelsHistoryNew = state.panelsHistory[setView];
|
||||||
|
|
||||||
|
if (panelsHistoryNew.length > 1) {
|
||||||
|
setPanel = panelsHistoryNew[panelsHistoryNew.length - 1];
|
||||||
|
} else {
|
||||||
|
setPanel = panelsHistoryNew[0];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
VK.closeApp();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (panelsHistory.length === 1) {
|
||||||
|
VK.swipeBackOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeView: setView,
|
||||||
|
activePanel: setPanel,
|
||||||
|
activeStory: setStory,
|
||||||
|
|
||||||
|
viewsHistory: {
|
||||||
|
...state.viewsHistory,
|
||||||
|
[state.activeView]: viewsHistory
|
||||||
|
},
|
||||||
|
panelsHistory: {
|
||||||
|
...state.panelsHistory,
|
||||||
|
[state.activeView]: panelsHistory
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPEN_POPOUT: {
|
||||||
|
window.history.pushState(null, null);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
popouts: {
|
||||||
|
...state.popouts,
|
||||||
|
[state.activeView]: action.payload.popout
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case CLOSE_POPOUT: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
popouts: {
|
||||||
|
...state.popouts,
|
||||||
|
[state.activeView]: null
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case OPEN_MODAL: {
|
||||||
|
window.history.pushState(null, null);
|
||||||
|
|
||||||
|
let activeModal = action.payload.id || null;
|
||||||
|
let modalsHistory = state.modalHistory[state.activeView] ? [...state.modalHistory[state.activeView]] : [];
|
||||||
|
|
||||||
|
if (activeModal === null) {
|
||||||
|
modalsHistory = [];
|
||||||
|
} else if (modalsHistory.indexOf(activeModal) !== -1) {
|
||||||
|
modalsHistory = modalsHistory.splice(0, modalsHistory.indexOf(activeModal) + 1);
|
||||||
|
} else {
|
||||||
|
modalsHistory.push(activeModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeModals: {
|
||||||
|
...state.activeModals,
|
||||||
|
[state.activeView]: activeModal
|
||||||
|
},
|
||||||
|
modalHistory: {
|
||||||
|
...state.modalHistory,
|
||||||
|
[state.activeView]: modalsHistory
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case CLOSE_MODAL: {
|
||||||
|
let activeModal = state.modalHistory[state.activeView][state.modalHistory[state.activeView].length - 2] || null;
|
||||||
|
let modalsHistory = state.modalHistory[state.activeView] ? [...state.modalHistory[state.activeView]] : [];
|
||||||
|
|
||||||
|
if (activeModal === null) {
|
||||||
|
modalsHistory = [];
|
||||||
|
} else if (modalsHistory.indexOf(activeModal) !== -1) {
|
||||||
|
modalsHistory = modalsHistory.splice(0, modalsHistory.indexOf(activeModal) + 1);
|
||||||
|
} else {
|
||||||
|
modalsHistory.push(activeModal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeModals: {
|
||||||
|
...state.activeModals,
|
||||||
|
[state.activeView]: activeModal
|
||||||
|
},
|
||||||
|
modalHistory: {
|
||||||
|
...state.modalHistory,
|
||||||
|
[state.activeView]: modalsHistory
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
5
src/js/store/vk/actionTypes.js
Normal file
5
src/js/store/vk/actionTypes.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export const SET_COLOR_SCHEME = 'SET_COLOR_SCHEME';
|
||||||
|
export const SET_ACCESS_TOKEN = 'SET_ACCESS_TOKEN';
|
||||||
|
export const SET_ACTIVE_TAB = 'SET_ACTIVE_TAB';
|
||||||
|
export const SET_SCROLL_POSITION = 'SET_SCROLL_POSITION';
|
||||||
|
export const SET_SCROLL_POSITION_BY_ID = 'SET_SCROLL_POSITION_BY_ID';
|
||||||
51
src/js/store/vk/actions.js
Normal file
51
src/js/store/vk/actions.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
import {
|
||||||
|
SET_COLOR_SCHEME,
|
||||||
|
SET_ACCESS_TOKEN,
|
||||||
|
SET_ACTIVE_TAB,
|
||||||
|
SET_SCROLL_POSITION,
|
||||||
|
SET_SCROLL_POSITION_BY_ID
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
export const setColorScheme = (scheme) => (
|
||||||
|
{
|
||||||
|
type: SET_COLOR_SCHEME,
|
||||||
|
payload: scheme
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setAccessToken = (accessToken) => (
|
||||||
|
{
|
||||||
|
type: SET_ACCESS_TOKEN,
|
||||||
|
payload: accessToken
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setActiveTab = (component, tab) => (
|
||||||
|
{
|
||||||
|
type: SET_ACTIVE_TAB,
|
||||||
|
payload: {
|
||||||
|
component,
|
||||||
|
tab
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setScrollPosition = (component, x = 0, y = 0) => (
|
||||||
|
{
|
||||||
|
type: SET_SCROLL_POSITION,
|
||||||
|
payload: {
|
||||||
|
component,
|
||||||
|
x,
|
||||||
|
y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export const setScrollPositionByID = (component) => (
|
||||||
|
{
|
||||||
|
type: SET_SCROLL_POSITION_BY_ID,
|
||||||
|
payload: {
|
||||||
|
component
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
80
src/js/store/vk/reducers.js
Normal file
80
src/js/store/vk/reducers.js
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import {
|
||||||
|
SET_ACCESS_TOKEN,
|
||||||
|
SET_ACTIVE_TAB,
|
||||||
|
SET_COLOR_SCHEME,
|
||||||
|
SET_SCROLL_POSITION,
|
||||||
|
SET_SCROLL_POSITION_BY_ID
|
||||||
|
} from './actionTypes';
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
accessToken: undefined,
|
||||||
|
colorScheme: 'client_light',
|
||||||
|
|
||||||
|
activeTab: [],
|
||||||
|
componentScroll: []
|
||||||
|
};
|
||||||
|
|
||||||
|
export const vkuiReducer = (state = initialState, action) => {
|
||||||
|
|
||||||
|
switch (action.type) {
|
||||||
|
|
||||||
|
case SET_COLOR_SCHEME: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
colorScheme: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_ACCESS_TOKEN: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
accessToken: action.payload,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_ACTIVE_TAB: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
activeTab: {
|
||||||
|
...state.activeTab,
|
||||||
|
[action.payload.component]: action.payload.tab
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_SCROLL_POSITION: {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
componentScroll: {
|
||||||
|
...state.componentScroll,
|
||||||
|
[action.payload.component]: {
|
||||||
|
x: action.payload.x,
|
||||||
|
y: action.payload.y
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
case SET_SCROLL_POSITION_BY_ID: {
|
||||||
|
let element = document.getElementById(action.payload.component).getElementsByClassName("HorizontalScroll__in")[0];
|
||||||
|
|
||||||
|
let x = element.scrollLeft;
|
||||||
|
let y = element.scrollTop;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
componentScroll: {
|
||||||
|
...state.componentScroll,
|
||||||
|
[action.payload.component]: {
|
||||||
|
x: x,
|
||||||
|
y: y
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user