1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-24 08:12:24 +02:00
joplin/CliClient/app/app-gui.js

406 lines
10 KiB
JavaScript
Raw Normal View History

2017-10-05 19:17:56 +02:00
import { Logger } from 'lib/logger.js';
import { Folder } from 'lib/models/folder.js';
import { Note } from 'lib/models/note.js';
2017-10-06 19:38:17 +02:00
import { cliUtils } from './cli-utils.js';
2017-10-07 18:30:27 +02:00
import { reducer, defaultState } from 'lib/reducer.js';
2017-10-09 00:34:01 +02:00
import { _ } from 'lib/locale.js';
2017-10-05 19:17:56 +02:00
const tk = require('terminal-kit');
const termutils = require('tkwidgets/framework/termutils.js');
const Renderer = require('tkwidgets/framework/Renderer.js');
2017-10-07 18:30:27 +02:00
const BaseWidget = require('tkwidgets/BaseWidget.js');
2017-10-05 19:17:56 +02:00
const ListWidget = require('tkwidgets/ListWidget.js');
const TextWidget = require('tkwidgets/TextWidget.js');
const ConsoleWidget = require('tkwidgets/ConsoleWidget.js');
const HLayoutWidget = require('tkwidgets/HLayoutWidget.js');
const VLayoutWidget = require('tkwidgets/VLayoutWidget.js');
2017-10-07 23:01:03 +02:00
const ReduxRootWidget = require('tkwidgets/ReduxRootWidget.js');
2017-10-05 19:17:56 +02:00
const RootWidget = require('tkwidgets/RootWidget.js');
const WindowWidget = require('tkwidgets/WindowWidget.js');
2017-10-07 23:01:03 +02:00
const NoteWidget = require('./gui/NoteWidget.js');
2017-10-08 00:17:10 +02:00
const FolderListWidget = require('./gui/FolderListWidget.js');
2017-10-07 23:01:03 +02:00
2017-10-05 19:17:56 +02:00
class AppGui {
2017-10-07 23:01:03 +02:00
constructor(app, store) {
2017-10-05 19:17:56 +02:00
this.app_ = app;
2017-10-07 23:01:03 +02:00
this.store_ = store;
2017-10-07 18:30:27 +02:00
BaseWidget.setLogger(app.logger());
2017-10-05 19:17:56 +02:00
this.term_ = tk.terminal;
this.renderer_ = null;
this.logger_ = new Logger();
2017-10-07 23:01:03 +02:00
this.buildUi();
2017-10-06 19:38:17 +02:00
this.renderer_ = new Renderer(this.term(), this.rootWidget_);
2017-10-07 20:05:35 +02:00
this.renderer_.on('renderDone', async (event) => {
2017-10-09 20:05:01 +02:00
if (this.widget('console').hasFocus) this.widget('console').resetCursor();
2017-10-07 20:05:35 +02:00
});
2017-10-06 19:38:17 +02:00
this.app_.on('modelAction', async (event) => {
await this.handleModelAction(event.action);
});
2017-10-07 18:30:27 +02:00
this.shortcuts_ = this.setupShortcuts();
2017-10-09 00:34:01 +02:00
this.inputMode_ = AppGui.INPUT_MODE_NORMAL;
this.currentShortcutKeys_ = '';
this.lastShortcutKeyTime_ = 0;
2017-10-06 19:38:17 +02:00
}
2017-10-05 19:17:56 +02:00
2017-10-06 19:38:17 +02:00
buildUi() {
2017-10-07 23:01:03 +02:00
this.rootWidget_ = new ReduxRootWidget(this.store_);
2017-10-09 00:34:01 +02:00
this.rootWidget_.name = 'root';
2017-10-05 19:17:56 +02:00
2017-10-08 00:17:10 +02:00
const folderList = new FolderListWidget();
2017-10-09 20:05:01 +02:00
folderList.style = { borderBottomWidth: 1 };
2017-10-08 19:50:43 +02:00
folderList.name = 'folderList';
2017-10-09 20:05:01 +02:00
folderList.vStretch = true;
2017-10-05 19:17:56 +02:00
folderList.on('currentItemChange', async () => {
2017-10-07 22:04:53 +02:00
const folder = folderList.currentItem;
2017-10-08 00:17:10 +02:00
this.store_.dispatch({
type: 'FOLDERS_SELECT',
folderId: folder ? folder.id : 0,
});
});
this.rootWidget_.connect(folderList, (state) => {
return {
selectedFolderId: state.selectedFolderId,
items: state.folders,
};
2017-10-05 19:17:56 +02:00
});
const noteList = new ListWidget();
2017-10-07 22:04:53 +02:00
noteList.items = [];
2017-10-09 20:05:01 +02:00
noteList.itemRenderer = (note) => {
2017-10-06 19:38:17 +02:00
let label = note.title;
if (note.is_todo) {
label = '[' + (note.todo_completed ? 'X' : ' ') + '] ' + label;
}
return label;
2017-10-09 20:05:01 +02:00
};
2017-10-08 19:50:43 +02:00
noteList.name = 'noteList';
2017-10-09 20:05:01 +02:00
noteList.vStretch = true;
noteList.style = {
2017-10-05 19:17:56 +02:00
borderBottomWidth: 1,
borderLeftWidth: 1,
borderRightWidth: 1,
2017-10-09 20:05:01 +02:00
};
2017-10-05 19:17:56 +02:00
noteList.on('currentItemChange', async () => {
2017-10-07 22:04:53 +02:00
let note = noteList.currentItem;
2017-10-07 23:01:03 +02:00
this.store_.dispatch({
type: 'NOTES_SELECT',
noteId: note ? note.id : 0,
});
2017-10-08 00:17:10 +02:00
});
this.rootWidget_.connect(noteList, (state) => {
return {
selectedNoteId: state.selectedNoteId,
items: state.notes,
};
2017-10-05 19:17:56 +02:00
});
2017-10-07 23:01:03 +02:00
const noteText = new NoteWidget();
2017-10-09 20:05:01 +02:00
noteText.vStretch = true;
2017-10-08 19:50:43 +02:00
noteText.name = 'noteText';
2017-10-09 20:05:01 +02:00
noteText.style = { borderBottomWidth: 1 };
2017-10-07 23:01:03 +02:00
this.rootWidget_.connect(noteText, (state) => {
return { noteId: state.selectedNoteId };
});
2017-10-06 19:38:17 +02:00
const consoleWidget = new ConsoleWidget();
2017-10-09 20:05:01 +02:00
consoleWidget.hStretch = true;
2017-10-08 19:50:43 +02:00
consoleWidget.name = 'console';
2017-10-06 19:38:17 +02:00
consoleWidget.on('accept', (event) => {
2017-10-08 19:50:43 +02:00
this.processCommand(event.input, 'console');
2017-10-06 19:38:17 +02:00
});
2017-10-06 20:01:10 +02:00
const hLayout = new HLayoutWidget();
2017-10-08 19:50:43 +02:00
hLayout.name = 'hLayout';
2017-10-06 20:01:10 +02:00
hLayout.addChild(folderList, { type: 'stretch', factor: 1 });
hLayout.addChild(noteList, { type: 'stretch', factor: 1 });
hLayout.addChild(noteText, { type: 'stretch', factor: 1 });
2017-10-05 19:17:56 +02:00
2017-10-06 19:38:17 +02:00
const vLayout = new VLayoutWidget();
2017-10-08 19:50:43 +02:00
vLayout.name = 'vLayout';
2017-10-06 19:38:17 +02:00
vLayout.addChild(hLayout, { type: 'stretch', factor: 1 });
2017-10-09 00:34:01 +02:00
vLayout.addChild(consoleWidget, { type: 'fixed', factor: 6 });
2017-10-05 19:17:56 +02:00
const win1 = new WindowWidget();
2017-10-06 19:38:17 +02:00
win1.addChild(vLayout);
2017-10-08 19:50:43 +02:00
win1.name = 'mainWindow';
2017-10-05 19:17:56 +02:00
2017-10-07 23:01:03 +02:00
this.rootWidget_.addChild(win1);
2017-10-05 19:17:56 +02:00
}
2017-10-07 18:30:27 +02:00
setupShortcuts() {
const shortcuts = {};
2017-10-08 19:50:43 +02:00
const consoleWidget = this.widget('console');
2017-10-09 00:34:01 +02:00
shortcuts['DELETE'] = {
description: _('Delete a note'),
action: 'rm $n',
};
2017-10-08 19:50:43 +02:00
2017-10-09 00:34:01 +02:00
shortcuts[' '] = {
friendlyName: 'SPACE',
description: _('Set a todo as completed / not completed'),
action: 'todo toggle $n',
}
2017-10-08 19:50:43 +02:00
2017-10-09 00:34:01 +02:00
shortcuts['c'] = {
description: _('Enter the console'),
action: () => { consoleWidget.focus(); }
};
shortcuts['ESC'] = {
description: _('Exit the console'),
isDocOnly: true,
};
shortcuts['ENTER'] = {
description: null,
action: () => {
2017-10-09 20:05:01 +02:00
const w = this.widget('mainWindow').focusedWidget;
2017-10-09 00:34:01 +02:00
if (w.name == 'folderList') {
this.widget('noteList').focus();
} else if (w.name == 'noteList') {
this.processCommand('edit $n');
}
},
2017-10-08 19:50:43 +02:00
}
2017-10-09 00:34:01 +02:00
shortcuts['nt'] = {
description: _('Create a new todo'),
action: () => { consoleWidget.focus('mktodo '); },
2017-10-08 19:50:43 +02:00
}
2017-10-09 00:34:01 +02:00
shortcuts['nn'] = {
description: _('Create a new note'),
action: () => { consoleWidget.focus('mknote '); },
2017-10-08 19:50:43 +02:00
}
2017-10-09 00:34:01 +02:00
shortcuts['nt'] = {
description: _('Create a new todo'),
action: () => { consoleWidget.focus('mktodo '); },
2017-10-08 19:50:43 +02:00
}
2017-10-09 00:34:01 +02:00
shortcuts['nb'] = {
description: _('Create a new notebook'),
action: () => { consoleWidget.focus('mkbook '); },
}
shortcuts['CTRL_JCTRL_Z'] = {
friendlyName: 'Ctrl+J Ctrl+Z',
description: _('Maximise/minimise the console'),
action: () => { this.toggleMaximizeConsole(); },
2017-10-08 19:50:43 +02:00
}
2017-10-07 18:30:27 +02:00
return shortcuts;
}
2017-10-09 00:34:01 +02:00
toggleMaximizeConsole() {
this.maximizeConsole(!this.consoleIsMaximized());
}
maximizeConsole(doMaximize = true) {
const consoleWidget = this.widget('console');
if (consoleWidget.isMaximized__ === undefined) {
consoleWidget.isMaximized__ = false;
}
if (consoleWidget.isMaximized__ === doMaximize) return;
let constraints = {
type: 'fixed',
2017-10-09 20:05:01 +02:00
factor: !doMaximize ? 5 : this.widget('vLayout').height - 4,
2017-10-09 00:34:01 +02:00
};
consoleWidget.isMaximized__ = doMaximize;
this.widget('vLayout').setWidgetConstraints(consoleWidget, constraints);
}
minimizeConsole() {
this.maximizeConsole(false);
}
consoleIsMaximized() {
return this.widget('console').isMaximized__ === true;
}
2017-10-05 19:17:56 +02:00
widget(name) {
2017-10-09 00:34:01 +02:00
if (name === 'root') return this.rootWidget_;
2017-10-05 19:17:56 +02:00
return this.rootWidget_.childByName(name);
}
app() {
return this.app_;
}
setLogger(l) {
this.logger_ = l;
}
logger() {
return this.logger_;
}
2017-10-09 00:34:01 +02:00
shortcuts() {
return this.shortcuts_;
}
2017-10-05 19:17:56 +02:00
term() {
return this.term_;
}
2017-10-07 18:30:27 +02:00
activeListItem() {
2017-10-09 20:05:01 +02:00
const widget = this.widget('mainWindow').focusedWidget;
2017-10-07 18:30:27 +02:00
if (!widget) return null;
2017-10-08 19:50:43 +02:00
if (widget.name == 'noteList' || widget.name == 'folderList') {
2017-10-07 22:04:53 +02:00
return widget.currentItem;
2017-10-07 18:30:27 +02:00
}
return null;
}
2017-10-06 19:38:17 +02:00
async handleModelAction(action) {
2017-10-07 20:05:35 +02:00
this.logger().info('Action:', action);
2017-10-07 18:30:27 +02:00
let state = Object.assign({}, defaultState);
2017-10-07 22:04:53 +02:00
state.notes = this.widget('noteList').items;
2017-10-06 19:38:17 +02:00
2017-10-07 18:30:27 +02:00
let newState = reducer(state, action);
2017-10-06 19:38:17 +02:00
2017-10-07 18:30:27 +02:00
if (newState !== state) {
2017-10-07 22:04:53 +02:00
this.widget('noteList').items = newState.notes;
2017-10-07 18:30:27 +02:00
}
}
2017-10-06 19:38:17 +02:00
2017-10-07 18:30:27 +02:00
async processCommand(cmd) {
if (!cmd) return;
cmd = cmd.trim();
if (!cmd.length) return;
2017-10-07 22:04:53 +02:00
let note = this.widget('noteList').currentItem;
let folder = this.widget('folderList').currentItem;
2017-10-06 19:38:17 +02:00
let args = cliUtils.splitCommandString(cmd);
for (let i = 0; i < args.length; i++) {
2017-10-08 19:50:43 +02:00
if (args[i] == '$n') {
args[i] = note ? note.id : '';
} else if (args[i] == '$b') {
args[i] = folder ? folder.id : '';
2017-10-07 18:30:27 +02:00
} else if (args[i] == '$c') {
const item = this.activeListItem();
args[i] = item ? item.id : '';
}
2017-10-06 19:38:17 +02:00
}
2017-10-07 18:30:27 +02:00
try {
await this.app().execCommand(args);
} catch (error) {
2017-10-09 00:34:01 +02:00
this.widget('console').bufferPush(error.message);
2017-10-07 18:30:27 +02:00
}
2017-10-06 19:38:17 +02:00
}
2017-10-05 19:17:56 +02:00
async updateFolderList() {
const folders = await Folder.all();
2017-10-07 22:04:53 +02:00
this.widget('folderList').items = folders;
2017-10-05 19:17:56 +02:00
}
async updateNoteList(folderId) {
2017-10-06 19:38:17 +02:00
const fields = Note.previewFields();
fields.splice(fields.indexOf('body'), 1);
const notes = folderId ? await Note.previews(folderId, { fields: fields }) : [];
2017-10-07 22:04:53 +02:00
this.widget('noteList').items = notes;
2017-10-05 19:17:56 +02:00
}
async updateNoteText(note) {
const text = note ? note.body : '';
2017-10-07 22:04:53 +02:00
this.widget('noteText').text = text;
2017-10-05 19:17:56 +02:00
}
2017-10-09 00:34:01 +02:00
isSpecialKey(name) {
return ['ENTER', 'DOWN', 'UP', 'LEFT', 'RIGHT', 'DELETE', 'BACKSPACE', 'ESCAPE', 'TAB', 'SHIFT_TAB'].indexOf(name) >= 0;
}
2017-10-05 19:17:56 +02:00
async start() {
const term = this.term();
term.fullscreen();
termutils.hideCursor(term);
try {
this.renderer_.start();
2017-10-07 18:30:27 +02:00
const consoleWidget = this.widget('console');
2017-10-05 19:17:56 +02:00
term.grabInput();
2017-10-07 18:30:27 +02:00
term.on('key', async (name, matches, data) => {
2017-10-05 19:17:56 +02:00
if (name === 'CTRL_C' ) {
termutils.showCursor(term);
term.fullscreen(false);
process.exit();
2017-10-07 18:30:27 +02:00
return;
2017-10-05 19:17:56 +02:00
}
2017-10-09 00:34:01 +02:00
const now = (new Date()).getTime();
if (now - this.lastShortcutKeyTime_ > 1000 || this.isSpecialKey(name)) {
this.currentShortcutKeys_ = name;
} else {
this.currentShortcutKeys_ += name;
}
this.lastShortcutKeyTime_ = now;
// Don't process shortcut keys if the console is active, except if the shortcut
// starts with CTRL (eg. CTRL+J CTRL+Z to maximize the console window).
2017-10-09 20:05:01 +02:00
if (!consoleWidget.hasFocus || this.currentShortcutKeys_.indexOf('CTRL') === 0) {
2017-10-09 00:34:01 +02:00
this.logger().debug('Now: ' + name + ', Keys: ' + this.currentShortcutKeys_);
if (this.currentShortcutKeys_ in this.shortcuts_) {
const cmd = this.shortcuts_[this.currentShortcutKeys_].action;
if (!cmd.isDocOnly) {
this.currentShortcutKeys_ = '';
if (typeof cmd === 'function') {
cmd();
} else {
consoleWidget.bufferPush(cmd);
await this.processCommand(cmd);
}
2017-10-07 18:30:27 +02:00
}
}
2017-10-05 19:17:56 +02:00
}
});
} catch (error) {
this.logger().error(error);
term.fullscreen(false);
termutils.showCursor(term);
console.error(error);
}
2017-10-08 00:17:10 +02:00
process.on('unhandledRejection', (reason, p) => {
term.fullscreen(false);
termutils.showCursor(term);
console.error('Unhandled promise rejection', p, 'reason:', reason);
process.exit(1);
});
2017-10-05 19:17:56 +02:00
}
}
2017-10-09 00:34:01 +02:00
AppGui.INPUT_MODE_NORMAL = 1;
AppGui.INPUT_MODE_META = 2;
2017-10-05 19:17:56 +02:00
module.exports = AppGui;