Browse Source

Move Keymaps to TS

pull/4746/head
Artur Arseniev 4 years ago
parent
commit
6c583ff269
  1. 7
      src/editor/model/Editor.ts
  2. 235
      src/keymaps/index.js
  3. 258
      src/keymaps/index.ts
  4. 2
      src/utils/mixins.ts

7
src/editor/model/Editor.ts

@ -9,6 +9,7 @@ import FrameView from '../../canvas/view/FrameView';
import EditorModule from '..';
import EditorView from '../view/EditorView';
import { IModule } from '../../abstract/Module';
import CanvasModule from '../../canvas';
//@ts-ignore
Backbone.$ = $;
@ -100,6 +101,10 @@ export default class EditorModel extends Model {
return this.get('shallow');
}
get Canvas(): CanvasModule {
return this.get('Canvas');
}
constructor(conf = {}) {
super();
this._config = conf;
@ -315,7 +320,7 @@ export default class EditorModel extends Model {
this.set('Editor', editor);
}
getEditor() {
getEditor(): EditorModule {
return this.get('Editor');
}

235
src/keymaps/index.js

@ -1,235 +0,0 @@
/**
* You can customize the initial state of the module from the editor initialization
* ```js
* const editor = grapesjs.init({
* keymaps: {
* // Object of keymaps
* defaults: {
* 'your-namespace:keymap-name' {
* keys: '⌘+z, ctrl+z',
* handler: 'some-command-id'
* },
* ...
* }
* }
* })
* ```
*
* Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance.
*
* ```js
* // Listen to events
* editor.on('keymap:add', () => { ... });
*
* // Use the API
* const keymaps = editor.Keymaps;
* keymaps.add(...);
* ```
*
* ## Available Events
* * `keymap:add` - New keymap added. The new keyamp object is passed as an argument
* * `keymap:remove` - Keymap removed. The removed keyamp object is passed as an argument
* * `keymap:emit` - Some keymap emitted, in arguments you get keymapId, shortcutUsed, Event
* * `keymap:emit:{keymapId}` - `keymapId` emitted, in arguments you get keymapId, shortcutUsed, Event
*
* ## Methods
* * [getConfig](#getconfig)
* * [add](#add)
* * [get](#get)
* * [getAll](#getAll)
* * [remove](#remove)
* * [removeAll](#removeall)
*
* @module Keymaps
*/
import { isString } from 'underscore';
import { hasWin } from '../utils/mixins';
import keymaster from '../utils/keymaster';
hasWin() && keymaster.init(window);
export default () => {
const configDef = {
defaults: {
'core:undo': {
keys: '⌘+z, ctrl+z',
handler: 'core:undo',
},
'core:redo': {
keys: '⌘+shift+z, ctrl+shift+z',
handler: 'core:redo',
},
'core:copy': {
keys: '⌘+c, ctrl+c',
handler: 'core:copy',
},
'core:paste': {
keys: '⌘+v, ctrl+v',
handler: 'core:paste',
},
'core:component-next': {
keys: 's',
handler: 'core:component-next',
},
'core:component-prev': {
keys: 'w',
handler: 'core:component-prev',
},
'core:component-enter': {
keys: 'd',
handler: 'core:component-enter',
},
'core:component-exit': {
keys: 'a',
handler: 'core:component-exit',
},
'core:component-delete': {
keys: 'backspace, delete',
handler: 'core:component-delete',
opts: { prevent: 1 },
},
},
};
return {
keymaster,
name: 'Keymaps',
/**
* Get module configurations
* @return {Object} Configuration object
*/
getConfig() {
return this.config;
},
/**
* Initialize module
* @param {Object} config Configurations
* @private
*/
init(opts = {}) {
this.config = { ...configDef, ...opts };
this.em = this.config.em;
this.keymaps = {};
return this;
},
onLoad() {
const defKeys = this.config.defaults;
for (let id in defKeys) {
const value = defKeys[id];
this.add(id, value.keys, value.handler, value.opts || {});
}
},
/**
* Add new keymap
* @param {string} id Keymap id
* @param {string} keys Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
* @param {Function|string} handler Keymap handler, might be a function
* @param {Object} [opts={}] Options
* @return {Object} Added keymap
* or just a command id as a string
* @example
* // 'ns' is just a custom namespace
* keymaps.add('ns:my-keymap', '⌘+j, ⌘+u, ctrl+j, alt+u', editor => {
* console.log('do stuff');
* });
* // or
* keymaps.add('ns:my-keymap', '⌘+s, ctrl+s', 'some-gjs-command');
*
* // listen to events
* editor.on('keymap:emit', (id, shortcut, e) => {
* // ...
* })
*/
add(id, keys, handler, opts = {}) {
const { em } = this;
const cmd = em.get('Commands');
const editor = em.getEditor();
const canvas = em.get('Canvas');
const keymap = { id, keys, handler };
const pk = this.keymaps[id];
pk && this.remove(id);
this.keymaps[id] = keymap;
keymaster(keys, (e, h) => {
// It's safer putting handlers resolution inside the callback
const opt = { event: e, h };
handler = isString(handler) ? cmd.get(handler) : handler;
const ableTorun = !em.isEditing() && !editor.Canvas.isInputFocused();
if (ableTorun || opts.force) {
opts.prevent && canvas.getCanvasView().preventDefault(e);
typeof handler == 'object' ? cmd.runCommand(handler, opt) : handler(editor, 0, opt);
const args = [id, h.shortcut, e];
em.trigger('keymap:emit', ...args);
em.trigger(`keymap:emit:${id}`, ...args);
}
});
em.trigger('keymap:add', keymap);
return keymap;
},
/**
* Get the keymap by id
* @param {string} id Keymap id
* @return {Object} Keymap object
* @example
* keymaps.get('ns:my-keymap');
* // -> {keys, handler};
*/
get(id) {
return this.keymaps[id];
},
/**
* Get all keymaps
* @return {Object}
* @example
* keymaps.getAll();
* // -> {id1: {}, id2: {}};
*/
getAll() {
return this.keymaps;
},
/**
* Remove the keymap by id
* @param {string} id Keymap id
* @return {Object} Removed keymap
* @example
* keymaps.remove('ns:my-keymap');
* // -> {keys, handler};
*/
remove(id) {
const em = this.em;
const keymap = this.get(id);
if (keymap) {
delete this.keymaps[id];
keymap.keys.split(', ').forEach(k => keymaster.unbind(k.trim()));
em && em.trigger('keymap:remove', keymap);
return keymap;
}
},
/**
* Remove all binded keymaps
* @return {this}
*/
removeAll() {
Object.keys(this.keymaps).forEach(keymap => this.remove(keymap));
keymaster.handlers = {};
return this;
},
destroy() {
this.removeAll();
this.keymaps = {};
this.em = {};
},
};
};

258
src/keymaps/index.ts

@ -0,0 +1,258 @@
/**
* You can customize the initial state of the module from the editor initialization
* ```js
* const editor = grapesjs.init({
* keymaps: {
* // Object of keymaps
* defaults: {
* 'your-namespace:keymap-name' {
* keys: '⌘+z, ctrl+z',
* handler: 'some-command-id'
* },
* ...
* }
* }
* })
* ```
*
* Once the editor is instantiated you can use its API and listen to its events. Before using these methods, you should get the module from the instance.
*
* ```js
* // Listen to events
* editor.on('keymap:add', () => { ... });
*
* // Use the API
* const keymaps = editor.Keymaps;
* keymaps.add(...);
* ```
*
* ## Available Events
* * `keymap:add` - New keymap added. The new keyamp object is passed as an argument
* * `keymap:remove` - Keymap removed. The removed keyamp object is passed as an argument
* * `keymap:emit` - Some keymap emitted, in arguments you get keymapId, shortcutUsed, Event
* * `keymap:emit:{keymapId}` - `keymapId` emitted, in arguments you get keymapId, shortcutUsed, Event
*
* ## Methods
* * [getConfig](#getconfig)
* * [add](#add)
* * [get](#get)
* * [getAll](#getAll)
* * [remove](#remove)
* * [removeAll](#removeall)
*
* @module Keymaps
*/
import { isString } from 'underscore';
import { hasWin, isObject } from '../utils/mixins';
import keymaster from '../utils/keymaster';
import { Module } from '../abstract';
import EditorModel from '../editor/model/Editor';
import EditorModule from '../editor';
hasWin() && keymaster.init(window);
export interface Keymap {
id: string;
keys: string;
handler: string | ((editor: EditorModule) => any);
}
export interface KeymapOptions {
/**
* Force the handler to be executed.
*/
force?: boolean;
/**
* Prevent default of the original triggered event.
*/
prevent?: boolean;
}
export interface KeymapsConfig {
/**
* Default keymaps.
*/
defaults?: Record<string, { keys: string; handler: string; opts?: KeymapOptions }>;
}
const configDef: KeymapsConfig = {
defaults: {
'core:undo': {
keys: '⌘+z, ctrl+z',
handler: 'core:undo',
},
'core:redo': {
keys: '⌘+shift+z, ctrl+shift+z',
handler: 'core:redo',
},
'core:copy': {
keys: '⌘+c, ctrl+c',
handler: 'core:copy',
},
'core:paste': {
keys: '⌘+v, ctrl+v',
handler: 'core:paste',
},
'core:component-next': {
keys: 's',
handler: 'core:component-next',
},
'core:component-prev': {
keys: 'w',
handler: 'core:component-prev',
},
'core:component-enter': {
keys: 'd',
handler: 'core:component-enter',
},
'core:component-exit': {
keys: 'a',
handler: 'core:component-exit',
},
'core:component-delete': {
keys: 'backspace, delete',
handler: 'core:component-delete',
opts: { prevent: true },
},
},
};
export default class KeymapsModule extends Module<KeymapsConfig & { name?: string }> {
keymaster = keymaster;
keymaps: Record<string, Keymap>;
constructor(em: EditorModel) {
super(em, 'Keymaps', configDef);
this.keymaps = {};
}
/**
* Get module configurations
* @return {Object} Configuration object
*/
getConfig() {
return this.config;
}
onLoad() {
const defKeys = this.config.defaults;
for (let id in defKeys) {
const value = defKeys[id];
this.add(id, value.keys, value.handler, value.opts || {});
}
}
/**
* Add new keymap
* @param {string} id Keymap id
* @param {string} keys Keymap keys, eg. `ctrl+a`, `⌘+z, ctrl+z`
* @param {Function|string} handler Keymap handler, might be a function
* @param {Object} [opts={}] Options
* @return {Object} Added keymap
* or just a command id as a string
* @example
* // 'ns' is just a custom namespace
* keymaps.add('ns:my-keymap', '⌘+j, ⌘+u, ctrl+j, alt+u', editor => {
* console.log('do stuff');
* });
* // or
* keymaps.add('ns:my-keymap', '⌘+s, ctrl+s', 'some-gjs-command');
*
* // listen to events
* editor.on('keymap:emit', (id, shortcut, e) => {
* // ...
* })
*/
add(id: Keymap['id'], keys: Keymap['keys'], handler: Keymap['handler'], opts: KeymapOptions = {}) {
const { em } = this;
const cmd = em.get('Commands');
const editor = em.getEditor();
const canvas = em.Canvas;
const keymap: Keymap = { id, keys, handler };
const pk = this.keymaps[id];
pk && this.remove(id);
this.keymaps[id] = keymap;
keymaster(
keys,
(e: any, h: any) => {
// It's safer putting handlers resolution inside the callback
const opt = { event: e, h };
const handlerRes = isString(handler) ? cmd.get(handler) : handler;
const ableTorun = !em.isEditing() && !editor.Canvas.isInputFocused();
if (ableTorun || opts.force) {
opts.prevent && canvas.getCanvasView().preventDefault(e);
isObject(handlerRes) ? cmd.runCommand(handlerRes, opt) : handlerRes(editor, 0, opt);
const args = [id, h.shortcut, e];
em.trigger('keymap:emit', ...args);
em.trigger(`keymap:emit:${id}`, ...args);
}
},
undefined
);
em.trigger('keymap:add', keymap);
return keymap;
}
/**
* Get the keymap by id
* @param {string} id Keymap id
* @return {Object} Keymap object
* @example
* keymaps.get('ns:my-keymap');
* // -> {keys, handler};
*/
get(id: string) {
return this.keymaps[id];
}
/**
* Get all keymaps
* @return {Object}
* @example
* keymaps.getAll();
* // -> {id1: {}, id2: {}};
*/
getAll() {
return this.keymaps;
}
/**
* Remove the keymap by id
* @param {string} id Keymap id
* @return {Object} Removed keymap
* @example
* keymaps.remove('ns:my-keymap');
* // -> {keys, handler};
*/
remove(id: string) {
const { em } = this;
const keymap = this.get(id);
if (keymap) {
delete this.keymaps[id];
keymap.keys.split(', ').forEach(k => {
// @ts-ignore
keymaster.unbind(k.trim());
});
em?.trigger('keymap:remove', keymap);
return keymap;
}
}
/**
* Remove all binded keymaps
* @return {this}
*/
removeAll() {
Object.keys(this.keymaps).forEach(keymap => this.remove(keymap));
keymaster.handlers = {};
return this;
}
destroy() {
this.removeAll();
this.keymaps = {};
}
}

2
src/utils/mixins.ts

@ -272,7 +272,7 @@ const getKeyCode = (ev: KeyboardEvent) => ev.which || ev.keyCode;
const getKeyChar = (ev: KeyboardEvent) => String.fromCharCode(getKeyCode(ev));
const isEscKey = (ev: KeyboardEvent) => getKeyCode(ev) === 27;
const isEnterKey = (ev: KeyboardEvent) => getKeyCode(ev) === 13;
const isObject = (val: any) => val !== null && !Array.isArray(val) && typeof val === 'object';
const isObject = (val: any): val is Object => val !== null && !Array.isArray(val) && typeof val === 'object';
const isEmptyObj = (val: Record<string, any>) => Object.keys(val).length <= 0;
const capitalize = (str: string) => str && str.charAt(0).toUpperCase() + str.substring(1);

Loading…
Cancel
Save