diff --git a/.babelrc b/.babelrc index ceb7b89a..48f399f2 100644 --- a/.babelrc +++ b/.babelrc @@ -9,6 +9,7 @@ "plugins": [ "@babel/plugin-transform-object-assign", "@babel/plugin-proposal-object-rest-spread", + "@babel/plugin-syntax-dynamic-import", "module:fast-async", "module:babel-plugin-macros", ] diff --git a/.eslintrc b/.eslintrc index 3b54c586..ecd242d5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,4 +1,5 @@ { + "parser": "babel-eslint", "env": { "es6": true, "node": true, @@ -14,7 +15,7 @@ "indent": ["error", 4], "semi": "error", "no-console": 0, - "no-use-before-define": ["error", "nofunc"] + "no-use-before-define": 0, }, "extends": [ "eslint:recommended" diff --git a/.npmignore b/.npmignore index 3243bfd4..35f1bd5c 100644 --- a/.npmignore +++ b/.npmignore @@ -11,6 +11,8 @@ yarn-error.log yarn.lock now.json +dist*/modules + modules/jquery-mouse-wheel modules/jquery/dist diff --git a/.webpack/js.js b/.webpack/js.js index 27128537..00685e7e 100644 --- a/.webpack/js.js +++ b/.webpack/js.js @@ -27,6 +27,8 @@ const devtool = isDev ? 'eval' : 'source-map'; const notEmpty = (a) => a; const clean = (array) => array.filter(notEmpty); +const noParse = (a) => /\.spec\.js$/.test(a); + const babelDev = { babelrc: false, plugins: [ @@ -55,8 +57,8 @@ const plugins = [ ]; const splitChunks = { - chunks: 'all', name: 'cloudcmd.common', + chunks: 'async', }; module.exports = { @@ -96,6 +98,7 @@ module.exports = { ], module: { rules, + noParse, }, plugins, }; diff --git a/client/client.js b/client/client.js index 0217cd0f..2302c17f 100644 --- a/client/client.js +++ b/client/client.js @@ -1,14 +1,16 @@ 'use strict'; -/* global Util, DOM */ +/* global DOM */ -const itype = require('itype/legacy'); const Emitify = require('emitify/legacy'); const inherits = require('inherits'); const rendy = require('rendy/legacy'); const wraptile = require('wraptile/legacy'); const exec = require('execon'); +const {kebabToCamelCase} = require('../common/util'); +const isDev = process.env.NODE_ENV === 'development'; + const Images = require('./dom/images'); const { unregisterSW, @@ -27,22 +29,21 @@ const { buildFromJSON, } = require('../common/cloudfunc'); -/* global Util, DOM */ +const loadModule = require('./load-module'); inherits(CloudCmdProto, Emitify); -module.exports = new CloudCmdProto(Util, DOM); +module.exports = new CloudCmdProto(DOM); -function CloudCmdProto(Util, DOM) { +function CloudCmdProto(DOM) { let Key; - let Debug; let Listeners; - const log = (str) => { - if (!Debug) + const log = (...a) => { + if (!isDev ) return; - console.log(str); + console.log(...a); }; Emitify.call(this); @@ -75,16 +76,6 @@ function CloudCmdProto(Util, DOM) { right: 'asc', }; - log.enable = () => { - Debug = true; - }; - - log.disable = () => { - Debug = false; - }; - - const kebabToCamelCase = Util.kebabToCamelCase; - /** * Функция привязываеться ко всем ссылкам и * загружает содержимое каталогов @@ -127,49 +118,6 @@ function CloudCmdProto(Util, DOM) { }, panel, callback); }; - /** - * function load modules - * @params = {name, path, func, dobefore, arg} - */ - function loadModule(params) { - if (!params) - return; - - let path = params.path; - const name = params.name || path && kebabToCamelCase(path); - const func = params.func; - const funcName = params.funcName; - const doBefore = params.dobefore; - - const isContain = /\.js/.test(path); - - if (!isContain) - path += '.js'; - - if (CloudCmd[name]) - return; - - CloudCmd[name] = (...args) => { - const prefix = CloudCmd.PREFIX; - const pathFull = prefix + CloudCmd.DIRCLIENT_MODULES + path; - - exec(doBefore); - - const done = (error) => { - const Proto = CloudCmd[name]; - - if (error || !itype.function(Proto)) - return; - - CloudCmd[name] = new Proto(...args); - }; - - return DOM.load.js(pathFull, func || done); - }; - - CloudCmd[name][funcName] = CloudCmd[name]; - } - /** * Конструктор CloudClient, который * выполняет весь функционал по @@ -180,7 +128,6 @@ function CloudCmdProto(Util, DOM) { initModules, baseInit, loadPlugins, - loadStyle, exec.with(CloudCmd.route, location.hash), ], noop); @@ -217,13 +164,6 @@ function CloudCmdProto(Util, DOM) { exec.if(document.body.scrollIntoViewIfNeeded, func, funcBefore); }; - function loadStyle(callback) { - const prefix = CloudCmd.PREFIX; - const name = prefix + '/dist/cloudcmd.common.css'; - - DOM.load.css(name, callback); - } - function loadPlugins(callback) { const prefix = CloudCmd.PREFIX; const plugins = prefix + '/plugins.js'; @@ -263,7 +203,6 @@ function CloudCmdProto(Util, DOM) { } DOM.setCurrentFile(current); - CloudCmd.execFromModule(module, 'show'); }; @@ -303,14 +242,10 @@ function CloudCmdProto(Util, DOM) { }; const load = (name, path, dobefore) => { - const isTmpl = path === 'template'; - const funcName = isTmpl ? 'get' : 'show'; - loadModule({ name, path, dobefore, - funcName, }); }; @@ -372,16 +307,11 @@ function CloudCmdProto(Util, DOM) { ]; } - this.execFromModule = (moduleName, funcName, ...args) => { - const obj = CloudCmd[moduleName]; - const isObj = itype.object(obj); + this.execFromModule = async (moduleName, funcName, ...args) => { + await CloudCmd[moduleName](); - exec.if(isObj, () => { - const obj = CloudCmd[moduleName]; - const func = obj[funcName]; - - func(...args); - }, obj); + const func = CloudCmd[moduleName][funcName]; + func(...args); }; this.refresh = (options = {}, callback) => { diff --git a/client/load-module.js b/client/load-module.js new file mode 100644 index 00000000..7629f9a2 --- /dev/null +++ b/client/load-module.js @@ -0,0 +1,53 @@ +'use strict'; + +/* global CloudCmd */ + +const exec = require('execon'); +const tryToCatch = require('try-to-catch/legacy'); +const { + kebabToCamelCase, +} = require('../common/util'); + +/** + * function load modules + * @params = {name, path, func, dobefore, arg} + */ +module.exports = function loadModule(params) { + if (!params) + return; + + let path = params.path; + const name = params.name || path && kebabToCamelCase(path); + const doBefore = params.dobefore; + + if (CloudCmd[name]) + return; + + CloudCmd[name] = () => { + exec(doBefore); + return import(`./modules/${path}` /* webpackChunkName: "cloudcmd-" */).then(async (module) => { + const newModule = async (f) => f && f(); + + Object.assign(newModule, module); + CloudCmd[name] = newModule; + + CloudCmd.log('init', name); + await module.init(); + + return newModule; + }); + }; + + CloudCmd[name].show = async (...args) => { + CloudCmd.log('show', name, args); + const m = CloudCmd[name]; + + const [e, a] = await tryToCatch(m); + + if (e) + return console.error(e); + + a.show(...args); + }; +}; + diff --git a/client/modules/cloud.js b/client/modules/cloud.js index 466ba987..6980de8e 100644 --- a/client/modules/cloud.js +++ b/client/modules/cloud.js @@ -2,10 +2,9 @@ 'use strict'; -CloudCmd.Cloud = CloudProto; - const exec = require('execon'); const currify = require('currify/legacy'); +const {promisify} = require('es6-promisify'); const {log} = CloudCmd; @@ -15,11 +14,9 @@ const Images = require('../dom/images'); const upload = currify(_upload); -function CloudProto(callback) { - loadFiles(callback); - - return module.exports; -} +module.exports.init = async () => { + await loadFiles(); +}; module.exports.uploadFile = (filename, data) => { const mimetype = ''; @@ -52,7 +49,7 @@ function _upload(callback, file) { }); } -function loadFiles(callback) { +const loadFiles = promisify((callback) => { const js = '//api.filepicker.io/v2/filepicker.js'; load.js(js, () => { @@ -65,5 +62,5 @@ function loadFiles(callback) { exec(callback); }); }); -} +}); diff --git a/client/modules/config.js b/client/modules/config.js index 459ed8bb..573b6b16 100644 --- a/client/modules/config.js +++ b/client/modules/config.js @@ -7,21 +7,22 @@ require('../../css/config.css'); const rendy = require('rendy/legacy'); const exec = require('execon'); const currify = require('currify/legacy'); +const wraptile = require('wraptile/legacy'); const squad = require('squad/legacy'); -const input = require('../input'); +const {promisify} = require('es6-promisify'); +const input = require('../input'); const Images = require('../dom/images'); const Events = require('../dom/events'); const Files = require('../dom/files'); const {getTitle} = require('../../common/cloudfunc'); - const {Dialog, setTitle} = DOM; const TITLE = 'Config'; const alert = currify(Dialog.alert, TITLE); -const Config = module.exports; +const loadSocket = promisify(DOM.loadSocket); const showLoad = () => { Images.show.load('top'); @@ -37,33 +38,18 @@ const addChange = currify((fn, input) => { return input; }); -CloudCmd.Config = ConfigProto; +const Config = {}; let Loading = true; -function ConfigProto() { - const noop = () => {}; - - if (!CloudCmd.config('configDialog')) - return { - show: noop - }; - - Loading = true; - +module.exports.init = async () => { showLoad(); - exec.series([ - CloudCmd.View, - (callback) => { - Loading = false; - exec(callback); - DOM.loadSocket(initSocket); - }, - show - ]); - return module.exports; -} + await CloudCmd.View(); + await loadSocket(); + initSocket(); + Loading = false; +}; const config = CloudCmd.config; @@ -113,10 +99,7 @@ function initSocket() { function authCheck(socket) { socket.emit('auth', config('username'), config('password')); - - socket.on('reject', () => { - alert('Wrong credentials!'); - }); + socket.on('reject', wraptile(alert, 'Wrong credentials!')); } Config.save = saveHttp; @@ -124,6 +107,9 @@ Config.save = saveHttp; module.exports.show = show; function show() { + if (!CloudCmd.config('configDialog')) + return; + const prefix = CloudCmd.PREFIX; const funcs = [ exec.with(Files.get, 'config-tmpl'), @@ -193,9 +179,11 @@ function fillTemplate(error, template) { }); } -module.exports.hide = () => { +module.exports.hide = hide; + +function hide() { CloudCmd.View.hide(); -}; +} function onChange(el) { const obj = {}; @@ -249,7 +237,7 @@ function onNameChange(name) { function onKey({keyCode, target}) { switch (keyCode) { case Key.ESC: - Config.hide(); + hide(); break; case Key.ENTER: diff --git a/client/modules/edit-file-vim.js b/client/modules/edit-file-vim.js index e153ccb4..dc0493d0 100644 --- a/client/modules/edit-file-vim.js +++ b/client/modules/edit-file-vim.js @@ -2,54 +2,46 @@ /* global CloudCmd */ -const exec = require('execon'); const Events = require('../dom/events'); const {Key} = CloudCmd; -CloudCmd.EditFileVim = function EditFileVimProto(callback) { - const EditFileVim = this; - - const ConfigView = { - bindKeys: false, - beforeClose: () => { - Events.rmKey(listener); - CloudCmd.EditFile.isChanged(); - } - }; - - function init(callback) { - exec.series([ - CloudCmd.EditFile, - callback || EditFileVim.show, - ]); +const ConfigView = { + bindKeys: false, + beforeClose: () => { + Events.rmKey(listener); + CloudCmd.EditFile.isChanged(); } - - this.show = () => { - Events.addKey(listener); - - CloudCmd.EditFile - .show(ConfigView) - .getEditor() - .setKeyMap('vim'); - }; - - this.hide = () => { - CloudCmd.Edit.hide(); - }; - - function listener(event) { - const { - keyCode, - shiftKey, - } = event; - - if (shiftKey && keyCode === Key.ESC) { - event.preventDefault(); - EditFileVim.hide(); - } - } - - init(callback); }; +module.exports.init = async () => { + await CloudCmd.EditFile(); +}; + +module.exports.show = () => { + Events.addKey(listener); + + CloudCmd.EditFile + .show(ConfigView) + .getEditor() + .setKeyMap('vim'); +}; + +module.exports.hide = hide; + +function hide() { + CloudCmd.Edit.hide(); +} + +function listener(event) { + const { + keyCode, + shiftKey, + } = event; + + if (shiftKey && keyCode === Key.ESC) { + event.preventDefault(); + hide(); + } +} + diff --git a/client/modules/edit-file.js b/client/modules/edit-file.js index 1c52e9db..9a6f3083 100644 --- a/client/modules/edit-file.js +++ b/client/modules/edit-file.js @@ -3,204 +3,183 @@ /* global CloudCmd, DOM*/ const Format = require('format-io/legacy'); -const currify = require('currify/legacy'); -const store = require('fullstore/legacy'); -const squad = require('squad/legacy'); const exec = require('execon'); const supermenu = require('supermenu'); -const call = currify((fn, callback) => { - fn(); - callback(); -}); +const Info = DOM.CurrentInfo; +const Dialog = DOM.Dialog; +const config = CloudCmd.config; -CloudCmd.EditFile = function EditFileProto(callback) { - const Info = DOM.CurrentInfo; - const Dialog = DOM.Dialog; - const EditFile = exec.bind(); - const config = CloudCmd.config; +let Menu; + +const TITLE = 'Edit'; +const Images = DOM.Images; + +let MSG_CHANGED; +const ConfigView = { + beforeClose: () => { + exec.ifExist(Menu, 'hide'); + isChanged(); + } +}; + +module.exports.init = async () => { + await CloudCmd.Edit(); - let Menu; + const editor = CloudCmd.Edit.getEditor(); + authCheck(editor); + setListeners(editor); +}; + +function getName() { + const {name, isDir} = Info; - const TITLE = 'Edit'; - const Images = DOM.Images; + if (isDir) + return `${name}.json`; - let MSG_CHANGED; - const ConfigView = { - beforeClose: () => { - exec.ifExist(Menu, 'hide'); - EditFile.isChanged(); - } + return name; +} + +module.exports.show = (options) => { + const config = { + ...ConfigView, + ...options, }; - function init(callback) { - const editor = store(); - - const getMainEditor = () => CloudCmd.Edit.getEditor(); - const getEditor = squad(editor, getMainEditor); - const auth = squad(authCheck, editor); - const listeners = squad(setListeners, editor); - - const show = callback ? exec : EditFile.show; - - exec.series([ - CloudCmd.Edit, - call(getEditor), - call(auth), - call(listeners), - show, - ], callback); - } + Images.show.load(); - function getName() { - const {name, isDir} = Info; - - if (isDir) - return `${name}.json`; - - return name; - } + CloudCmd.Edit + .getEditor() + .setOption('keyMap', 'default'); - EditFile.show = (options) => { - const config = { - ...ConfigView, - ...options, - }; + Info.getData((error, data) => { + const path = Info.path; + const name = getName(); - Images.show.load(); + if (error) + return Images.hide(); + + setMsgChanged(name); CloudCmd.Edit .getEditor() - .setOption('keyMap', 'default'); + .setValueFirst(path, data) + .setModeForPath(name) + .enableKey(); - Info.getData((error, data) => { - const path = Info.path; - const name = getName(); - - if (error) - return Images.hide(); - - setMsgChanged(name); - - CloudCmd.Edit - .getEditor() - .setValueFirst(path, data) - .setModeForPath(name) - .enableKey(); - - CloudCmd.Edit.show(config); - }); - - return CloudCmd.Edit; - }; + CloudCmd.Edit.show(config); + }); - EditFile.hide = () => { - CloudCmd.Edit.hide(); - }; - - function setListeners(editor) { - const element = CloudCmd.Edit.getElement(); - - DOM.Events.addOnce('contextmenu', element, setMenu); - - editor.on('save', (value) => { - DOM.setCurrentSize(Format.size(value)); - }); - } - - function authCheck(spawn) { - spawn.emit('auth', config('username'), config('password')); - spawn.on('reject', () => { - Dialog.alert(TITLE, 'Wrong credentials!'); - }); - } - - function setMenu(event) { - const position = { - x: event.clientX, - y: event.clientY - }; - - event.preventDefault(); - - if (Menu) - return; - - const options = { - beforeShow: (params) => { - params.x -= 18; - params.y -= 27; - }, - - afterClick: () => { - const editor = CloudCmd.Edit.getEditor(); - editor.focus(); - } - }; - - const element = CloudCmd.Edit.getElement(); - - Menu = supermenu(element, options, getMenuData()); - Menu.show(position.x, position.y); - } - - function getMenuData() { - const editor = CloudCmd.Edit.getEditor(); - - return { - 'Save Ctrl+S' : () => { - editor.save(); - }, - 'Go To Line Ctrl+G' : () => { - editor.goToLine(); - }, - 'Cut Ctrl+X' : () => { - editor.cutToClipboard(); - }, - 'Copy Ctrl+C' : () => { - editor.copyToClipboard(); - }, - 'Paste Ctrl+V' : () => { - editor.pasteFromClipboard(); - }, - 'Delete Del' : () => { - editor.remove('right'); - }, - 'Select All Ctrl+A' : () => { - editor.selectAll(); - }, - 'Beautify Ctrl+B' : () => { - editor.beautify(); - }, - 'Minify Ctrl+M' : () => { - editor.minify(); - }, - 'Close Esc' : () => { - EditFile.hide(); - } - }; - } - - function setMsgChanged(name) { - MSG_CHANGED = 'Do you want to save changes to ' + name + '?'; - } - - EditFile.isChanged = () => { - const editor = CloudCmd.Edit.getEditor(); - const is = editor.isChanged(); - - if (!is) - return; - - const cancel = false; - Dialog.confirm(TITLE, MSG_CHANGED, {cancel}) - .then(() => { - editor.save(); - }); - }; - - init(callback); - - return EditFile; + return CloudCmd.Edit; }; +module.exports.hide = hide; + +function hide() { + CloudCmd.Edit.hide(); +} + +function setListeners(editor) { + const element = CloudCmd.Edit.getElement(); + + DOM.Events.addOnce('contextmenu', element, setMenu); + + editor.on('save', (value) => { + DOM.setCurrentSize(Format.size(value)); + }); +} + +function authCheck(spawn) { + spawn.emit('auth', config('username'), config('password')); + spawn.on('reject', () => { + Dialog.alert(TITLE, 'Wrong credentials!'); + }); +} + +function setMenu(event) { + const position = { + x: event.clientX, + y: event.clientY + }; + + event.preventDefault(); + + if (Menu) + return; + + const options = { + beforeShow: (params) => { + params.x -= 18; + params.y -= 27; + }, + + afterClick: () => { + CloudCmd.Edit + .getEditor() + .focus(); + } + }; + + const element = CloudCmd.Edit.getElement(); + + Menu = supermenu(element, options, getMenuData()); + Menu.show(position.x, position.y); +} + +function getMenuData() { + const editor = CloudCmd.Edit.getEditor(); + + return { + 'Save Ctrl+S' : () => { + editor.save(); + }, + 'Go To Line Ctrl+G' : () => { + editor.goToLine(); + }, + 'Cut Ctrl+X' : () => { + editor.cutToClipboard(); + }, + 'Copy Ctrl+C' : () => { + editor.copyToClipboard(); + }, + 'Paste Ctrl+V' : () => { + editor.pasteFromClipboard(); + }, + 'Delete Del' : () => { + editor.remove('right'); + }, + 'Select All Ctrl+A' : () => { + editor.selectAll(); + }, + 'Beautify Ctrl+B' : () => { + editor.beautify(); + }, + 'Minify Ctrl+M' : () => { + editor.minify(); + }, + 'Close Esc' : () => { + hide(); + } + }; +} + +function setMsgChanged(name) { + MSG_CHANGED = 'Do you want to save changes to ' + name + '?'; +} + +module.exports.isChanged = isChanged; + +function isChanged() { + const editor = CloudCmd.Edit.getEditor(); + const is = editor.isChanged(); + + if (!is) + return; + + const cancel = false; + Dialog.confirm(TITLE, MSG_CHANGED, {cancel}) + .then(() => { + editor.save(); + }); +} + diff --git a/client/modules/edit.js b/client/modules/edit.js index 52a6bc19..a7e56ac1 100644 --- a/client/modules/edit.js +++ b/client/modules/edit.js @@ -2,137 +2,122 @@ 'use strict'; -const exec = require('execon'); -const currify = require('currify/legacy'); +const {promisify} = require('es6-promisify'); const load = require('../dom/load'); const {MAX_FILE_SIZE: maxSize} = require('../../common/cloudfunc'); const {time, timeEnd} = require('../../common/util'); -CloudCmd.Edit = EditProto; +const Name = 'Edit'; +const EditorName = CloudCmd.config('editor'); -function EditProto(callback) { - const Name = 'Edit'; - const EditorName = CloudCmd.config('editor'); - const loadFiles = currify(_loadFiles); - - let Loading = true; - let Element; - let editor; - - const ConfigView = { - afterShow: () => { - editor - .moveCursorTo(0, 0) - .focus(); - } - }; - - const Edit = exec.bind(); - - function init(callback) { - const element = createElement(); - - exec.series([ - CloudCmd.View, - loadFiles(element) - ], callback); +let Loading = true; +let Element; +let editor; + +const ConfigView = { + afterShow: () => { + editor + .moveCursorTo(0, 0) + .focus(); } +}; + +module.exports.init = async () => { + const element = createElement(); - function createElement() { - const element = load({ - name: 'div', - style: - 'width : 100%;' + - 'height : 100%;' + - 'font-family: "Droid Sans Mono";' + - 'position : absolute;', - notAppend: true - }); - - Element = element; - - return element; - } + await CloudCmd.View(); + await loadFiles(element); +}; + +function createElement() { + const element = load({ + name: 'div', + style: + 'width : 100%;' + + 'height : 100%;' + + 'font-family: "Droid Sans Mono";' + + 'position : absolute;', + notAppend: true + }); - function checkFn(name, fn) { - if (typeof fn !== 'function') - throw Error(name + ' should be a function!'); - } + Element = element; - function initConfig(options = {}) { - const config = Object.assign({}, options, ConfigView); - - if (!options.afterShow) - return config; - - checkFn('options.afterShow', options.afterShow); - - const afterShow = {config}; - - config.afterShow = () => { - afterShow(); - options.afterShow(); - }; - - return config; - } - - Edit.show = (options) => { - if (Loading) - return; - - CloudCmd.View.show(Element, initConfig(options)); - - Edit.getEditor() - .setOptions({ - fontSize: 16, - }); - - return Edit; - }; - - Edit.getEditor = () => { - return editor; - }; - - Edit.getElement = () => { - return Element; - }; - - Edit.hide = () => { - CloudCmd.View.hide(); - return Edit; - }; - - function _loadFiles(element, callback) { - const socketPath = CloudCmd.PREFIX; - const prefix = socketPath + '/' + EditorName; - const url = prefix + '/' + EditorName + '.js'; - - time(Name + ' load'); - - load.js(url, () => { - const word = window[EditorName]; - const options = { - maxSize, - prefix, - socketPath, - }; - - word(element, options, (ed) => { - timeEnd(Name + ' load'); - editor = ed; - Loading = false; - - exec(callback); - }); - }); - } - - init(callback); - - return Edit; + return element; } +function checkFn(name, fn) { + if (typeof fn !== 'function') + throw Error(name + ' should be a function!'); +} + +function initConfig(options = {}) { + const config = Object.assign({}, options, ConfigView); + + if (!options.afterShow) + return config; + + checkFn('options.afterShow', options.afterShow); + + const afterShow = {config}; + + config.afterShow = () => { + afterShow(); + options.afterShow(); + }; + + return config; +} + +module.exports.show = (options) => { + if (Loading) + return; + + CloudCmd.View.show(Element, initConfig(options)); + + getEditor() + .setOptions({ + fontSize: 16, + }); +}; + +module.exports.getEditor = getEditor; + +function getEditor() { + return editor; +} + +module.exports.getElement = () => { + return Element; +}; + +module.exports.hide = () => { + CloudCmd.View.hide(); +}; + +const loadFiles = promisify((element, callback) => { + const socketPath = CloudCmd.PREFIX; + const prefix = socketPath + '/' + EditorName; + const url = prefix + '/' + EditorName + '.js'; + + time(Name + ' load'); + + load.js(url, () => { + const word = window[EditorName]; + const options = { + maxSize, + prefix, + socketPath, + }; + + word(element, options, (ed) => { + timeEnd(Name + ' load'); + editor = ed; + Loading = false; + + callback(); + }); + }); +}); + diff --git a/client/modules/help.js b/client/modules/help.js index 29de96fa..d783985e 100644 --- a/client/modules/help.js +++ b/client/modules/help.js @@ -2,16 +2,11 @@ /* global CloudCmd */ -CloudCmd.Help = HelpProto; - const Images = require('../dom/images'); -function HelpProto() { +module.exports.init = () => { Images.show.load('top'); - show(); - - return exports; -} +}; module.exports.show = show; module.exports.hide = hide; diff --git a/client/modules/konsole.js b/client/modules/konsole.js index 574d8070..0d34a316 100644 --- a/client/modules/konsole.js +++ b/client/modules/konsole.js @@ -6,6 +6,7 @@ /* global Console */ const exec = require('execon'); +const {promisify} = require('es6-promisify'); const currify = require('currify/legacy'); const Images = require('../dom/images'); const { @@ -15,144 +16,130 @@ const { const rmLastSlash = (a) => a.replace(/\/$/, '') || '/'; -CloudCmd.Konsole = ConsoleProto; +let konsole; +const {config} = CloudCmd; -function ConsoleProto() { - let konsole; - const {config} = CloudCmd; +const cd = currify((fn, dir) => fn(`cd ${rmLastSlash(dir)}`)); + +const Name = 'Konsole'; +const TITLE = 'Console'; + +let Element; +let Loaded; + +module.exports.init = async () => { + Images.show.load('top'); - const noop = () => {}; - const cd = currify((fn, dir) => fn(`cd ${rmLastSlash(dir)}`)); - - if (!config('console')) - return { - show: noop - }; - - const Name = 'Konsole'; - const TITLE = 'Console'; - - let Element; - let Loaded; - - const Konsole = this; - - function init() { - Images.show.load('top'); - - exec.series([ - CloudCmd.View, - load, - create, - Konsole.show, - ]); - - Element = DOM.load({ - name : 'div', - className : 'console' - }); - } - - this.hide = () => { - CloudCmd.View.hide(); - }; - - this.clear = () => { - konsole.clear(); - }; - - function getPrefix() { - return CloudCmd.PREFIX + '/console'; - } - - function getEnv() { - return { - ACTIVE_DIR: DOM.getCurrentDirPath.bind(DOM), - PASSIVE_DIR: DOM.getNotCurrentDirPath.bind(DOM), - CURRENT_NAME: DOM.getCurrentName.bind(DOM), - CURRENT_PATH: () => { - return Info.path; - } - }; - } - - function onPath(path) { - if (Info.dirPath === path) - return; - - CloudCmd.loadDir({ - path, - }); - } - - const getDirPath = () => { - if (config('syncConsolePath')) - return Info.dirPath; - }; - - function create(callback) { - const options = { - cwd: getDirPath(), - env: getEnv(), - prefix: getPrefix(), - socketPath: CloudCmd.PREFIX, - }; - - konsole = Console(Element, options, (spawn) => { - spawn.on('connect', exec.with(authCheck, spawn)); - spawn.on('path', config.if('syncConsolePath', onPath)); - - CloudCmd.on('active-dir', config.if('syncConsolePath', cd(spawn.handler))); - - exec(callback); - }); - - konsole.addShortCuts({ - 'P': () => { - const command = konsole.getPromptText(); - const path = DOM.getCurrentDirPath(); - - konsole.setPromptText(command + path); - } - }); - } - - function authCheck(spawn) { - spawn.emit('auth', config('username'), config('password')); - - spawn.on('reject', () => { - Dialog.alert(TITLE, 'Wrong credentials!'); - }); - } - - this.show = (callback) => { - if (!Loaded) - return; - - CloudCmd.View.show(Element, { - afterShow: () => { - konsole.focus(); - exec(callback); - } - }); - }; - - function load(callback) { - const prefix = getPrefix(); - const url = prefix + '/console.js'; - - DOM.load.js(url, (error) => { - if (error) - return Dialog.alert(TITLE, error.message); - - Loaded = true; - Util.timeEnd(Name + ' load'); - exec(callback); - }); - - Util.time(Name + ' load'); - } - - init(); + await CloudCmd.View(); + await load(); + await create(); +}; + +module.exports.hide = () => { + CloudCmd.View.hide(); +}; + +module.exports.clear = () => { + konsole.clear(); +}; + +function getPrefix() { + return CloudCmd.PREFIX + '/console'; } +function getEnv() { + return { + ACTIVE_DIR: DOM.getCurrentDirPath.bind(DOM), + PASSIVE_DIR: DOM.getNotCurrentDirPath.bind(DOM), + CURRENT_NAME: DOM.getCurrentName.bind(DOM), + CURRENT_PATH: () => { + return Info.path; + } + }; +} + +function onPath(path) { + if (Info.dirPath === path) + return; + + CloudCmd.loadDir({ + path, + }); +} + +const getDirPath = () => { + if (config('syncConsolePath')) + return Info.dirPath; +}; + +const create = promisify((callback) => { + const options = { + cwd: getDirPath(), + env: getEnv(), + prefix: getPrefix(), + socketPath: CloudCmd.PREFIX, + }; + + Element = DOM.load({ + name : 'div', + className : 'console' + }); + + konsole = Console(Element, options, (spawn) => { + spawn.on('connect', exec.with(authCheck, spawn)); + spawn.on('path', config.if('syncConsolePath', onPath)); + + CloudCmd.on('active-dir', config.if('syncConsolePath', cd(spawn.handler))); + + exec(callback); + }); + + konsole.addShortCuts({ + 'P': () => { + const command = konsole.getPromptText(); + const path = DOM.getCurrentDirPath(); + + konsole.setPromptText(command + path); + } + }); +}); + +function authCheck(spawn) { + spawn.emit('auth', config('username'), config('password')); + + spawn.on('reject', () => { + Dialog.alert(TITLE, 'Wrong credentials!'); + }); +} + +module.exports.show = (callback) => { + if (!Loaded) + return; + + if (!config('console')) + return; + + CloudCmd.View.show(Element, { + afterShow: () => { + konsole.focus(); + exec(callback); + } + }); +}; + +const load = promisify((callback) => { + const prefix = getPrefix(); + const url = prefix + '/console.js'; + + DOM.load.js(url, (error) => { + if (error) + return Dialog.alert(TITLE, error.message); + + Loaded = true; + Util.timeEnd(Name + ' load'); + exec(callback); + }); + + Util.time(Name + ' load'); +}); + diff --git a/client/modules/markdown.js b/client/modules/markdown.js index 7d23af50..14f65a45 100644 --- a/client/modules/markdown.js +++ b/client/modules/markdown.js @@ -2,24 +2,14 @@ /*global CloudCmd */ -CloudCmd.Markdown = MarkdownProto; - -const exec = require('execon'); - const Images = require('../dom/images'); const load = require('../dom/load'); const {Markdown} = require('../dom/rest'); -function MarkdownProto(name, options) { +module.exports.init = async () => { Images.show.load('top'); - - exec.series([ - CloudCmd.View, - exec.with(show, name, options), - ]); - - return module.exports; -} + await CloudCmd.View(); +}; module.exports.show = show; diff --git a/client/modules/menu.js b/client/modules/menu.js index b94b3a87..d6f46c57 100644 --- a/client/modules/menu.js +++ b/client/modules/menu.js @@ -2,8 +2,6 @@ 'use strict'; -CloudCmd.Menu = MenuProto; - const exec = require('execon'); const wrap = require('wraptile/legacy'); const supermenu = require('supermenu'); @@ -12,324 +10,320 @@ const {FS} = require('../../common/cloudfunc'); const load = require('../dom/load'); const RESTful = require('../dom/rest'); -function MenuProto(position) { - const config = CloudCmd.config; - const Buffer = DOM.Buffer; - const Info = DOM.CurrentInfo; - const Key = CloudCmd.Key; - const Events = DOM.Events; - const Dialog = DOM.Dialog; - const Images = DOM.Images; - const Menu = this; - const TITLE = 'Cloud Commander'; - const alertNoFiles = wrap(Dialog.alert.noFiles)(TITLE); - const uploadTo = wrap(_uploadTo); +const config = CloudCmd.config; +const Buffer = DOM.Buffer; +const Info = DOM.CurrentInfo; +const Key = CloudCmd.Key; +const Events = DOM.Events; +const Dialog = DOM.Dialog; +const Images = DOM.Images; +const TITLE = 'Cloud Commander'; +const alertNoFiles = wrap(Dialog.alert.noFiles)(TITLE); +const uploadTo = wrap(_uploadTo); + +let MenuShowedName; +let MenuContext; +let MenuContextFile; + +module.exports.ENABLED = false; + +module.exports.init = () => { + const {isAuth, menuDataFile} = getFileMenuData(); - let MenuShowedName; - let MenuContext; - let MenuContextFile; + const NOT_FILE = true; + const fm = DOM.getFM(); + const menuData = getMenuData(isAuth); + const options = getOptions(NOT_FILE); + const optionsFile = getOptions(); - this.ENABLED = false; + MenuContext = supermenu(fm, options, menuData); + MenuContextFile = supermenu(fm, optionsFile, menuDataFile); - function init(position) { - const {isAuth, menuDataFile} = getFileMenuData(); - - const NOT_FILE = true; - const fm = DOM.getFM(); - const menuData = getMenuData(isAuth); - const options = getOptions(NOT_FILE); - const optionsFile = getOptions(); - - MenuContext = supermenu(fm, options, menuData); - MenuContextFile = supermenu(fm, optionsFile, menuDataFile); - - Menu.show(position); - Events.addKey(listener); - } + Events.addKey(listener); +}; + +module.exports.hide = hide; + +function hide() { + MenuContext.hide(); + MenuContextFile.hide(); +} + +module.exports.show = (position) => { + const {x, y} = getPosition(position); - this.hide = () => { - MenuContext.hide(); - MenuContextFile.hide(); - }; + MenuContext.show(x, y); + MenuContextFile.show(x, y); - this.show = (position) => { - const {x, y} = getPosition(position); - - MenuContext.show(x, y); - MenuContextFile.show(x, y); - - Images.hide(); - }; - - function getPosition(position) { - if (position) - return { - x: position.x, - y: position.y, - }; - - return getCurrentPosition(); - } - - function getMenuNameByEl(el) { - if (!el) - return 'context'; - - const name = DOM.getCurrentName(el); - - if (name === '..') - return 'context'; - - return 'contextFile'; - } - - function getOptions(notFile) { - let name, func; - - if (notFile) { - name = 'context'; - func = Key.unsetBind; - } else { - name = 'contextFile'; - } - - const options = { - icon : true, - beforeClose : Key.setBind, - beforeShow : exec.with(beforeShow, func), - beforeClick, - name, - }; - - return options; - } - - function getMenuData(isAuth) { - const menu = { - 'Paste': Buffer.paste, - 'New': { - 'File': DOM.promptNewFile, - 'Directory': DOM.promptNewDir - }, - 'Upload': () => { - CloudCmd.Upload.show(); - }, - 'Upload From Cloud': uploadFromCloud, - '(Un)Select All': DOM.toggleAllSelectedFiles - }; - - if (isAuth) - menu['Log Out'] = CloudCmd.logOut; - - return menu; - } - - function getFileMenuData() { - const isAuth = CloudCmd.config('auth'); - const show = wrap((name) => { - CloudCmd[name].show(); - }); - - const menuBottom = getMenuData(isAuth); - const menuTop = { - 'View': show('View'), - 'Edit': show('EditFile'), - 'Rename': () => { - setTimeout(DOM.renameCurrent, 100); - }, - 'Delete': () => { - CloudCmd.Operation.show('delete'); - }, - 'Pack': () => { - CloudCmd.Operation.show('pack'); - }, - 'Extract': () => { - CloudCmd.Operation.show('extract'); - }, - 'Download': preDownload, - 'Upload To Cloud': uploadTo('Cloud'), - 'Cut': () => { - isCurrent(Buffer.cut, alertNoFiles); - }, - 'Copy': () => { - isCurrent(Buffer.copy, alertNoFiles); - }, - }; - - const menuDataFile = { - ...menuTop, - ...menuBottom, - }; - + Images.hide(); +}; + +function getPosition(position) { + if (position) return { - isAuth, - menuDataFile, + x: position.x, + y: position.y, }; + + return getCurrentPosition(); +} + +function getMenuNameByEl(el) { + if (!el) + return 'context'; + + const name = DOM.getCurrentName(el); + + if (name === '..') + return 'context'; + + return 'contextFile'; +} + +function getOptions(notFile) { + let name, func; + + if (notFile) { + name = 'context'; + func = Key.unsetBind; + } else { + name = 'contextFile'; } - function isCurrent(yesFn, noFn) { - if (Info.name !== '..') - return yesFn(); - - noFn(); + const options = { + icon : true, + beforeClose : Key.setBind, + beforeShow : exec.with(beforeShow, func), + beforeClick, + name, + }; + + return options; +} + +function getMenuData(isAuth) { + const menu = { + 'Paste': Buffer.paste, + 'New': { + 'File': DOM.promptNewFile, + 'Directory': DOM.promptNewDir + }, + 'Upload': () => { + CloudCmd.Upload.show(); + }, + 'Upload From Cloud': uploadFromCloud, + '(Un)Select All': DOM.toggleAllSelectedFiles + }; + + if (isAuth) + menu['Log Out'] = CloudCmd.logOut; + + return menu; +} + +function getFileMenuData() { + const isAuth = CloudCmd.config('auth'); + const show = wrap((name) => { + CloudCmd[name].show(); + }); + + const menuBottom = getMenuData(isAuth); + const menuTop = { + 'View': show('View'), + 'Edit': show('EditFile'), + 'Rename': () => { + setTimeout(DOM.renameCurrent, 100); + }, + 'Delete': () => { + CloudCmd.Operation.show('delete'); + }, + 'Pack': () => { + CloudCmd.Operation.show('pack'); + }, + 'Extract': () => { + CloudCmd.Operation.show('extract'); + }, + 'Download': preDownload, + 'Upload To Cloud': uploadTo('Cloud'), + 'Cut': () => { + isCurrent(Buffer.cut, alertNoFiles); + }, + 'Copy': () => { + isCurrent(Buffer.copy, alertNoFiles); + }, + }; + + const menuDataFile = { + ...menuTop, + ...menuBottom, + }; + + return { + isAuth, + menuDataFile, + }; +} + +function isCurrent(yesFn, noFn) { + if (Info.name !== '..') + return yesFn(); + + noFn(); +} + +function isPath(x, y) { + const {panel} = Info; + const isEmptyRoot = !panel; + + if (isEmptyRoot) + return false; + + const el = document.elementFromPoint(x, y); + const elements = panel.querySelectorAll('[data-name="js-path"] *'); + const is = ~[].indexOf.call(elements, el); + + return is; +} + +function beforeShow(callback, params) { + const name = params.name; + let el = DOM.getCurrentByPosition({ + x: params.x, + y: params.y + }); + + const menuName = getMenuNameByEl(el); + let notShow = menuName === 'contextFile'; + + if (params.name === 'contextFile') { + notShow = !notShow; } - function isPath(x, y) { - const {panel} = Info; - const isEmptyRoot = !panel; - - if (isEmptyRoot) - return false; - - const el = document.elementFromPoint(x, y); - const elements = panel.querySelectorAll('[data-name="js-path"] *'); - const is = ~[].indexOf.call(elements, el); - - return is; - } + if (!notShow) + MenuShowedName = name; - function beforeShow(callback, params) { - const name = params.name; - let el = DOM.getCurrentByPosition({ - x: params.x, - y: params.y - }); - - const menuName = getMenuNameByEl(el); - let notShow = menuName === 'contextFile'; - - if (params.name === 'contextFile') { - notShow = !notShow; - } - - if (!notShow) - MenuShowedName = name; - - exec(callback); - - if (!notShow) - notShow = isPath(params.x, params.y); - - return notShow; - } + exec(callback); - function beforeClick(name) { - return MenuShowedName !== name; - } + if (!notShow) + notShow = isPath(params.x, params.y); - function _uploadTo(nameModule) { - Info.getData((error, data) => { - if (error) - return; - - const name = Info.name; - const execFrom = CloudCmd.execFromModule; - - execFrom(nameModule, 'uploadFile', name, data); - }); - - CloudCmd.log('Uploading to ' + name + '...'); - } - - function uploadFromCloud() { - Images.show.load('top'); - - CloudCmd.execFromModule('Cloud', 'saveFile', (currentName, data) => { - const path = DOM.getCurrentDirPath() + currentName; - - RESTful.write(path, data, (error) => { - if (error) - return; - - CloudCmd.refresh({currentName}); - }); - }); - } - - function preDownload() { - download(config('packer')); - } - - function download(type) { - const TIME = 30 * 1000; - const prefixUr = CloudCmd.PREFIX_URL; - const PACK = '/pack'; - const date = Date.now(); - const files = DOM.getActiveFiles(); - - if (!files.length) - return alertNoFiles(); - - files.forEach((file) => { - const selected = DOM.isSelected(file); - const isDir = DOM.isCurrentIsDir(file); - const path = DOM.getCurrentPath(file); - - CloudCmd.log('downloading file ' + path + '...'); - /* - * if we send ajax request - - * no need in hash so we escape # - * and all other characters, like "%" - */ - const encodedPath = encodeURI(path).replace(/#/g, '%23'); - const id = load.getIdBySrc(path); - - let src; - - if (isDir) - src = prefixUr + PACK + encodedPath + DOM.getPackerExt(type); - else - src = prefixUr + FS + encodedPath + '?download'; - - const element = load({ - id : id + '-' + date, - name : 'iframe', - async : false, - className : 'hidden', - src, - }); - - const {body} = document; - const removeChild = body.removeChild.bind(body, element); - - setTimeout(removeChild, TIME); - - if (selected) - DOM.toggleSelectedFile(file); - }); - } - - function getCurrentPosition() { - const current = Info.element; - const rect = current.getBoundingClientRect(); - - const position = { - x: Math.round(rect.left + rect.width / 3), - y: Math.round(rect.top) - }; - - return position; - } - - function listener(event) { - const F9 = Key.F9; - const ESC = Key.ESC; - const key = event.keyCode; - const isBind = Key.isBind(); - - if (!isBind) + return notShow; +} + +function beforeClick(name) { + return MenuShowedName !== name; +} + +function _uploadTo(nameModule) { + Info.getData((error, data) => { + if (error) return; - if (key === ESC) - return Menu.hide(); - - if (key === F9) { - const position = getCurrentPosition(); - MenuContext.show(position.x, position.y); - - event.preventDefault(); - } - } + const name = Info.name; + const execFrom = CloudCmd.execFromModule; + + execFrom(nameModule, 'uploadFile', name, data); + }); - init(position); + CloudCmd.log('Uploading to ' + name + '...'); +} + +function uploadFromCloud() { + Images.show.load('top'); + + CloudCmd.execFromModule('Cloud', 'saveFile', (currentName, data) => { + const path = DOM.getCurrentDirPath() + currentName; + + RESTful.write(path, data, (error) => { + if (error) + return; + + CloudCmd.refresh({currentName}); + }); + }); +} + +function preDownload() { + download(config('packer')); +} + +function download(type) { + const TIME = 30 * 1000; + const prefixUr = CloudCmd.PREFIX_URL; + const PACK = '/pack'; + const date = Date.now(); + const files = DOM.getActiveFiles(); + + if (!files.length) + return alertNoFiles(); + + files.forEach((file) => { + const selected = DOM.isSelected(file); + const isDir = DOM.isCurrentIsDir(file); + const path = DOM.getCurrentPath(file); + + CloudCmd.log('downloading file ' + path + '...'); + /* + * if we send ajax request - + * no need in hash so we escape # + * and all other characters, like "%" + */ + const encodedPath = encodeURI(path).replace(/#/g, '%23'); + const id = load.getIdBySrc(path); + + let src; + + if (isDir) + src = prefixUr + PACK + encodedPath + DOM.getPackerExt(type); + else + src = prefixUr + FS + encodedPath + '?download'; + + const element = load({ + id : id + '-' + date, + name : 'iframe', + async : false, + className : 'hidden', + src, + }); + + const {body} = document; + const removeChild = body.removeChild.bind(body, element); + + setTimeout(removeChild, TIME); + + if (selected) + DOM.toggleSelectedFile(file); + }); +} + +function getCurrentPosition() { + const current = Info.element; + const rect = current.getBoundingClientRect(); + + const position = { + x: Math.round(rect.left + rect.width / 3), + y: Math.round(rect.top) + }; + + return position; +} + +function listener(event) { + const F9 = Key.F9; + const ESC = Key.ESC; + const key = event.keyCode; + const isBind = Key.isBind(); + + if (!isBind) + return; + + if (key === ESC) + return hide(); + + if (key === F9) { + const position = getCurrentPosition(); + MenuContext.show(position.x, position.y); + + event.preventDefault(); + } } diff --git a/client/modules/operation/get-next-current-name.spec.js b/client/modules/operation/get-next-current-name.spec.js deleted file mode 100644 index 514ecc29..00000000 --- a/client/modules/operation/get-next-current-name.spec.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const test = require('tape'); -const getNextCurrentName = require('./get-next-current-name'); - -test('get-next-current-name', (t) => { - const names = [ - '..', - 'hello', - '1', - '2', - ]; - - const removedNames = [ - '1' - ]; - - const name = getNextCurrentName('hello', names, removedNames); - - t.equal(name, 'hello', 'should equal'); - t.end(); -}); - -test('get-next-current-name: all files removed', (t) => { - const names = [ - '..', - 'hello', - '1', - '2', - ]; - - const removedNames = [ - '1', - '2', - 'hello', - ]; - - const name = getNextCurrentName('2', names, removedNames); - - t.equal(name, '..', 'should equal'); - t.end(); -}); - diff --git a/client/modules/operation/index.js b/client/modules/operation/index.js index c577ab8c..4aa315d8 100644 --- a/client/modules/operation/index.js +++ b/client/modules/operation/index.js @@ -5,10 +5,10 @@ 'use strict'; -CloudCmd.Operation = OperationProto; const currify = require('currify/legacy'); const wraptile = require('wraptile/legacy'); +const {promisify} = require('es6-promisify'); const exec = require('execon'); const { @@ -22,455 +22,453 @@ const getNextCurrentName = require('./get-next-current-name'); const removeQuery = (a) => a.replace(/\?.*/, ''); -function OperationProto(operation, data) { - const Name = 'Operation'; - const { - TITLE, - config, - } = CloudCmd; - const {Dialog, Images} = DOM; - const initOperations = wraptile(_initOperations); - const authCheck = wraptile(_authCheck); +const Name = 'Operation'; +const { + TITLE, + config, +} = CloudCmd; +const {Dialog, Images} = DOM; +const initOperations = wraptile(_initOperations); +const authCheck = wraptile(_authCheck); + +const Operation = {}; + +let Loaded; + +let { + cp: copyFn, + mv: moveFn, + delete: deleteFn, + extract: extractFn, +} = RESTful; + +let packZipFn = RESTful.pack; +let packTarFn = RESTful.pack; + +const Info = DOM.CurrentInfo; +const showLoad = Images.show.load.bind(null, 'top'); + +const processFiles = currify(_processFiles); + +const noFilesCheck = () => { + const {length} = DOM.getActiveFiles(); + const is = Boolean(!length); - let Loaded; + if (is) + return Dialog.alert.noFiles(TITLE); - let { - cp: copyFn, - mv: moveFn, - delete: deleteFn, - extract: extractFn, - } = RESTful; + return is; +}; - let packZipFn = RESTful.pack; - let packTarFn = RESTful.pack; +module.exports.init = promisify((callback) => { + showLoad(); - const Info = DOM.CurrentInfo; - const showLoad = Images.show.load.bind(null, 'top'); - const Operation = this; - const processFiles = currify(_processFiles); - - const noFilesCheck = () => { - const {length} = DOM.getActiveFiles(); - const is = Boolean(!length); - - if (is) - return Dialog.alert.noFiles(TITLE); - - return is; - }; - - function init() { - showLoad(); - - exec.series([ - DOM.loadSocket, - (callback) => { - if (!config('progress')) - return callback(); - - load(initOperations(CloudCmd.PREFIX, callback)); - }, - () => { - Loaded = true; - Images.hide(); - Operation.show(operation, data); - } - ]); - } - - function _authCheck(spawn, ok) { - const accept = wraptile(ok); - const alertDialog = wraptile(Dialog.alert); - - spawn.on('accept', accept(spawn)); - spawn.on('reject', alertDialog (TITLE, 'Wrong credentials!')); - spawn.emit('auth', config('username'), config('password')); - } - - function _initOperations(socketPrefix, fn) { - const prefix = `${socketPrefix}/fileop`; - fileop({prefix, socketPrefix}, (e, operator) => { - fn(); - - operator.on('connect', authCheck(operator, onConnect)); - operator.on('disconnect', onDisconnect); - }); - } - - function onConnect(operator) { - packTarFn = (data, callback) => { - operator.tar(data.from, data.to, data.names) - .then(setListeners({noContinue: true}, callback)); - }; - - packZipFn = (data, callback) => { - operator.zip(data.from, data.to, data.names) - .then(setListeners({noContinue: true}, callback)); - }; - - deleteFn = (from, files, callback) => { - from = removeQuery(from); - operator.remove(from, files) - .then(setListeners(callback)); - }; - - copyFn = (data, callback) => { - operator.copy(data.from, data.to, data.names) - .then(setListeners(callback)); - }; - - moveFn = (data, callback) => { - operator.move(data.from, data.to, data.names) - .then(setListeners(callback)); - }; - - extractFn = (data, callback) => { - operator.extract(data.from, data.to) - .then(setListeners({noContinue: true}, callback)); - }; - } - - function onDisconnect() { - packZipFn = RESTful.pack; - packTarFn = RESTful.pack; - deleteFn = RESTful.delete; - copyFn = RESTful.cp; - moveFn = RESTful.mv; - extractFn = RESTful.extract; - } - - function getPacker(type) { - if (type === 'zip') - return packZipFn; - - return packTarFn; - } - - this.hide = () => { - CloudCmd.View.hide(); - }; - - this.show = (operation, data) => { - if (!Loaded) - return; - - if (operation === 'copy') - return Operation.copy(data); - - if (operation === 'move') - return Operation.move(data); - - if (operation === 'delete') - return Operation.delete(); - - if (operation === 'delete:silent') - return Operation.deleteSilent(); - - if (operation === 'pack') - return Operation.pack(); - - if (operation === 'extract') - return Operation.extract(); - }; - - this.copy = processFiles({ - type: 'copy', - }); - - this.move = processFiles({ - type: 'move' - }); - - this.delete = () => { - promptDelete(); - }; - - this.deleteSilent = () => { - deleteSilent(); - }; - - this.pack = () => { - const isZip = config('packer') === 'zip'; - twopack('pack', isZip ? 'zip' : 'tar'); - }; - - this.extract = () => { - twopack('extract'); - }; - - /** - * prompt and delete current file or selected files - * - * @currentFile - */ - function promptDelete() { - if (noFilesCheck()) - return; - - const msgAsk = 'Do you really want to delete the '; - const msgSel = 'selected '; - - const files = DOM.getActiveFiles(); - const names = DOM.getFilenames(files); - const n = names.length; - - let msg; - if (n) { - let name = ''; - - for (let i = 0; i < 5 && i < n; i++) - name += '\n' + names[i]; - - if (n >= 5) - name += '\n...'; - - msg = msgAsk + msgSel + n + ' files/directories?\n' + encode(name); - } else { - const current = DOM.getCurrentFile(); - const isDir = DOM.isCurrentIsDir(current); - const getType = (isDir) => { - return isDir ? 'directory' : 'file'; - }; - - const type = getType(isDir) + ' '; - - const name = DOM.getCurrentName(current); - msg = msgAsk + msgSel + type + name + '?'; - } - - const cancel = false; - - Dialog.confirm(TITLE, msg, {cancel}).then(() => { - deleteSilent(files); - }); - } - - /** - * delete current or selected files - * - * @files - */ - function deleteSilent(files = DOM.getActiveFiles()) { - const query = '?files'; - const path = Info.dirPath; - - if (noFilesCheck()) - return; - - showLoad(); - - const removedNames = DOM.getFilenames(files); - const names = DOM.CurrentInfo.files.map(DOM.getCurrentName); - const prevCurrent = DOM.getCurrentName(); - const currentName = getNextCurrentName(prevCurrent, names, removedNames); - - deleteFn(path + query, removedNames, (e) => { - const Storage = DOM.Storage; - const dirPath = Info.dirPath; - - !e && CloudCmd.refresh({ - currentName - }); - - Storage.removeMatch(dirPath); - }); - } - - /* - * process files (copy or move) - * @param data - * @param operation - */ - function _processFiles(options, data) { - let selFiles, files; - let panel; - let shouldAsk; - let sameName; - let ok; - - let from = ''; - let to = ''; - - let names = []; - - if (data) { - from = data.from; - to = data.to; - names = data.names; - panel = Info.panel; - } else { - from = Info.dirPath; - to = DOM.getNotCurrentDirPath(); - selFiles = DOM.getSelectedFiles(); - names = DOM.getFilenames(selFiles); - data = {}; - shouldAsk = true; - panel = Info.panelPassive; - } - - if (!names.length) - names.push(DOM.getCurrentName()); - - const name = names[0]; - - sameName = DOM.getCurrentByName(name, panel); - - if (!data && noFilesCheck()) - return; - - const {type} = options; - - const isCopy = type === 'copy'; - const option = isCopy ? 'confirmCopy' : 'confirmMove'; - const title = isCopy ? 'Copy' : 'Rename/Move'; - const operation = isCopy ? copyFn : moveFn; - - if (shouldAsk && config(option)) - return message(title, to, names.map(encode)) - .then(ask); - - ask(to); - - function ask(to) { - ok = from !== to && to; - - if (ok && !shouldAsk || !sameName) - return go(); - - const str = `"${ name }" already exist. Overwrite?`; - const cancel = false; - - Dialog.confirm(TITLE, str, {cancel}).then(go); - - function go() { - showLoad(); - - files = { - from, - to, - names, - }; - - operation(files, () => { - DOM.Storage.remove(from, () => { - const { - panel, - panelPassive, - } = Info; - - const setCurrent = () => { - const currentName = name || data.names[0]; - DOM.setCurrentByName(currentName); - }; - - if (!Info.isOnePanel) - CloudCmd.refresh({ - panel: panelPassive, - noCurrent: true, - }); - - CloudCmd.refresh({panel}, setCurrent); - }); - }); - } - } - } - - function checkEmpty(name, operation) { - if (!operation) - throw Error(name + ' could not be empty!'); - } - - function twopack(operation, type) { - let op; - let fileFrom; - let currentName = Info.name; - - const Images = DOM.Images; - const path = Info.path; - const dirPath = Info.dirPath; - const activeFiles = DOM.getActiveFiles(); - const names = DOM.getFilenames(activeFiles); - - checkEmpty('operation', operation); - - if (!names.length) - return Dialog.alert.noFiles(TITLE); - - switch(operation) { - case 'extract': - op = extractFn; - - fileFrom = { - from: path, - to: dirPath - }; - - currentName = removeExtension(currentName); - - break; - - case 'pack': - op = getPacker(type); - - if (names.length > 1) - currentName = Info.dir; - - currentName += DOM.getPackerExt(type); - - fileFrom = { - from: dirPath, - to: dirPath + currentName, - names, - }; - break; - } - - Images.show.load('top'); - - op(fileFrom, (error) => { - !error && CloudCmd.refresh({ - currentName - }); - }); - } - - function message(msg, to, names) { - const n = names.length; - const name = names[0]; - - msg += ' '; - - if (names.length > 1) - msg += n + ' file(s)'; - else - msg += '"' + name + '"'; - - msg += ' to'; - - const cancel = false; - - return Dialog.prompt(TITLE, msg, to, {cancel}); - } - - function load(callback) { - const prefix = CloudCmd.PREFIX; - const file = `${prefix}/fileop/fileop.js`; - - DOM.load.js(file, (error) => { - if (error) { - Dialog.alert(TITLE, error.message); - return exec(callback); - } + exec.series([ + DOM.loadSocket, + (callback) => { + if (!config('progress')) + return callback(); + load(initOperations(CloudCmd.PREFIX, callback)); + }, + (callback) => { Loaded = true; - Util.timeEnd(Name + ' load'); - exec(callback); - }); - - Util.time(Name + ' load'); - } + Images.hide(); + callback(); + } + ], callback); +}); + +function _authCheck(spawn, ok) { + const accept = wraptile(ok); + const alertDialog = wraptile(Dialog.alert); - init(); + spawn.on('accept', accept(spawn)); + spawn.on('reject', alertDialog (TITLE, 'Wrong credentials!')); + spawn.emit('auth', config('username'), config('password')); +} + +function _initOperations(socketPrefix, fn) { + const prefix = `${socketPrefix}/fileop`; + fileop({prefix, socketPrefix}, (e, operator) => { + fn(); + + operator.on('connect', authCheck(operator, onConnect)); + operator.on('disconnect', onDisconnect); + }); +} + +function onConnect(operator) { + packTarFn = (data, callback) => { + operator.tar(data.from, data.to, data.names) + .then(setListeners({noContinue: true}, callback)); + }; + + packZipFn = (data, callback) => { + operator.zip(data.from, data.to, data.names) + .then(setListeners({noContinue: true}, callback)); + }; + + deleteFn = (from, files, callback) => { + from = removeQuery(from); + operator.remove(from, files) + .then(setListeners(callback)); + }; + + copyFn = (data, callback) => { + operator.copy(data.from, data.to, data.names) + .then(setListeners(callback)); + }; + + moveFn = (data, callback) => { + operator.move(data.from, data.to, data.names) + .then(setListeners(callback)); + }; + + extractFn = (data, callback) => { + operator.extract(data.from, data.to) + .then(setListeners({noContinue: true}, callback)); + }; +} + +function onDisconnect() { + packZipFn = RESTful.pack; + packTarFn = RESTful.pack; + deleteFn = RESTful.delete; + copyFn = RESTful.cp; + moveFn = RESTful.mv; + extractFn = RESTful.extract; +} + +function getPacker(type) { + if (type === 'zip') + return packZipFn; + + return packTarFn; +} + +module.exports.hide = () => { + CloudCmd.View.hide(); +}; + +module.exports.show = (operation, data) => { + if (!Loaded) + return; + + if (operation === 'copy') + return Operation.copy(data); + + if (operation === 'move') + return Operation.move(data); + + if (operation === 'delete') + return Operation.delete(); + + if (operation === 'delete:silent') + return Operation.deleteSilent(); + + if (operation === 'pack') + return Operation.pack(); + + if (operation === 'extract') + return Operation.extract(); +}; + +Operation.copy = processFiles({ + type: 'copy', +}); + +Operation.move = processFiles({ + type: 'move' +}); + +Operation.delete = () => { + promptDelete(); +}; + +Operation.deleteSilent = () => { + deleteSilent(); +}; + +Operation.pack = () => { + const isZip = config('packer') === 'zip'; + twopack('pack', isZip ? 'zip' : 'tar'); +}; + +Operation.extract = () => { + twopack('extract'); +}; + +/** + * prompt and delete current file or selected files + * + * @currentFile + */ +function promptDelete() { + if (noFilesCheck()) + return; + + const msgAsk = 'Do you really want to delete the '; + const msgSel = 'selected '; + + const files = DOM.getActiveFiles(); + const names = DOM.getFilenames(files); + const n = names.length; + + let msg; + if (n) { + let name = ''; + + for (let i = 0; i < 5 && i < n; i++) + name += '\n' + names[i]; + + if (n >= 5) + name += '\n...'; + + msg = msgAsk + msgSel + n + ' files/directories?\n' + encode(name); + } else { + const current = DOM.getCurrentFile(); + const isDir = DOM.isCurrentIsDir(current); + const getType = (isDir) => { + return isDir ? 'directory' : 'file'; + }; + + const type = getType(isDir) + ' '; + + const name = DOM.getCurrentName(current); + msg = msgAsk + msgSel + type + name + '?'; + } + + const cancel = false; + + Dialog.confirm(TITLE, msg, {cancel}).then(() => { + deleteSilent(files); + }); +} + +/** + * delete current or selected files + * + * @files + */ +function deleteSilent(files = DOM.getActiveFiles()) { + const query = '?files'; + const path = Info.dirPath; + + if (noFilesCheck()) + return; + + showLoad(); + + const removedNames = DOM.getFilenames(files); + const names = DOM.CurrentInfo.files.map(DOM.getCurrentName); + const prevCurrent = DOM.getCurrentName(); + const currentName = getNextCurrentName(prevCurrent, names, removedNames); + + deleteFn(path + query, removedNames, (e) => { + const Storage = DOM.Storage; + const dirPath = Info.dirPath; + + !e && CloudCmd.refresh({ + currentName + }); + + Storage.removeMatch(dirPath); + }); +} + +/* + * process files (copy or move) + * @param data + * @param operation + */ +function _processFiles(options, data) { + let selFiles, files; + let panel; + let shouldAsk; + let sameName; + let ok; + + let from = ''; + let to = ''; + + let names = []; + + if (data) { + from = data.from; + to = data.to; + names = data.names; + panel = Info.panel; + } else { + from = Info.dirPath; + to = DOM.getNotCurrentDirPath(); + selFiles = DOM.getSelectedFiles(); + names = DOM.getFilenames(selFiles); + data = {}; + shouldAsk = true; + panel = Info.panelPassive; + } + + if (!names.length) + names.push(DOM.getCurrentName()); + + const name = names[0]; + + sameName = DOM.getCurrentByName(name, panel); + + if (!data && noFilesCheck()) + return; + + const {type} = options; + + const isCopy = type === 'copy'; + const option = isCopy ? 'confirmCopy' : 'confirmMove'; + const title = isCopy ? 'Copy' : 'Rename/Move'; + const operation = isCopy ? copyFn : moveFn; + + if (shouldAsk && config(option)) + return message(title, to, names.map(encode)) + .then(ask); + + ask(to); + + function ask(to) { + ok = from !== to && to; + + if (ok && !shouldAsk || !sameName) + return go(); + + const str = `"${ name }" already exist. Overwrite?`; + const cancel = false; + + Dialog.confirm(TITLE, str, {cancel}).then(go); + + function go() { + showLoad(); + + files = { + from, + to, + names, + }; + + operation(files, () => { + DOM.Storage.remove(from, () => { + const { + panel, + panelPassive, + } = Info; + + const setCurrent = () => { + const currentName = name || data.names[0]; + DOM.setCurrentByName(currentName); + }; + + if (!Info.isOnePanel) + CloudCmd.refresh({ + panel: panelPassive, + noCurrent: true, + }); + + CloudCmd.refresh({panel}, setCurrent); + }); + }); + } + } +} + +function checkEmpty(name, operation) { + if (!operation) + throw Error(name + ' could not be empty!'); +} + +function twopack(operation, type) { + let op; + let fileFrom; + let currentName = Info.name; + + const Images = DOM.Images; + const path = Info.path; + const dirPath = Info.dirPath; + const activeFiles = DOM.getActiveFiles(); + const names = DOM.getFilenames(activeFiles); + + checkEmpty('operation', operation); + + if (!names.length) + return Dialog.alert.noFiles(TITLE); + + switch(operation) { + case 'extract': + op = extractFn; + + fileFrom = { + from: path, + to: dirPath + }; + + currentName = removeExtension(currentName); + + break; + + case 'pack': + op = getPacker(type); + + if (names.length > 1) + currentName = Info.dir; + + currentName += DOM.getPackerExt(type); + + fileFrom = { + from: dirPath, + to: dirPath + currentName, + names, + }; + break; + } + + Images.show.load('top'); + + op(fileFrom, (error) => { + !error && CloudCmd.refresh({ + currentName + }); + }); +} + +function message(msg, to, names) { + const n = names.length; + const name = names[0]; + + msg += ' '; + + if (names.length > 1) + msg += n + ' file(s)'; + else + msg += '"' + name + '"'; + + msg += ' to'; + + const cancel = false; + + return Dialog.prompt(TITLE, msg, to, {cancel}); +} + +function load(callback) { + const prefix = CloudCmd.PREFIX; + const file = `${prefix}/fileop/fileop.js`; + + DOM.load.js(file, (error) => { + if (error) { + Dialog.alert(TITLE, error.message); + return exec(callback); + } + + Loaded = true; + Util.timeEnd(Name + ' load'); + exec(callback); + }); + + Util.time(Name + ' load'); } diff --git a/client/modules/terminal.js b/client/modules/terminal.js index 574f18f6..131a94f4 100644 --- a/client/modules/terminal.js +++ b/client/modules/terminal.js @@ -2,18 +2,18 @@ /* global CloudCmd, gritty */ +const {promisify} = require('es6-promisify'); + require('../../css/terminal.css'); const exec = require('execon'); const load = require('../dom/load'); const DOM = require('../dom'); const Images = require('../dom/images'); -const {Dialog} = DOM; const TITLE = 'Terminal'; -CloudCmd.Terminal = TerminalProto; - +const {Dialog} = DOM; const {Key} = CloudCmd; let Element; @@ -22,30 +22,26 @@ let Terminal; const {config} = CloudCmd; -function TerminalProto() { - const noop = () => {}; - - if (!config('terminal')) - return { - show: noop - }; +const loadAll = promisify((callback) => { + const prefix = getPrefix(); + const url = prefix + '/gritty.js'; + DOM.load.js(url, (error) => { + if (error) + return Dialog.alert(TITLE, error.message); + + Loaded = true; + exec(callback); + }); +}); + +module.exports.init = async () => { Images.show.load('top'); - exec.series([ - CloudCmd.View, - loadAll, - create, - show, - ]); - - Element = load({ - name: 'div', - className : 'terminal', - }); - - return module.exports; -} + await CloudCmd.View(); + await loadAll(); + await create(); +}; module.exports.show = show; module.exports.hide = hide; @@ -67,7 +63,12 @@ function getEnv() { }; } -function create(callback) { +function create() { + Element = load({ + name: 'div', + className : 'terminal', + }); + const options = { env: getEnv(), prefix: getPrefix(), @@ -86,12 +87,11 @@ function create(callback) { }); socket.on('connect', exec.with(authCheck, socket)); - exec(callback); } function authCheck(spawn) { spawn.emit('auth', config('username'), config('password')); - + spawn.on('reject', () => { Dialog.alert(TITLE, 'Wrong credentials!'); }); @@ -101,6 +101,9 @@ function show(callback) { if (!Loaded) return; + if (!config('terminal')) + return; + CloudCmd.View.show(Element, { afterShow: () => { if (Terminal) @@ -111,16 +114,3 @@ function show(callback) { }); } -function loadAll(callback) { - const prefix = getPrefix(); - const url = prefix + '/gritty.js'; - - DOM.load.js(url, (error) => { - if (error) - return Dialog.alert(TITLE, error.message); - - Loaded = true; - exec(callback); - }); -} - diff --git a/client/modules/upload.js b/client/modules/upload.js index 0337e47f..6daab2e8 100644 --- a/client/modules/upload.js +++ b/client/modules/upload.js @@ -2,25 +2,15 @@ 'use strict'; -const exec = require('execon'); - const load = require('../dom/load'); const Files = require('../dom/files'); const Images = require('../dom/images'); const uploadFiles = require('../dom/upload-files'); -CloudCmd.Upload = UploadProto; - -function UploadProto() { +module.exports.init = async () => { Images.show.load('top'); - - exec.series([ - CloudCmd.View, - show - ]); - - return exports; -} + await CloudCmd.View(); +}; module.exports.show = show; module.exports.hide = hide; diff --git a/client/modules/view.js b/client/modules/view.js index befc0419..571a4f3f 100644 --- a/client/modules/view.js +++ b/client/modules/view.js @@ -8,6 +8,7 @@ const itype = require('itype/legacy'); const rendy = require('rendy/legacy'); const exec = require('execon'); const currify = require('currify/legacy'); +const {promisify} = require('es6-promisify'); const {time} = require('../../common/util'); const {FS} = require('../../common/cloudfunc'); @@ -23,10 +24,7 @@ const lifo = currify((fn, el, cb, name) => fn(name, el, cb)); const addEvent = lifo(Events.add); const getRegExp = (ext) => RegExp(`\\.${ext}$`, 'i'); -CloudCmd.View = ViewProto; - -module.exports = exec.bind(); - +//module.exports = exec.bind(); module.exports.show = show; module.exports.hide = hide; @@ -77,9 +75,7 @@ const Config = { } }; -function ViewProto(callback) { - const func = callback || exec.with(show, null); - +module.exports.init = promisify((fn) => { Loading = true; exec.series([ @@ -87,9 +83,9 @@ function ViewProto(callback) { loadAll, (callback) => { Loading = false; - exec(callback); + callback(); } - ], func); + ], fn); Config.parent = Overlay = load({ id : 'js-view', @@ -103,9 +99,7 @@ function ViewProto(callback) { ]; events.forEach(addEvent(Overlay, onOverLayClick)); - - return module.exports; -} +}); function show(data, options) { const prefixUrl = CloudCmd.PREFIX_URL + FS; diff --git a/client/sw/register.js b/client/sw/register.js index 73019c30..18e66a2c 100644 --- a/client/sw/register.js +++ b/client/sw/register.js @@ -5,6 +5,7 @@ module.exports.unregisterSW = unregisterSW; async function registerSW(prefix) { prefix = prefix ? `/${prefix}/` : `/`; + if (!navigator.serviceWorker) return; diff --git a/client/sw/sw.js b/client/sw/sw.js index 4126bc19..f7dc23c1 100644 --- a/client/sw/sw.js +++ b/client/sw/sw.js @@ -4,6 +4,10 @@ const preval = require('preval.macro'); const tryToCatch = require('try-to-catch/legacy'); const currify = require('currify/legacy'); +const isDev = process.env.NODE_ENV === 'development'; + +const tryWrap = (a) => (...b) => tryToCatch(a, ...b); + const wait = currify((f, e) => e.waitUntil(f())); const respondWith = currify((f, e) => e.respondWith(f(e))); const getPathName = (url) => new URL(url).pathname; @@ -62,10 +66,10 @@ async function onFetch(event) { const cache = await caches.open(NAME); const response = await cache.match(request); - if (response) + if (!isDev && response) return response; - const [e, resp] = await tryToCatch(fetch, request.clone()); + const [e, resp] = await tryToRequest(request); if (e) return console.error(e, response, pathname); @@ -92,3 +96,28 @@ async function addToCache(request, response) { return cache.put(request, response); } +async function tryToRequest(req) { + const {url} = req; + const path = url.replace(`${location.origin}/`, ''); + const get = tryWrap(fetch); + const prefix = getPrefix(); + + if (prefix && /^dist/.test(path)) + return await get(createRequest(`${prefix}${path}`)); + + return await get(req.clone()); +} + +function getPrefix() { + const { + href, + origin, + } = location; + + const prefix = href + .replace(origin, '') + .replace('sw.js', ''); + + return prefix; +} + diff --git a/package.json b/package.json index 876b999f..3556bd04 100644 --- a/package.json +++ b/package.json @@ -158,9 +158,11 @@ "devDependencies": { "@babel/core": "^7.0.0-beta.49", "@babel/plugin-proposal-object-rest-spread": "^7.0.0-beta.49", + "@babel/plugin-syntax-dynamic-import": "^7.0.0-beta.51", "@babel/plugin-transform-object-assign": "^7.0.0-beta.49", "@babel/preset-env": "^7.0.0-beta.49", "@cloudcmd/clipboard": "^1.0.0", + "babel-eslint": "^8.2.3", "babel-loader": "^8.0.0-beta", "babel-plugin-macros": "^2.2.1", "clean-css-loader": "^1.0.1", diff --git a/server/rest/info.js b/server/rest/info.js index eeb38c66..11da857c 100644 --- a/server/rest/info.js +++ b/server/rest/info.js @@ -1,8 +1,10 @@ 'use strict'; -const version = require('../../package').version; const format = require('format-io'); +const version = require('../../package').version; +const config = require('../config'); + const getMemory = () => { const rss = process.memoryUsage().rss; return format.size(rss); @@ -11,5 +13,6 @@ const getMemory = () => { module.exports = () => ({ version, memory: getMemory(), + prefix: config('prefix'), });