mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-08-04 21:53:14 +00:00
feature(user-menu) add (#221)
This commit is contained in:
parent
f60af287d3
commit
eb4f7c0d7c
34 changed files with 562 additions and 41 deletions
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
/* global DOM */
|
||||
/* global CloudCmd */
|
||||
|
||||
const {
|
||||
Dialog,
|
||||
|
|
|
|||
146
client/modules/terminal-run.js
Normal file
146
client/modules/terminal-run.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd, gritty */
|
||||
|
||||
const {promisify} = require('es6-promisify');
|
||||
const tryToCatch = require('try-to-catch/legacy');
|
||||
|
||||
require('../../css/terminal.css');
|
||||
|
||||
const exec = require('execon');
|
||||
const load = require('load.js');
|
||||
const DOM = require('../dom');
|
||||
const Images = require('../dom/images');
|
||||
|
||||
const loadParallel = promisify(load.parallel);
|
||||
|
||||
const {Dialog} = DOM;
|
||||
const {
|
||||
Key,
|
||||
config,
|
||||
} = CloudCmd;
|
||||
|
||||
CloudCmd.TerminalRun = exports;
|
||||
|
||||
let Loaded;
|
||||
let Terminal;
|
||||
let Socket;
|
||||
|
||||
const loadAll = async () => {
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
const prefixGritty = getPrefix();
|
||||
const js = `${prefixGritty}/gritty.js`;
|
||||
const css = `${prefix}/dist/terminal.css`;
|
||||
|
||||
const [e] = await tryToCatch(loadParallel, [js, css]);
|
||||
|
||||
if (e) {
|
||||
const src = e.target.src.replace(window.location.href, '');
|
||||
return Dialog.alert(`file ${src} could not be loaded`);
|
||||
}
|
||||
|
||||
Loaded = true;
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
if (!config('terminal'))
|
||||
return;
|
||||
|
||||
Images.show.load('top');
|
||||
|
||||
await CloudCmd.View();
|
||||
await loadAll();
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide () {
|
||||
CloudCmd.View.hide();
|
||||
}
|
||||
|
||||
function getPrefix() {
|
||||
return CloudCmd.prefix + '/gritty';
|
||||
}
|
||||
|
||||
function getPrefixSocket() {
|
||||
return CloudCmd.prefixSocket + '/gritty';
|
||||
}
|
||||
|
||||
function getEnv() {
|
||||
return {
|
||||
ACTIVE_DIR: DOM.getCurrentDirPath,
|
||||
PASSIVE_DIR: DOM.getNotCurrentDirPath,
|
||||
CURRENT_NAME: DOM.getCurrentName,
|
||||
CURRENT_PATH: DOM.getCurrentPath,
|
||||
};
|
||||
}
|
||||
|
||||
function create(createOptions) {
|
||||
const {
|
||||
command,
|
||||
autoClose,
|
||||
closeMessage = 'Press any key to close Terminal...',
|
||||
} = createOptions;
|
||||
|
||||
const options = {
|
||||
env: getEnv(),
|
||||
prefix: getPrefixSocket(),
|
||||
socketPath: CloudCmd.prefix,
|
||||
fontFamily: 'Droid Sans Mono',
|
||||
command,
|
||||
autoRestart: false,
|
||||
};
|
||||
|
||||
let commandExit = false;
|
||||
|
||||
const {socket, terminal} = gritty(document.body, options);
|
||||
|
||||
Socket = socket;
|
||||
Terminal = terminal;
|
||||
|
||||
Terminal.on('key', (char, {keyCode, shiftKey}) => {
|
||||
if (commandExit)
|
||||
hide();
|
||||
|
||||
if (shiftKey && keyCode === Key.ESC) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
Socket.on('exit', () => {
|
||||
if (autoClose)
|
||||
return hide();
|
||||
|
||||
terminal.write(`\n${closeMessage}`);
|
||||
commandExit = true;
|
||||
});
|
||||
|
||||
Socket.on('connect', exec.with(authCheck, socket));
|
||||
}
|
||||
|
||||
function authCheck(spawn) {
|
||||
spawn.emit('auth', config('username'), config('password'));
|
||||
|
||||
spawn.on('reject', () => {
|
||||
Dialog.alert('Wrong credentials!');
|
||||
});
|
||||
}
|
||||
|
||||
async function show(options = {}) {
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
if (!config('terminal'))
|
||||
return;
|
||||
|
||||
await create(options);
|
||||
|
||||
CloudCmd.View.show(Terminal.element, {
|
||||
afterShow: () => {
|
||||
Terminal.focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ CloudCmd.Terminal = exports;
|
|||
|
||||
let Loaded;
|
||||
let Terminal;
|
||||
let Socket;
|
||||
|
||||
const loadAll = async () => {
|
||||
const {prefix} = CloudCmd;
|
||||
|
|
@ -84,17 +85,19 @@ function create() {
|
|||
socketPath: CloudCmd.prefix,
|
||||
fontFamily: 'Droid Sans Mono',
|
||||
};
|
||||
|
||||
const {socket, terminal} = gritty(document.body, options);
|
||||
|
||||
Socket = socket;
|
||||
Terminal = terminal;
|
||||
|
||||
terminal.on('key', (char, {keyCode, shiftKey}) => {
|
||||
Terminal.on('key', (char, {keyCode, shiftKey}) => {
|
||||
if (shiftKey && keyCode === Key.ESC) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', exec.with(authCheck, socket));
|
||||
Socket.on('connect', exec.with(authCheck, socket));
|
||||
}
|
||||
|
||||
function authCheck(spawn) {
|
||||
|
|
@ -105,7 +108,7 @@ function authCheck(spawn) {
|
|||
});
|
||||
}
|
||||
|
||||
function show(callback) {
|
||||
function show() {
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
|
|
@ -114,10 +117,7 @@ function show(callback) {
|
|||
|
||||
CloudCmd.View.show(Terminal.element, {
|
||||
afterShow: () => {
|
||||
if (Terminal)
|
||||
Terminal.focus();
|
||||
|
||||
exec(callback);
|
||||
Terminal.focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
20
client/modules/user-menu/get-user-menu.js
Normal file
20
client/modules/user-menu/get-user-menu.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
const defaultUserMenu = {
|
||||
'F2 - Rename file': async ({DOM}) => {
|
||||
DOM.renameCurrent();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = (menuFn) => {
|
||||
if (!menuFn)
|
||||
return defaultUserMenu;
|
||||
|
||||
const module = {};
|
||||
const fn = Function('module', menuFn);
|
||||
|
||||
fn(module);
|
||||
|
||||
return module.exports;
|
||||
};
|
||||
|
||||
29
client/modules/user-menu/get-user-menu.spec.js
Normal file
29
client/modules/user-menu/get-user-menu.spec.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('supertape');
|
||||
const getUserMenu = require('./get-user-menu');
|
||||
|
||||
test('user-menu: getUserMenu', (t) => {
|
||||
const menu = `module.exports = {
|
||||
'F2 - Rename file': ({DOM}) => {
|
||||
const {element} = DOM.CurrentInfo;
|
||||
DOM.renameCurrent(element);
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = getUserMenu(menu);
|
||||
|
||||
const [key] = Object.keys(result);
|
||||
|
||||
t.equal(key, 'F2 - Rename file', 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('user-menu: getUserMenu: no args', (t) => {
|
||||
const result = getUserMenu();
|
||||
const [key] = Object.keys(result);
|
||||
|
||||
t.equal(key, 'F2 - Rename file', 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
112
client/modules/user-menu/index.js
Normal file
112
client/modules/user-menu/index.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd, DOM */
|
||||
|
||||
require('../../../css/user-menu.css');
|
||||
|
||||
const currify = require('currify/legacy');
|
||||
const {promisify} = require('es6-promisify');
|
||||
const load = require('load.js');
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
|
||||
const Images = require('../../dom/images');
|
||||
const getUserMenu = require('./get-user-menu');
|
||||
|
||||
const loadCSS = promisify(load.css);
|
||||
|
||||
const Name = 'UserMenu';
|
||||
CloudCmd[Name] = module.exports;
|
||||
|
||||
const {Key} = CloudCmd;
|
||||
|
||||
module.exports.init = async () => {
|
||||
await Promise.all([
|
||||
loadCSS(`${CloudCmd.prefix}/dist/user-menu.css`),
|
||||
CloudCmd.View(),
|
||||
]);
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
const getKey = (a) => a.split(' - ')[0];
|
||||
const beginWith = (a) => (b) => !b.indexOf(a);
|
||||
|
||||
const {CurrentInfo} = DOM;
|
||||
|
||||
async function show() {
|
||||
Images.show.load('top');
|
||||
|
||||
const {dirPath} = CurrentInfo;
|
||||
const res = await fetch(`/api/v1/user-menu?dir=${dirPath}`);
|
||||
const userMenu = getUserMenu(await res.text());
|
||||
const options = Object.keys(userMenu);
|
||||
|
||||
const el = createElement('select', {
|
||||
className: 'cloudcmd-user-menu',
|
||||
innerHTML: fillTemplate(options),
|
||||
size: 10,
|
||||
});
|
||||
|
||||
const keys = options.map(getKey);
|
||||
el.addEventListener('keydown', onKeyDown(keys, options, userMenu));
|
||||
el.addEventListener('dblclick', onDblClick(options, userMenu));
|
||||
|
||||
const afterShow = () => el.focus();
|
||||
const autoSize = true;
|
||||
|
||||
Images.hide();
|
||||
|
||||
CloudCmd.View.show(el, {
|
||||
autoSize,
|
||||
afterShow,
|
||||
});
|
||||
}
|
||||
|
||||
function fillTemplate(options) {
|
||||
const result = [];
|
||||
|
||||
for (const option of options) {
|
||||
result.push(`<option>${option}</option>`);
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
function hide() {
|
||||
CloudCmd.View.hide();
|
||||
}
|
||||
|
||||
const onDblClick = currify(async (options, userMenu, e) => {
|
||||
const {value} = e.target;
|
||||
await runUserMenu(value, options, userMenu);
|
||||
});
|
||||
|
||||
const onKeyDown = currify(async (keys, options, userMenu, e) => {
|
||||
const {keyCode} = e;
|
||||
const key = e.key.toUpperCase();
|
||||
|
||||
let value;
|
||||
|
||||
if (keyCode === Key.ENTER)
|
||||
({value} = e.target);
|
||||
else if (keys.includes(key))
|
||||
value = options.find(beginWith(key));
|
||||
else
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
await runUserMenu(value, options, userMenu);
|
||||
});
|
||||
|
||||
const runUserMenu = async (value, options, userMenu) => {
|
||||
hide();
|
||||
|
||||
await userMenu[value]({
|
||||
DOM,
|
||||
CloudCmd,
|
||||
});
|
||||
};
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue