Create dropdown menu (WIP)

This commit is contained in:
Jos de Jong 2020-07-27 13:45:23 +02:00
parent c441663528
commit a6bb790f5e
9 changed files with 206 additions and 15 deletions

View File

@ -0,0 +1,46 @@
@import '../styles.scss';
.menu-dropdown {
position: relative;
overflow: visible;
ul {
margin: 0;
padding: 0;
li {
margin: 0;
padding: 0;
list-style-type : none;
}
}
.items {
display: none;
position: absolute;
top: 100%;
left: 0;
background: white;
z-index: 2;
color: $black;
box-shadow: $box-shadow;
&.visible {
display: block;
}
button {
width: 100%;
text-align: left;
&:hover {
background: $background-gray;
}
&:disabled {
color: $gray;
background: unset;
}
}
}
}

View File

@ -0,0 +1,63 @@
<script>
import Icon from 'svelte-awesome'
import { faCaretDown } from '@fortawesome/free-solid-svg-icons'
import { onDestroy, onMount } from 'svelte'
import { keyComboFromEvent} from '../utils/keyBindings.js'
/** @type {MenuDropdownItem[]} */
export let items = []
export let title = null
export let width = '120px'
export let visible = false
export let disabled = false
function toggleShow (event) {
event.stopPropagation()
visible = !visible
}
function handleClick () {
visible = false
}
function handleKeyDown (event) {
const combo = keyComboFromEvent(event)
if (combo === 'Escape') {
event.preventDefault()
visible = false
}
}
onMount(() => {
document.addEventListener('click', handleClick)
document.addEventListener('keydown', handleKeyDown)
})
onDestroy(() => {
document.removeEventListener('click', handleClick)
document.removeEventListener('keydown', handleKeyDown)
})
</script>
<div class="menu-dropdown" title={title} on:click={handleClick}>
<slot name="defaultItem"></slot>
<button on:click={toggleShow}>
<Icon data={faCaretDown} />
</button>
<div class="items" class:visible style="width: {width};">
<ul>
{#each items as item}
<li>
<button on:click={item.onClick} disabled={disabled}>
{item.text}
</button>
</li>
{/each}
</ul>
</div>
</div>
<style src="./DropdownMenu.scss"></style>

View File

@ -45,8 +45,6 @@
let selection = null let selection = null
let clipboard = null let clipboard = null
$: hasSelectionContents = selection != null && selection.paths != null
$: hasClipboardContents = clipboard != null && selection != null
$: state = syncState(doc, state, [], (path) => path.length < 1) $: state = syncState(doc, state, [], (path) => path.length < 1)
@ -209,6 +207,15 @@
} }
} }
function handleInsert() {
if (selection != null) {
console.log('insert', { selection })
// TODO: impelemnt insert
}
}
// TODO: cleanup // TODO: cleanup
$: console.log('doc', doc) $: console.log('doc', doc)
$: console.log('state', state) $: console.log('state', state)
@ -382,6 +389,10 @@
event.preventDefault() event.preventDefault()
handleDuplicate() handleDuplicate()
} }
if (combo === 'Ctrl+Insert' || combo === 'Command+Insert') {
event.preventDefault()
handleInsert()
}
if (combo === 'Escape') { if (combo === 'Escape') {
event.preventDefault() event.preventDefault()
selection = null selection = null
@ -433,9 +444,9 @@
searchText={searchText} searchText={searchText}
searchResult={searchResult} searchResult={searchResult}
bind:showSearch bind:showSearch
hasSelectionContents={hasSelectionContents} selection={selection}
hasClipboardContents={hasClipboardContents} clipboard={clipboard}
onCut={handleCut} onCut={handleCut}
onCopy={handleCopy} onCopy={handleCopy}
onPaste={handlePaste} onPaste={handlePaste}

View File

@ -1,6 +1,7 @@
<script> <script>
import { debounce, isEqual } from 'lodash-es' import { debounce, isEqual } from 'lodash-es'
import { rename } from '../logic/operations.js' import { rename } from '../logic/operations.js'
import { singleton } from './singleton.js'
import { import {
DEBOUNCE_DELAY, DEBOUNCE_DELAY,
DEFAULT_LIMIT, DEFAULT_LIMIT,
@ -39,12 +40,6 @@
export let onSelect export let onSelect
export let selection export let selection
const singleton = {
mousedown: false,
selectionAnchor: null, // Path
selectionFocus: null // Path
}
$: expanded = state && state[STATE_EXPANDED] $: expanded = state && state[STATE_EXPANDED]
$: limit = state && state[STATE_LIMIT] $: limit = state && state[STATE_LIMIT]
$: props = state && state[STATE_PROPS] $: props = state && state[STATE_PROPS]

View File

@ -1,13 +1,14 @@
<script> <script>
import Icon from 'svelte-awesome' import Icon from 'svelte-awesome'
import { faCut, faClone, faCopy, faPaste, faSearch, faUndo, faRedo } from '@fortawesome/free-solid-svg-icons' import { faCut, faClone, faCopy, faPaste, faSearch, faUndo, faRedo, faPlus } from '@fortawesome/free-solid-svg-icons'
import SearchBox from './SearchBox.svelte' import SearchBox from './SearchBox.svelte'
import DropdownMenu from './DropdownMenu.svelte'
export let searchText export let searchText
export let searchResult export let searchResult
export let showSearch = false export let showSearch = false
export let hasSelectionContents export let selection
export let hasClipboardContents export let clipboard
export let historyState export let historyState
export let onCut export let onCut
@ -21,6 +22,10 @@
export let onNextSearchResult export let onNextSearchResult
export let onPreviousSearchResult export let onPreviousSearchResult
$: hasSelection = selection != null
$: hasSelectionContents = selection != null && selection.paths != null
$: hasClipboardContents = clipboard != null && selection != null
function handleToggleSearch() { function handleToggleSearch() {
showSearch = !showSearch showSearch = !showSearch
} }
@ -30,6 +35,36 @@
onSearchText('') onSearchText('')
} }
function handleInsertValue () {
console.log('TODO: insert value')
}
/** @type {MenuDropdownItem[]} */
const insertItems = [
{
text: "Insert value",
onClick: handleInsertValue,
default: true
},
{
text: "Insert object",
onClick: () => {
console.log('TODO: insert object')
}
},
{
text: "Insert array",
onClick: () => {
console.log('TODO: insert array')
}
},
{
text: "Insert structure",
onClick: () => {
console.log('TODO: insert structure')
}
}
]
</script> </script>
<div class="menu"> <div class="menu">
@ -69,6 +104,21 @@
<Icon data={faClone} /> <Icon data={faClone} />
</button> </button>
<DropdownMenu
items={insertItems}
title="Insert new value (Ctrl+Insert)"
disabled={!hasSelection}
>
<button
class="button insert"
slot="defaultItem"
on:click={handleInsertValue}
disabled={!hasSelection}
>
<Icon data={faPlus} />
</button>
</DropdownMenu>
<div class="separator"></div> <div class="separator"></div>
<button <button

View File

@ -6,7 +6,7 @@ $search-size: 24px;
border: 2px solid $theme-color; border: 2px solid $theme-color;
border-radius: $border-radius; border-radius: $border-radius;
background: $white; background: $white;
box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.24); box-shadow: $box-shadow;
.search-form { .search-form {
display: flex; display: flex;

View File

@ -0,0 +1,6 @@
// used by JSONNode during dragging
export const singleton = {
mousedown: false,
selectionAnchor: null, // Path
selectionFocus: null // Path
}

View File

@ -26,6 +26,7 @@ $gray: #9d9d9d;
$gray-icon: $gray; $gray-icon: $gray;
$light-gray: #c0c0c0; $light-gray: #c0c0c0;
$selection-background: #e0e0e0; $selection-background: #e0e0e0;
$background-gray: #f5f5f5;
$line-height: 18px; $line-height: 18px;
$indentation-width: 18px; // IMPORTANT: keep in sync with js constant INDENTATION_WIDTH $indentation-width: 18px; // IMPORTANT: keep in sync with js constant INDENTATION_WIDTH
@ -36,3 +37,15 @@ $border-radius: 3px;
$menu-padding: 5px; $menu-padding: 5px;
$bottom-height: 5px; $bottom-height: 5px;
$box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.24);
button {
border: none;
background: transparent;
color: inherit;
cursor: pointer;
font-family: $font-family-menu;
font-size: $font-size;
padding: $menu-padding;
}

View File

@ -75,3 +75,10 @@
/** /**
* @typedef {MultiSelectionSchema | BeforeSelection | AppendSelection} SelectionSchema * @typedef {MultiSelectionSchema | BeforeSelection | AppendSelection} SelectionSchema
*/ */
/**
* @typedef {Object} MenuDropdownItem
* @property {string} text
* @property {function} onClick
* @property {boolean} [default=false]
**/