From 9c8febb1ff001f29fe7f7f96ac0156eb5236304e Mon Sep 17 00:00:00 2001 From: josdejong Date: Fri, 22 May 2020 14:13:51 +0200 Subject: [PATCH] Implement SearchBox (WIP) --- public/index.html | 1 + src/JSONEditor.scss | 15 +++ src/JSONEditor.svelte | 109 +++++++++++++++---- src/JSONNode.svelte | 2 +- src/SearchBox.scss | 57 ++++++++++ src/SearchBox.svelte | 85 +++++++++++++++ src/styles.scss | 2 + src/utils/keyBindings.js | 215 ++++++++++++++++++++++++++++++++++++++ src/{ => utils}/search.js | 7 +- 9 files changed, 468 insertions(+), 25 deletions(-) create mode 100644 src/SearchBox.scss create mode 100644 src/SearchBox.svelte create mode 100644 src/utils/keyBindings.js rename src/{ => utils}/search.js (92%) diff --git a/public/index.html b/public/index.html index bcdb0d0..2f1b857 100644 --- a/public/index.html +++ b/public/index.html @@ -32,6 +32,7 @@ #testEditor { width: 800px; height: 500px; + max-width: 100%; } diff --git a/src/JSONEditor.scss b/src/JSONEditor.scss index 9939548..23a53b4 100644 --- a/src/JSONEditor.scss +++ b/src/JSONEditor.scss @@ -39,6 +39,14 @@ flex: 1; } + .separator { + background: rgba(255, 255, 255, 0.2); + box-sizing: border-box; + width: 1px; + height: 24px; + margin: 5px; + } + .search-box { display: flex; align-items: center; @@ -60,6 +68,13 @@ .contents { flex: 1; overflow: auto; + position: relative; + + .search { + position: absolute; + top: $search-box-offset; + right: $search-box-offset; + } .bottom { height: $input-padding; diff --git a/src/JSONEditor.svelte b/src/JSONEditor.svelte index 267957b..2bd5272 100644 --- a/src/JSONEditor.svelte +++ b/src/JSONEditor.svelte @@ -1,14 +1,19 @@ -
+
+ {#if showSearch} + + {/if}
diff --git a/src/JSONNode.svelte b/src/JSONNode.svelte index 6109315..bb51491 100644 --- a/src/JSONNode.svelte +++ b/src/JSONNode.svelte @@ -2,7 +2,7 @@ import { getPlainText, setPlainText } from './utils/domUtils.js' import Icon from 'svelte-awesome' import { faCaretDown, faCaretRight } from '@fortawesome/free-solid-svg-icons' - import { SEARCH_PROPERTY, SEARCH_VALUE } from './search' + import { SEARCH_PROPERTY, SEARCH_VALUE } from './utils/search.js' import classnames from 'classnames' import debounce from 'lodash/debounce' import { findUniqueName } from './utils/stringUtils.js' diff --git a/src/SearchBox.scss b/src/SearchBox.scss new file mode 100644 index 0000000..da512e8 --- /dev/null +++ b/src/SearchBox.scss @@ -0,0 +1,57 @@ +@import './styles.scss'; + +$search-size: 24px; + +.search-box { + border: 2px solid $theme-color; + border-radius: $border-radius; + background: $white; + + .search-form { + display: flex; + align-items: center; + padding: 0 5px; + + .search-icon { + color: $light-gray; + display: block; + width: $search-size; + height: $search-size; + text-align: center; + background: transparent; + border: none; + padding: 0; + margin: 0; + } + + button.search-icon { + width: 20px; + cursor: pointer; + + &:hover, + &:active { + color: $gray; + } + } + + .search-input { + border: none; + height: $search-size; + padding: 0 $input-padding; + margin: 0; + flex: 1; + width: 100px; + } + + .search-count { + color: $light-gray; + font-size: $font-size-small; + visibility: hidden; + padding: 0 $input-padding; + + &.visible { + visibility: visible; + } + } + } +} diff --git a/src/SearchBox.svelte b/src/SearchBox.svelte new file mode 100644 index 0000000..55b272f --- /dev/null +++ b/src/SearchBox.svelte @@ -0,0 +1,85 @@ + + + + + diff --git a/src/styles.scss b/src/styles.scss index ab7dc13..275c00c 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -27,5 +27,7 @@ $light-gray: #c0c0c0; $line-height: 18px; $indentation-width: 18px; $input-padding: 5px; +$search-box-offset: 10px; +$border-radius: 3px; $menu-padding: 5px; diff --git a/src/utils/keyBindings.js b/src/utils/keyBindings.js new file mode 100644 index 0000000..1fffc67 --- /dev/null +++ b/src/utils/keyBindings.js @@ -0,0 +1,215 @@ +// inspiration: https://github.com/andrepolischuk/keycomb + +// TODO: write unit tests for keyBindings + +// FIXME: implement an escape sequence for the separator + + +/** + * Get a named key from a key code. + * For example: + * keyFromCode(65) returns 'A' + * keyFromCode(13) returns 'Enter' + * @param {string} code + * @return {string} + */ +export function nameFromKeyCode(code) { + return codes[code] || '' +} + +/** + * Get the active key combination from a keyboard event. + * For example returns "Ctrl+Shift+Up" or "Ctrl+A" + * @param {KeyboardEvent} event + * @return {string} + */ +export function keyComboFromEvent (event) { + let combi = [] + + if (event.ctrlKey) { combi.push('Ctrl') } + if (event.metaKey) { combi.push('Command') } + if (event.altKey) { combi.push(isMac ? 'Option' : 'Alt') } + if (event.shiftKey) { combi.push('Shift') } + + const keyName = nameFromKeyCode(event.which) + if (!metaCodes[keyName]) { // prevent output like 'Ctrl+Ctrl' + combi.push(keyName) + } + + return combi.join('+') +} + +/** + * Create a function which can quickly find a keyBinding from a set of + * keyBindings. + * @param {Object.} keyBindings + * @return {function} Returns a findKeyBinding function + */ +export function createFindKeyBinding (keyBindings) { + // turn the map with key bindings by name (multiple per binding) into a map by key combo + const keyCombos = {} + Object.keys(keyBindings).forEach ((name) => { + keyBindings[name].forEach(combo => keyCombos[normalizeKeyCombo(combo)] = name) + }) + + return function findKeyBinding (event) { + const keyCombo = keyComboFromEvent(event) + + return keyCombos[normalizeKeyCombo(keyCombo)] || null + } +} + +/** + * Normalize a key combo: + * + * - to upper case + * - replace aliases like "?" with "/" + * + * @param {string} combo + * @return {string} + */ +function normalizeKeyCombo (combo) { + const upper = combo.toUpperCase() + + const last = upper[upper.length - 1] + if (last in aliases) { + return upper.substring(0, upper.length - 1) + aliases[last] + } + + return upper +} + +const isMac = window.navigator.platform.toUpperCase().indexOf('MAC') >= 0 + +const metaCodes = { + 'Ctrl': true, + 'Command': true, + 'Alt': true, + 'Option': true, + 'Shift': true +} + +const codes = { + '8': 'Backspace', + '9': 'Tab', + '13': 'Enter', + '16': 'Shift', + '17': 'Ctrl', + '18': 'Alt', + '19': 'Pause_Break', + '20': 'Caps_Lock', + '27': 'Escape', + '33': 'Page_Up', + '34': 'Page_Down', + '35': 'End', + '36': 'Home', + '37': 'Left', + '38': 'Up', + '39': 'Right', + '40': 'Down', + '45': 'Insert', + '46': 'Delete', + '48': '0', + '49': '1', + '50': '2', + '51': '3', + '52': '4', + '53': '5', + '54': '6', + '55': '7', + '56': '8', + '57': '9', + '65': 'A', + '66': 'B', + '67': 'C', + '68': 'D', + '69': 'E', + '70': 'F', + '71': 'G', + '72': 'H', + '73': 'I', + '74': 'J', + '75': 'K', + '76': 'L', + '77': 'M', + '78': 'N', + '79': 'O', + '80': 'P', + '81': 'Q', + '82': 'R', + '83': 'S', + '84': 'T', + '85': 'U', + '86': 'V', + '87': 'W', + '88': 'X', + '89': 'Y', + '90': 'Z', + '91': 'Left_Window_Key', + '92': 'Right_Window_Key', + '93': 'Select_Key', + '96': 'Numpad_0', + '97': 'Numpad_1', + '98': 'Numpad_2', + '99': 'Numpad_3', + '100': 'Numpad_4', + '101': 'Numpad_5', + '102': 'Numpad_6', + '103': 'Numpad_7', + '104': 'Numpad_8', + '105': 'Numpad_9', + '106': 'Numpad_*', + '107': 'Numpad_+', + '109': 'Numpad_-', + '110': 'Numpad_.', + '111': 'Numpad_/', + '112': 'F1', + '113': 'F2', + '114': 'F3', + '115': 'F4', + '116': 'F5', + '117': 'F6', + '118': 'F7', + '119': 'F8', + '120': 'F9', + '121': 'F10', + '122': 'F11', + '123': 'F12', + '144': 'Num_Lock', + '145': 'Scroll_Lock', + '186': ';', + '187': '=', + '188': ',', + '189': '-', + '190': '.', + '191': '/', + '192': '`', + '219': '[', + '220': '\\', + '221': ']', + '222': '\'' +} + +// all secondary characters of the keyboard buttons (used via Shift) +const aliases = { + '~': '`', + '!': '1', + '@': '2', + '#': '3', + '$': '4', + '%': '5', + '^': '6', + '&': '7', + '*': '8', + '(': '9', + ')': '0', + '_': '-', + '+': '=', + '{': '[', + '}': ']', + '|': '\\', + ':': ';', + '"': '', + '<': ',', + '>': '.', + '?': '/' +} diff --git a/src/search.js b/src/utils/search.js similarity index 92% rename from src/search.js rename to src/utils/search.js index 05359e3..e64c876 100644 --- a/src/search.js +++ b/src/utils/search.js @@ -1,4 +1,4 @@ -import { valueType } from './utils/typeUtils' +import { valueType } from './typeUtils.js' export const SEARCH_PROPERTY = '$jse:search:property' export const SEARCH_VALUE = '$jse:search:value' @@ -38,11 +38,6 @@ function createOrAdd(object, key, value) { if (object) { object[key] = value return object - - // return { - // ...object, - // [key]: value - // } } else { return { [key]: value