HM-133. Рефакторинг (#64)

This commit is contained in:
Nikolay
2020-09-02 20:07:26 +03:00
committed by GitHub
parent caa7671b83
commit bc9b112904
132 changed files with 421 additions and 757 deletions

View File

@ -0,0 +1,77 @@
import {ENDPOINTS} from '../../../api/consts';
import adminConfigsService from '../../../services/AdminConfigsService';
import http from '../../../api/HttpAPI';
/**
* @interface Store
* @type {Object}
* @property {string} key - ключ апи
* @property {unknown} value - любое значение апи
* @property {string} description - описание апи
* @property {string} service_name - имя сервиса в котором используется апи
* @property {string} author - автор, который создал апи
*/
/**
* Класс для работы с store-service api
* @class
*/
class StorageServiceApi {
constructor () {
const {url, options} = adminConfigsService.getApi();
this.URL = `${url}${ENDPOINTS.STORE}`;
this.OPTIONS = options;
}
/**
* Запрос полного списка всех api
* @returns {Promise<Store[]>} - Возвращает список всех api
*/
request = async () => {
const {data} = await http.get(this.URL, null, this.OPTIONS);
return data;
}
/**
* Запрос api по ключу
* @param {string} key - ключ хранилища в api
* @returns {Promise<Store>} - Возвращает api по указанному ключу
*/
find = async (key) => {
const {data} = await http.get(`${this.URL}/${encodeURIComponent(key)}`, null, this.OPTIONS);
return data;
}
/**
* Создание api
* @param {Store} createData - объект создания api
* @returns {Promise<Store>} - Возвращает вновь созданный элемент
*/
create = async (createData) => {
const {data} = await http.post(this.URL, createData, this.OPTIONS);
return data;
}
/**
* Обновление api
* @param {Store} updateData - объект обновления api
* @returns {Promise<Store>} - Возвращает обновленный элемент
*/
update = async (updateData) => {
const {data} = await http.put(this.URL, updateData, this.OPTIONS);
return data;
}
/**
* Удаление api по ключу
* @param {string} key - ключ хранилища api
* @returns {Promise<Store>} - Возвращает удаленный элемент
*/
remove = async (key) => {
const {data} = await http.delete(`${this.URL}/${encodeURIComponent(key)}`, null, this.OPTIONS);
return data;
}
}
const storageApi = new StorageServiceApi();
export default storageApi;

View File

@ -0,0 +1,39 @@
.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

@ -0,0 +1,61 @@
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

@ -0,0 +1,14 @@
import Table from '../../../../core/components/table/Table';
import ApiTableRowComponent from './ApiTableRowComponent';
import {API_COLS} from '../../consts';
class ApiTableComponent extends Table {
constructor (parentNode) {
super(parentNode, API_COLS);
}
renderRow = (parentNode, cols, row, index) => {
return this.createComponent(ApiTableRowComponent, parentNode, cols, row, index);
}
}
export default ApiTableComponent;

View File

@ -0,0 +1,16 @@
import Component from '../../../../core/components/component/Component';
import TableCellOverflow from '../../../../core/components//table-cell-overflow/TableCellOverflow';
class ApiTableRowComponent extends Component {
constructor (parentNode, cols, row, index) {
super(null, parentNode);
this.cols = cols.map((col) => {
if (col.id === 'index') {
return this.createComponent(TableCellOverflow, this.mainNode, index + 1);
}
return this.createComponent(TableCellOverflow, this.mainNode, row[col.id]);
});
}
}
export default ApiTableRowComponent;

View File

@ -0,0 +1,44 @@
.Api__value-input {
height: 200px;
}
.Api__view-controls {
position: sticky;
bottom: 0;
width: 100%;
font-size: 1em;
display: flex;
flex-direction: row;
justify-content: space-around;
background-color: white;
min-height: 50px;
}
.Button__delete {
width: 100%;
}
.Button__delete-text {
margin: 10px;
}
.Button__edit {
width: 100%;
}
.Button__edit-text {
margin: 10px;
}
.Button__cancel {
width: 100%;
}
.Button__cancel-text {
margin: 10px;
}
@media (max-width: 600px) {
.Button__delete-text {
display: none;
}
.Button__edit-text {
display: none;
}
.Button__cancel-text {
display: none;
}
}

View File

@ -0,0 +1,112 @@
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 './ApiTableViewForm.css';
import storageApi from '../../api/StorageServiceAPI';
class ApiTableViewForm extends Component {
constructor (parentNode) {
super('#api-view-form', parentNode);
this.sidebar = this.createComponent(ModalSidebar, {
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: 'Название хранилища'
}),
this.serviceNameInput = this.createComponent(FormControl, this.form, {
id: 'api-service-name-input',
label: 'Название сервиса'
}),
this.authorInput = this.createComponent(FormControl, this.form, {
id: 'api-author-input',
label: 'Автор хранилища'
}),
this.descriptionInput = this.createComponent(FormControl, this.form, {
id: 'api-description-input',
label: 'Краткое описание'
}),
this.valueInput = this.createComponent(FormControl, this.form, {
id: 'api-value-input',
type: FORM_TYPES.TEXTAREA,
className: 'Api__value-input',
label: 'Содержимое хранилища'
}),
];
this.inputs.forEach((input) => {
input.disabled(true);
});
}
stringifyValue (value) {
return JSON.stringify(value, false, 4);
}
setForm ({key, service_name, author, description, value}) {
this.keyInput.setValue(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();
}
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;
} else {
this.inputs.forEach((input) => {
input.disabled(true);
});
this.editBtnText.textContent = 'Редактировать';
this.editIcon.className = 'fas fa-pencil-alt Edit__icon';
this.isEdit = false;
}
}
}
export default ApiTableViewForm;

View File

@ -0,0 +1,66 @@
.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

@ -0,0 +1,75 @@
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,8 @@
.Filter__input {
min-width: 200px;
}
.Create__btn {
margin: 0 20px;
}

View File

@ -0,0 +1,50 @@
import Component from '../../../../core/components/component/Component';
import ButtonComponent from '../../../../core/components/button-component/ButtonComponent';
import './FilterApiComponent.css';
import routeService from '../../../../services/RouteService';
import FilterInputComponent from '../filter-input-component/FilterInputComponent';
import {ROUTES, EVENTS} from '../../../../core/consts';
import {FILTER_ROWS} from '../../consts';
class FilterApiComponent extends Component {
constructor (container) {
super('#filter-container', container);
this.form = this.mainNode.querySelector('.Filter__form');
this.filterInputsBox = this.mainNode.querySelector('.Filter__inputs-box');
this.filterButtonBox = this.mainNode.querySelector('.Filter__button-box');
this.searchBtn = this.createComponent(ButtonComponent, this.filterButtonBox, 'Найти', 'btn btn-primary mb-3');
this.inputs = FILTER_ROWS.map((item) => {
const {query} = routeService.getUrlData();
const input = this.createComponent(FilterInputComponent, this.filterInputsBox, item);
Object.keys(query).forEach((elem) => {
if (elem === item.key) {
input.input.value = query[elem];
}
});
return input;
});
this.searchBtn.subscribe(EVENTS.CLICK, () => {
this.send();
});
this.addEventListener(this.form, EVENTS.SUBMIT, (event) => {
event.preventDefault();
this.send();
});
}
send = () => {
const query = this.inputs.reduce((memo, element) => {
const name = element.input.name;
return {
...memo,
[name]: element.input.value
};
}, {});
routeService.goTo(ROUTES.STORE, query);
}
}
export default FilterApiComponent;

View File

@ -0,0 +1,20 @@
import Component from '../../../../core/components/component/Component';
class FilterInputComponent extends Component {
constructor (container, content) {
super('#filter-input', container);
this.label = this.mainNode.querySelector('label');
this.label.htmlFor = content.id;
this.label.textContent = content.title;
this.input = this.mainNode.querySelector('input');
this.input.placeholder = content.placeholder;
this.input.id = content.id;
this.input.name = content.key;
}
getValue = () => {
return this.input.value;
}
}
export default FilterInputComponent;

View File

@ -0,0 +1,54 @@
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';
class ApiPage extends Component {
constructor (mainNodeSelector, parentNode) {
super(mainNodeSelector, parentNode);
this.filterBox = this.createComponent(FilterApiComponent, this.mainNode);
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.initStorageListTable();
this.addSubscribe(this.apiViewForm, 'deleteApi', () => {
this.initStorageListTable();
});
this.apiTable = this.createComponent(ApiTableComponent, this.mainNode);
this.addSubscribe(this.apiTable, EVENTS.ROW_DOUBLE_CLICK, (_, row) => {
this.apiViewForm.setForm(row);
});
}
initStorageListTable = async () => {
this.apiList = await storageApi.request();
return this.renderTable();
};
renderTable = () => {
this.render(() => {
this.apiTable.render(this.apiList);
});
}
}
export default ApiPage;

View File

@ -0,0 +1,14 @@
export const API_COLS = [
{id: 'index', label: '#'},
{id: 'key', label: 'Название хранилища'},
{id: 'description', label: 'Описание'},
{id: 'service_name', label: 'Имя сервиса'},
{id: 'author', label: 'Автор'},
];
export const FILTER_ROWS = [
{title: 'Название хранилища', placeholder: 'Введите текст', key: 'key', id: 'filter-api-input-key'},
{title: 'Описание', placeholder: 'Введите текст', key: 'description', id: 'filter-api-input-description'},
{title: 'Имя сервиса', placeholder: 'Введите текст', key: 'service_name', id: 'filter-api-input-service-name'},
{title: 'Автор', placeholder: 'Введите текст', key: 'author', id: 'filter-api-input-author'},
];