HM-126. Перенос хранилищ на новую модалку и логику, добавление работы… (#65)

This commit is contained in:
Nikolay
2020-09-06 10:28:33 +03:00
committed by GitHub
parent bc9b112904
commit 5d19104445
19 changed files with 565 additions and 364 deletions

View File

@ -0,0 +1,33 @@
import {ENDPOINTS} from '../../../api/consts';
import adminConfigsService from '../../../services/AdminConfigsService';
import http from '../../../api/HttpAPI';
class HooksApi {
constructor () {
const {url, options} = adminConfigsService.getApi();
this.URL = `${url}${ENDPOINTS.HOOKS}`;
this.OPTIONS = options;
}
request = async (key) => {
const {data} = await http.get(this.URL, {key}, this.OPTIONS);
return data;
}
create = async (key, createData) => {
const {data} = await http.post(this.URL, createData, {
...this.OPTIONS,
params: {key},
});
return data;
}
remove = async (key, id) => {
const {data} = await http.delete(this.URL, {key, id}, this.OPTIONS);
return data;
}
}
const hooksApi = new HooksApi();
export default hooksApi;

View File

@ -0,0 +1,115 @@
import Modal from '../../../../core/components/modal/Modal';
import {TAG_NAME, EVENTS} from '../../../../core/consts';
import FormControl from '../../../../core/components/form-control/FormControl';
import {FormCheck} from '../../../../core/components/form-check/FormCheck';
import {v4} from 'uuid';
import userInfoService from '../../../../services/UserInfoService';
import routeService from '../../../../services/RouteService';
import hooksApi from '../../api/HooksAPI';
export class AddHookModal extends Modal {
constructor (parentNode) {
super(parentNode);
this.createElement({
tagName: TAG_NAME.DIV,
parentNode: this.header,
options: {
textContent: 'Создание хука',
},
});
this.form = this.createElement({
tagName: TAG_NAME.FORM,
parentNode: this.content,
});
this.inputs = [
this.holderInput = this.createComponent(FormControl, this.form, {
id: 'add-hook-modal-holder',
label: 'Название',
}),
this.descriptionInput = this.createComponent(FormControl, this.form, {
id: 'add-hook-modal-description',
label: 'Описание'
}),
this.readCheckbox = this.createComponent(FormCheck, this.form, {
id: 'add-hook-modal-read',
label: 'Позволяет чтение',
}),
this.writeCheckbox = this.createComponent(FormCheck, this.form, {
id: 'add-hook-modal-write',
label: 'Позволяет запись',
}),
];
this.alert = this.createElement({
tagName: TAG_NAME.DIV,
parentNode: this.content,
options: {
className: 'alert alert-success',
},
});
this.saveButton = this.createElement({
tagName: TAG_NAME.BUTTON,
parentNode: this.footer,
options: {
textContent: 'Создать',
className: 'btn btn-primary',
}
});
this.cancelButton = this.createElement({
tagName: TAG_NAME.BUTTON,
parentNode: this.footer,
options: {
textContent: 'Отмена',
className: 'btn btn-secondary',
},
});
this.addEventListener(this.saveButton, EVENTS.CLICK, this.saveHook);
this.addEventListener(this.cancelButton, EVENTS.CLICK, this.cancel);
}
openForm = () => {
this.inputs.forEach((input) => {
input.mainNode.style.display = 'block';
input.setValue('');
});
this.alert.style.display = 'none';
this.saveButton.disabled = false;
this.cancelButton.textContent = 'Отмена';
this.show();
}
saveHook = async () => {
const token = v4();
const {login} = await userInfoService.getUserInfo();
const {query: {key}} = routeService.getUrlData();
const hook = {
holder: this.holderInput.getValue(),
description: this.descriptionInput.getValue(),
rights: {
write: this.writeCheckbox.getValue(),
read: this.readCheckbox.getValue(),
},
token,
author: login,
};
this.inputs.forEach((input) => {
input.mainNode.style.display = 'none';
});
this.saveButton.disabled = true;
await hooksApi.create(key, hook);
this.cancelButton.textContent = 'Закрыть';
this.alert.textContent = `"${token}" - сохраните ваш хук. Больше его нельзя будет увидеть.`;
this.alert.style.display = 'block';
this.next(EVENTS.SAVE_HOOK);
}
cancel = () => {
this.hide();
}
}

View File

@ -1,39 +0,0 @@
.Info__body {
height: 100%;
display: flex;
flex-direction: column;
}
.Info__container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.Info__title {
text-align: center;
}
.Info__service_name {
align-self: center;
}
.Info__editor {
border: 1px solid #F2F2F2;
text-align: left;
width: 100%;
height: 40vh;
}
.Info__controls {
position: absolute;
right: 15px;
top: 5px;
font-size: 20px;
}
.Button__delete {
background: none;
border: none;
}
.Button__edit {
background: none;
border: none;
}

View File

@ -1,61 +0,0 @@
import Component from '../../../../core/components/component/Component';
import './ApiInfoComponent.css';
import Modal from '../../../../core/components/modal/Modal';
import storageApi from '../../api/StorageServiceAPI';
class ApiInfoComponent extends Component {
constructor (container) {
super('#api-info', container);
this.infoTitle = this.mainNode.querySelector('.Info__title');
this.infoHeader = this.mainNode.querySelector('.Info__header');
this.infoDescription = this.mainNode.querySelector('.Info__description');
this.infoServiceName = this.mainNode.querySelector('.Info__service_name');
this.infoEditor = this.mainNode.querySelector('.Info__editor');
this.infoFooter = this.mainNode.querySelector('.Info__footer');
this.infoBody = this.mainNode.querySelector('.Info__body');
this.infoControls = this.mainNode.querySelector('.Info__controls');
this.btnDelete = this.mainNode.querySelector('.Button__delete');
this.btnEdit = this.mainNode.querySelector('.Button__edit');
this.info = {
headerNode: this.infoHeader,
contentNode: this.infoBody,
footerNode: this.infoFooter
};
this.key = null;
this.modal = this.createComponent(Modal, document.body, this.info);
this.addEventListener(this.btnDelete, 'click', () => {
if (this.key !== null) {
this.deleteApi(this.key);
}
});
}
render = (object, index) => {
this.object = object;
this.index = index;
this.key = object.key;
this.modal.show();
if (this.object) {
let title = '';
if (this.object.key.length > 20) {
title = `${this.object.key.substr(0, 20)} ...`;
} else title = this.object.key;
this.infoTitle.textContent = title;
this.infoServiceName.textContent = this.object.service_name;
this.infoEditor.innerHTML = JSON.stringify(this.object.value, false, 4);
this.infoFooter.textContent = this.object.author;
this.infoDescription.textContent = this.object.description;
}
}
deleteApi = async (key) => {
await storageApi.remove(key);
this.key = null;
this.modal.hide();
this.next('deleteApi');
}
}
export default ApiInfoComponent;

View File

@ -1,111 +1,255 @@
import Component from '../../../../core/components/component/Component';
import ModalSidebar from '../../../../core/components/modal-sidebar/ModalSibebar';
import FormControl from '../../../../core/components/form-control/FormControl';
import {FORM_TYPES} from '../../../../core/consts';
import {FORM_TYPES, EVENTS, MODES} from '../../../../core/consts';
import './ApiTableViewForm.css';
import storageApi from '../../api/StorageServiceAPI';
import routeService from '../../../../services/RouteService';
import {HooksComponent} from '../hooks-component/HooksComponent';
const TITLE_MODES = {
[MODES.Create]: 'Создание нового хранилища',
[MODES.Edit]: 'Редактирование хранилища',
[MODES.View]: 'Просмотр хранилища',
};
const EDIT_MODES = [MODES.Create, MODES.Edit];
const FILL_FIELD = 'Заполните поле';
class ApiTableViewForm extends Component {
constructor (parentNode) {
super('#api-view-form', parentNode);
this.sidebar = this.createComponent(ModalSidebar, {
content: this.mainNode
content: this.mainNode,
});
this.key = null;
this.isEdit = false;
this.title = this.mainNode.querySelector('.h2');
this.form = this.mainNode.querySelector('form');
this.deleteBtn = this.mainNode.querySelector('.Button__delete');
this.addEventListener(this.deleteBtn, 'click', () => {
if (this.key !== null) {
this.deleteApi(this.key);
}
});
this.editBtn = this.mainNode.querySelector('.Button__edit');
this.addEventListener(this.editBtn, 'click', () => {
this.editApi();
});
this.editIcon = this.mainNode.querySelector('.Edit__icon');
this.editBtnText = this.mainNode.querySelector('.Button__edit-text');
this.editBtnText.textContent = 'Редактировать';
this.deleteBtnText = this.mainNode.querySelector('.Button__delete-text');
this.deleteBtnText.textContent = 'Удалить';
this.cancelBtnText = this.mainNode.querySelector('.Button__cancel-text');
this.cancelBtnText.textContent = 'Отменить';
this.title.textContent = 'Информация о хранилище';
this.inputs = [
this.keyInput = this.createComponent(FormControl, this.form, {
id: 'api-key-input',
label: 'Название хранилища'
label: 'Название хранилища',
pattern: '^[a-zA-Z][a-zA-Z0-9_-]{3,}$',
required: true,
}),
this.serviceNameInput = this.createComponent(FormControl, this.form, {
id: 'api-service-name-input',
label: 'Название сервиса'
label: 'Название сервиса',
required: true,
}),
this.authorInput = this.createComponent(FormControl, this.form, {
id: 'api-author-input',
label: 'Автор хранилища'
label: 'Автор хранилища',
required: true,
}),
this.descriptionInput = this.createComponent(FormControl, this.form, {
id: 'api-description-input',
label: 'Краткое описание'
label: 'Краткое описание',
required: true,
}),
this.valueInput = this.createComponent(FormControl, this.form, {
id: 'api-value-input',
type: FORM_TYPES.TEXTAREA,
className: 'Api__value-input',
label: 'Содержимое хранилища'
label: 'Содержимое хранилища',
required: true,
}),
];
this.buttons = [
{
button: this.createButton = this.mainNode.querySelector('.ApiViewForm__create'),
modes: [MODES.Create],
},
{
button: this.saveButton = this.mainNode.querySelector('.ApiViewForm__save'),
modes: [MODES.Edit],
},
{
button: this.editButton = this.mainNode.querySelector('.ApiViewForm__edit'),
modes: [MODES.View],
},
{
button: this.cancelButton = this.mainNode.querySelector('.ApiViewForm__cancel'),
modes: [MODES.Create, MODES.Edit, MODES.View],
},
{
button: this.deleteButton = this.mainNode.querySelector('.ApiViewForm__delete'),
modes: [MODES.View, MODES.Edit],
}
];
this.addEventListener(this.createButton, EVENTS.CLICK, this.createStore);
this.addEventListener(this.saveButton, EVENTS.CLICK, this.saveStore);
this.addEventListener(this.deleteButton, EVENTS.CLICK, this.deleteStore);
this.addEventListener(this.editButton, EVENTS.CLICK, () => {
routeService.pushQuery({
mode: MODES.Edit,
});
});
this.addEventListener(this.cancelButton, EVENTS.CLICK, this.handleCloseModal);
this.inputs.forEach((input) => {
input.disabled(true);
});
this.hooksContainer = this.createComponent(HooksComponent, this.mainNode);
this.form.insertAdjacentElement('afterend', this.hooksContainer.mainNode);
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
const {mode, key} = query;
this.setForm(mode, key);
this.setVisibleHooks(mode);
});
const {query: {mode, key}} = routeService.getUrlData();
if (mode) {
this.setForm(mode, key);
}
}
stringifyValue (value) {
return JSON.stringify(value, false, 4);
handleCloseModal = () => {
routeService.pushQuery({}, true);
}
setForm ({key, service_name, author, description, value}) {
setVisibleHooks = (mode) => {
const method = mode !== MODES.View ? 'add' : 'remove';
this.hooksContainer.mainNode.classList[method]('invisible');
}
setSidebarTitle = (mode, storeKey) => {
this.title.textContent = [TITLE_MODES[mode], storeKey].filter(Boolean).join(' ');
}
stringifyValue = (value) => {
return value ? JSON.stringify(value, false, 4) : '';
}
loadStore = async (key) => {
if (key) {
return await storageApi.find(key);
}
return {
key: '',
value: '',
description: '',
service_name: '',
author: '',
};
}
setInputDisabled = (mode) => {
const disabled = !EDIT_MODES.includes(mode);
this.inputs.forEach((input) => {
const disabledLogin = this.keyInput === input && mode === MODES.Edit;
input.disabled(disabled ? disabled : disabledLogin);
});
}
showButtons = (mode) => {
this.buttons.forEach(({button, modes}) => {
const isShow = modes.includes(mode);
button.style.display = isShow ? 'inline-block' : 'none';
});
}
parseJSON = (text) => {
return JSON.parse(text);
}
validateInputs = () => {
this.form.classList.add('was-validated');
this.inputs.forEach((input) => {
const value = input.getValue();
if (!value) {
input.setError(FILL_FIELD);
}
});
try {
const value = this.valueInput.getValue();
this.parseJSON(value);
this.valueInput.setError('');
} catch (e) {
this.valueInput.setError('Некорректный json');
}
return this.form.checkValidity();
}
clearForm = () => {
this.form.classList.remove('was-validated');
this.inputs.forEach((input) => {
input.setValue('');
input.setError('');
});
}
createStore = () => {
if (this.validateInputs()) {
this.next(EVENTS.CREATE_STORE, {
key: this.keyInput.getValue(),
value: this.parseJSON(this.valueInput.getValue()),
service_name: this.serviceNameInput.getValue(),
author: this.authorInput.getValue(),
description: this.descriptionInput.getValue(),
});
this.clearForm();
routeService.pushQuery({}, true);
}
}
saveStore = () => {
if (this.validateInputs()) {
this.next(EVENTS.SAVE_STORE, {
key: this.keyInput.getValue(),
value: this.parseJSON(this.valueInput.getValue()),
service_name: this.serviceNameInput.getValue(),
author: this.authorInput.getValue(),
description: this.descriptionInput.getValue(),
});
this.clearForm();
routeService.pushQuery({}, true);
}
}
deleteStore = () => {
const {query: {key}} = routeService.getUrlData();
this.next(EVENTS.DELETE_STORE, key);
routeService.pushQuery({}, true);
}
setKeyInput = (mode, key) => {
const disabled = mode !== MODES.Create;
this.keyInput.disabled(disabled);
this.keyInput.setValue(key);
}
setForm = async (mode, storeKey) => {
const {key, value, description, service_name, author} = await this.loadStore(storeKey);
this.setSidebarTitle(mode, storeKey);
this.setKeyInput(mode, key);
this.serviceNameInput.setValue(service_name);
this.authorInput.setValue(author);
this.descriptionInput.setValue(description);
this.valueInput.setValue(this.stringifyValue(value));
this.key = key;
this.sidebar.show();
}
this.setInputDisabled(mode);
this.showButtons(mode);
deleteApi = async (key) => {
await storageApi.remove(key);
this.key = null;
this.next('deleteApi');
this.sidebar.hide();
}
editApi () {
if (!this.isEdit) {
this.inputs.forEach((input) => {
input.disabled(false);
});
this.editBtnText.textContent = 'Сохранить';
this.editIcon.className = 'fas fa-check Edit__icon';
this.isEdit = true;
if (mode) {
this.sidebar.show();
} else {
this.inputs.forEach((input) => {
input.disabled(true);
});
this.editBtnText.textContent = 'Редактировать';
this.editIcon.className = 'fas fa-pencil-alt Edit__icon';
this.isEdit = false;
this.sidebar.hide();
}
}
}

View File

@ -1,66 +0,0 @@
.Create__body {
height: 100%;
display: flex;
}
.Create__container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.Create__title {
text-align: center;
}
.Create__send {
align-self: flex-end;
}
.Create__sidebar {
width: 30%;
overflow-y: auto;
}
.Create__footer {
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-end;
display: flex;
width: 100%;
}
.Create__error-container {
font-size: 0.8em;
color: #F46E42;
margin: auto;
}
.Create__editor {
border: 1px solid #F2F2F2;
text-align: left;
width: 100%;
min-height: 40vh;
margin: 10px 0;
}
.Create__controls {
position: absolute;
right: 10px;
top: 15px;
font-size: 20px;
}
.Create__input {
margin: 10px;
}
.Large__container {
max-width: 800px;
}
.Scroll__body {
overflow-y: auto;
max-height: 60vh;
}
@media (max-width: 800px) {
.Create__body {
flex-direction: column;
}
}

View File

@ -1,75 +0,0 @@
import Component from '../../../../core/components/component/Component';
import ButtonComponent from '../../../../core/components/button-component/ButtonComponent';
import './CreateApiComponent.css';
import storageApi from '../../api/StorageServiceAPI';
import Modal from '../../../../core/components/modal/Modal';
class CreateApiComponent extends Component {
constructor (container) {
super('#create-api', container);
this.inputKey = this.mainNode.querySelector('.Create__key');
this.inputServiceName = this.mainNode.querySelector('.Create__serviceName');
this.inputDescription = this.mainNode.querySelector('.Create__description');
this.inputAuthor = this.mainNode.querySelector('.Create__author');
this.createError = this.mainNode.querySelector('.Create__error-container');
this.header = this.mainNode.querySelector('.Create__title');
this.body = this.mainNode.querySelector('.Create__body');
this.footer = this.mainNode.querySelector('.Create__footer');
this.form = this.mainNode.querySelector('.Create__form');
this.editor = this.mainNode.querySelector('.Create__editor');
this.button = this.createComponent(ButtonComponent, this.footer, 'Создать', 'btn btn-outline-primary Create__send');
this.content = {
headerNode: this.header,
contentNode: this.body,
footerNode: this.footer
};
this.modal = this.createComponent(Modal, document.body, this.content);
this.modal.container.classList.add('Large__container');
this.modal.content.classList.add('Scroll__body');
this.button.subscribe('click', () => {
this.send();
});
}
show = () => {
this.modal.show();
}
parseString = async (text) => {
const obj = await JSON.parse(text);
return obj;
}
send = async () => {
try {
const obj = await JSON.parse(this.editor.textContent);
this.data = {
key: this.inputKey.value,
value: obj,
description: this.inputDescription.value,
service_name: this.inputServiceName.value,
author: this.inputAuthor.value
};
try {
await storageApi.create(this.data);
this.createError.textContent = '';
this.modal.hide();
this.next('renderTable');
} catch (err) {
if (err.response.status === 502) {
this.createError.textContent = 'Заполните все необходимые поля для создания хранилища';
} else if (err.response.status === 404) {
this.createError.textContent = 'Не удалось создать хранилище. Возможно Вы используете не уникальное название хранилища';
} else {
this.createError.textContent = 'Что-то пошло не так';
}
}
} catch {
this.createError.textContent = 'Тело хранилища не соответствует требованиям';
}
}
}
export default CreateApiComponent;

View File

@ -0,0 +1,23 @@
import Component from '../../../../core/components/component/Component';
import {EVENTS} from '../../../../core/consts';
export class HooksRow extends Component {
constructor (parentNode, hook) {
super('#api-hooks-row', parentNode);
this.holder = this.mainNode.querySelector('.Hook__holder');
this.description = this.mainNode.querySelector('.Hook__description');
this.read = this.mainNode.querySelector('.Hook__read');
this.write = this.mainNode.querySelector('.Hook__write');
this.deleteButton = this.mainNode.querySelector('.close');
this.holder.textContent = hook.holder;
this.description.textContent = hook.description;
this.read.textContent = hook.rights.read ? 'Да' : 'Нет';
this.write.textContent = hook.rights.write ? 'Да' : 'Нет';
this.addEventListener(this.deleteButton, EVENTS.CLICK, () => {
this.next(EVENTS.DELETE_HOOK, hook.id);
});
}
}

View File

@ -0,0 +1,51 @@
import Component from '../../../../core/components/component/Component';
import hooksApi from '../../api/HooksAPI';
import routeService from '../../../../services/RouteService';
import {EVENTS} from '../../../../core/consts';
import {HooksRow} from './HookRow';
import {AddHookModal} from '../add-hook-modal/AddHookModal';
export class HooksComponent extends Component {
constructor () {
super('#api-hooks', null);
this.hooks = [];
this.container = this.mainNode.querySelector('.list-group');
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
const {mode, key} = query;
this.initHooks(mode, key);
});
this.addHookButton = this.mainNode.querySelector('.ApiHooks__addButton');
this.addHookModal = this.createComponent(AddHookModal, this.mainNode);
this.addEventListener(this.addHookButton, EVENTS.CLICK, this.addHookModal.openForm);
this.addSubscribe(this.addHookModal, EVENTS.SAVE_HOOK, this.initHooks);
this.initHooks();
}
initHooks = async () => {
const {query: {mode, key}} = routeService.getUrlData();
this.hooks.forEach((hook) => {
hook.destroy();
});
this.hooks = [];
if (key && mode) {
const hooks = await hooksApi.request(key);
this.hooks = hooks.map((hook) => {
const component = this.createComponent(HooksRow, this.container, hook);
this.addSubscribe(component, EVENTS.DELETE_HOOK, async (id) => {
await hooksApi.remove(key, id);
this.initHooks(mode, key);
});
return component;
});
}
}
}

View File

@ -1,12 +1,11 @@
import Component from '../../../../core/components/component/Component';
import storageApi from '../../api/StorageServiceAPI';
import FilterApiComponent from '../filter-api-component/FilterApiComponent';
import ApiInfoComponent from '../api-info-component/ApiInfoComponent';
import CreateApiComponent from '../create-api-component/CreateApiComponent';
import ButtonComponent from '../../../../core/components/button-component/ButtonComponent';
import ApiTableComponent from '../api-table-component/ApiTableComponent';
import ApiTableViewForm from '../api-table-view-form/ApiTableViewForm';
import {EVENTS} from '../../../../core/consts';
import {EVENTS, MODES} from '../../../../core/consts';
import routeService from '../../../../services/RouteService';
class ApiPage extends Component {
constructor (mainNodeSelector, parentNode) {
@ -16,26 +15,31 @@ class ApiPage extends Component {
this.apiViewForm = this.createComponent(ApiTableViewForm);
this.infoBox = this.createComponent(ApiInfoComponent);
this.createWindow = this.createComponent(CreateApiComponent);
this.createBtn = this.createComponent(ButtonComponent, this.filterBox.filterButtonBox, '✚', 'btn btn-primary mb-3 Create__btn');
this.addSubscribe(this.createBtn, 'click', () => {
this.createWindow.show();
});
this.addSubscribe(this.createWindow, 'renderTable', () => {
this.initStorageListTable();
this.addSubscribe(this.createBtn, EVENTS.CLICK, () => {
routeService.pushQuery({mode: MODES.Create}, true);
});
this.initStorageListTable();
this.addSubscribe(this.apiViewForm, 'deleteApi', () => {
this.addSubscribe(this.apiViewForm, EVENTS.CREATE_STORE, async (store) => {
await storageApi.create(store);
this.initStorageListTable();
});
this.addSubscribe(this.apiViewForm, EVENTS.SAVE_STORE, async (store) => {
await storageApi.update(store);
this.initStorageListTable();
});
this.addSubscribe(this.apiViewForm, EVENTS.DELETE_STORE, async (storeKey) => {
await storageApi.remove(storeKey);
this.initStorageListTable();
});
this.apiTable = this.createComponent(ApiTableComponent, this.mainNode);
this.addSubscribe(this.apiTable, EVENTS.ROW_DOUBLE_CLICK, (_, row) => {
this.apiViewForm.setForm(row);
routeService.pushQuery({mode: MODES.View, key: row.key}, true);
});
}