cloudcmd/client/client.js
2024-03-21 08:46:31 +02:00

435 lines
11 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'use strict';
const process = require('node:process');
/* global DOM */
const Emitify = require('emitify');
const inherits = require('inherits');
const rendy = require('rendy');
const load = require('load.js');
const tryToCatch = require('try-to-catch');
const {addSlashToEnd} = require('format-io');
const pascalCase = require('just-pascal-case');
const currify = require('currify');
const Images = require('./dom/images');
const {unregisterSW} = require('./sw/register');
const getJsonFromFileTable = require('./get-json-from-file-table');
const Key = require('./key');
const {
apiURL,
formatMsg,
buildFromJSON,
} = require('../common/cloudfunc');
const loadModule = require('./load-module');
const noJS = (a) => a.replace(/.js$/, '');
const isDev = process.env.NODE_ENV === 'development';
inherits(CloudCmdProto, Emitify);
module.exports = new CloudCmdProto(DOM);
load.addErrorListener((e, src) => {
const msg = `file ${src} could not be loaded`;
Images.show.error(msg);
});
function CloudCmdProto(DOM) {
let Listeners;
Emitify.call(this);
const CloudCmd = this;
const Info = DOM.CurrentInfo;
const {Storage, Files} = DOM;
this.log = (...a) => {
if (!isDev)
return;
console.log(...a);
};
this.prefix = '';
this.prefixSocket = '';
this.prefixURL = '';
this.MIN_ONE_PANEL_WIDTH = 1155;
this.HOST = location.origin || location.protocol + '//' + location.host;
this.TITLE = 'Cloud Commander';
this.sort = {
left: 'name',
right: 'name',
};
this.order = {
left: 'asc',
right: 'asc',
};
this.changeDir = async (path, {
isRefresh,
panel,
history = true,
noCurrent,
currentName,
} = {}) => {
const refresh = isRefresh;
let panelChanged;
if (!noCurrent && panel && panel !== Info.panel) {
DOM.changePanel();
panelChanged = true;
}
let imgPosition;
if (panelChanged || refresh || !history)
imgPosition = 'top';
Images.show.load(imgPosition, panel);
/* загружаем содержимое каталога */
await ajaxLoad(addSlashToEnd(path), {
refresh,
history,
noCurrent,
currentName,
}, panel);
};
/**
* Конструктор CloudClient, который
* выполняет весь функционал по
* инициализации
*/
this.init = async (prefix, config) => {
CloudCmd.prefix = prefix;
CloudCmd.prefixURL = `${prefix}${apiURL}`;
CloudCmd.prefixSocket = config.prefixSocket;
CloudCmd.DIR_DIST = `${prefix}/dist`;
CloudCmd.DIR_MODULES = `${this.DIR_DIST}/modules`;
CloudCmd.config = (key) => config[key];
CloudCmd.config.if = currify((key, fn, a) => config[key] && fn(a));
CloudCmd._config = (key, value) => {
/*
* should be called from config.js only
* after successful update on server
*/
if (key === 'password')
return;
config[key] = value;
};
if (config.oneFilePanel)
CloudCmd.MIN_ONE_PANEL_WIDTH = Infinity;
if (!document.body.scrollIntoViewIfNeeded)
await load.js(`${CloudCmd.DIR_MODULES}/polyfill.js`);
await initModules();
await baseInit();
await loadStyle();
CloudCmd.route(location.hash);
};
async function loadStyle() {
const {prefix} = CloudCmd;
const name = `${prefix}/dist/cloudcmd.common.css`;
await load.css(name);
}
this.route = (path) => {
const query = path.split('/');
if (!path)
return;
const [kebabModule] = query;
const module = noJS(pascalCase(kebabModule.slice(1)));
const file = query[1];
const current = DOM.getCurrentByName(file);
if (file && !current) {
const msg = formatMsg('set current file', file, 'error');
CloudCmd.log(msg);
return;
}
DOM.setCurrentFile(current);
CloudCmd.execFromModule(module, 'show');
};
this.logOut = async () => {
const url = CloudCmd.prefix + '/logout';
const error = () => document.location.reload();
const {prefix} = CloudCmd;
await DOM.Storage.clear();
unregisterSW(prefix);
DOM.load.ajax({
url,
error,
});
};
const initModules = async () => {
CloudCmd.Key = Key;
CloudCmd.Key.bind();
const [, modules] = await tryToCatch(Files.get, 'modules');
const showLoad = Images.show.load;
const doBefore = {
edit: showLoad,
menu: showLoad,
};
const load = (name, path, dobefore) => {
loadModule({
name,
path,
dobefore,
});
};
if (!modules)
return;
for (const module of modules.local) {
load(null, module, doBefore[module]);
}
};
async function saveCurrentName(currentName) {
await Storage.set('current-name', currentName);
}
async function baseInit() {
const files = DOM.getFiles();
CloudCmd.on('current-file', DOM.updateCurrentInfo);
CloudCmd.on('current-name', saveCurrentName);
const name = await Storage.get('current-name');
const currentFile = name && DOM.getCurrentByName(name) || files[0];
/* выделяем строку с первым файлом */
if (files)
DOM.setCurrentFile(currentFile, {
// when hash is present
// it should be handled with this.route
// overwre otherwise
history: !location.hash,
});
const dirPath = DOM.getCurrentDirPath();
Listeners = CloudCmd.Listeners;
Listeners.init();
const panels = getPanels();
panels.forEach(Listeners.setOnPanel);
Listeners.initKeysPanel();
if (!CloudCmd.config('dirStorage'))
return;
const data = await Storage.get(dirPath);
if (!data)
await Storage.setJson(dirPath, getJsonFromFileTable());
}
function getPanels() {
const panels = ['left'];
if (CloudCmd.config('oneFilePanel'))
return panels;
return [
...panels,
'right',
];
}
this.execFromModule = async (moduleName, funcName, ...args) => {
await CloudCmd[moduleName]();
const func = CloudCmd[moduleName][funcName];
func(...args);
};
this.refresh = async (options = {}) => {
const {panel = Info.panel, currentName} = options;
const path = DOM.getCurrentDirPath(panel);
const isRefresh = true;
const history = false;
const noCurrent = options?.noCurrent;
await CloudCmd.changeDir(path, {
isRefresh,
history,
panel,
noCurrent,
currentName,
});
};
/**
* Функция загружает json-данные о Файловой Системе
* через ajax-запрос.
* @param path - каталог для чтения
* @param options
* { refresh, history } - необходимость обновить данные о каталоге
* @param panel
* @param callback
*
*/
async function ajaxLoad(path, options = {}, panel) {
const {RESTful} = DOM;
CloudCmd.log(`reading dir: "${path}";`);
const dirStorage = CloudCmd.config('dirStorage');
const json = dirStorage && await Storage.getJson(path);
const name = options.currentName || Info.name;
const {noCurrent, refresh} = options;
if (!refresh && json)
return await createFileTable(json, panel, options);
const position = DOM.getPanelPosition(panel);
const sort = CloudCmd.sort[position];
const order = CloudCmd.order[position];
const query = rendy('?sort={{ sort }}&order={{ order }}', {
sort,
order,
});
const [, newObj] = await RESTful.read(path + query, 'json');
if (!newObj)
// that's OK, error handled by RESTful
return;
options.sort = sort;
options.order = order;
await createFileTable(newObj, panel, options);
if (refresh && !noCurrent)
DOM.setCurrentByName(name);
if (!CloudCmd.config('dirStorage'))
return;
Storage.setJson(path, newObj);
}
/**
* Функция строит файловую таблицу
* @param json - данные о файлах
* @param panelParam
* @param history
* @param callback
*/
async function createFileTable(data, panelParam, options) {
const {history, noCurrent} = options;
const names = [
'file',
'path',
'link',
'pathLink',
];
const [error, [file, path, link, pathLink]] = await tryToCatch(Files.get, names);
if (error)
return DOM.Dialog.alert(error.responseText);
const panel = panelParam || DOM.getPanel();
const {prefix} = CloudCmd;
const {dir, name} = Info;
const {childNodes} = panel;
let i = childNodes.length;
while (i--)
panel.removeChild(panel.lastChild);
panel.innerHTML = buildFromJSON({
sort: options.sort,
order: options.order,
data,
id: panel.id,
prefix,
template: {
file,
path,
pathLink,
link,
},
});
Listeners.setOnPanel(panel);
if (!noCurrent) {
let current;
if (name === '..' && dir !== '/')
current = DOM.getCurrentByName(dir);
if (!current)
[current] = DOM.getFiles(panel);
DOM.setCurrentFile(current, {
history,
});
CloudCmd.emit('active-dir', Info.dirPath);
}
}
this.goToParentDir = async () => {
const {
dir,
dirPath,
parentDirPath,
panel,
} = Info;
if (dirPath === parentDirPath)
return;
const path = parentDirPath;
await CloudCmd.changeDir(path);
const current = DOM.getCurrentByName(dir);
const [first] = DOM.getFiles(panel);
DOM.setCurrentFile(current || first, {
history,
});
};
}