Rename events, added mainmenu

This commit is contained in:
Benjamin Bädorf 2021-02-22 19:13:37 +01:00
parent d64d9e4af7
commit d76f40cf7d
No known key found for this signature in database
GPG key ID: 4406E80E13CD656C
16 changed files with 259 additions and 73 deletions

View file

@ -11,12 +11,12 @@ export interface BlockLibraryDefinition {
[name: string]: BlockDefinition; [name: string]: BlockDefinition;
} }
export interface BlockProps<T> { export interface BlockProps {
blockId: string; blockId: string;
data: T; data: any;
} }
export interface Block<T> extends BlockProps<T> { export interface Block extends BlockProps {
name: string; name: string;
} }

View file

@ -19,12 +19,14 @@ import './Block.scss';
interface BlockProps { interface BlockProps {
block: Block; block: Block;
eventUpdate: (b?: Block) => void; onUpdate: (b?: Block) => void;
eventPrependBlock: (b?: Block) => void; onPrependBlock: (b?: Block) => void;
eventAppendBlock: (b?: Block) => void; onAppendBlock: (b?: Block) => void;
eventRemoveBlock: () => void; onRemoveSelf: () => void;
eventMoveUp: () => void; onMoveBackward: () => void;
eventMoveDown: () => void; onMoveForward: () => void;
onActivateNext: () => void;
onActivatePrevious: () => void;
sortable: string; sortable: string;
} }
@ -40,12 +42,12 @@ export const SbBlock = defineComponent({
type: String, type: String,
default: null, default: null,
}, },
eventUpdate: { type: Function, default: () => {} }, onUpdate: { type: Function, default: () => {} },
eventPrependBlock: { type: Function, default: () => {} }, onPrependBlock: { type: Function, default: () => {} },
eventAppendBlock: { type: Function, default: () => {} }, onAppendBlock: { type: Function, default: () => {} },
eventRemoveBlock: { type: Function, default: () => {} }, onRemoveSelf: { type: Function, default: () => {} },
eventMoveUp: { type: Function, default: () => {} }, onMoveBackward: { type: Function, default: () => {} },
eventMoveDown: { type: Function, default: () => {} }, onMoveForward: { type: Function, default: () => {} },
}, },
setup(props: BlockProps, context) { setup(props: BlockProps, context) {
@ -61,7 +63,7 @@ export const SbBlock = defineComponent({
watch(() => props.block.data, triggerSizeCalculation); watch(() => props.block.data, triggerSizeCalculation);
const onChildUpdate = (updated: {[key: string]: any}) => { const onChildUpdate = (updated: {[key: string]: any}) => {
props.eventUpdate({ props.onUpdate({
...props.block, ...props.block,
data: { data: {
...props.block.data, ...props.block.data,
@ -99,10 +101,12 @@ export const SbBlock = defineComponent({
<BlockComponent <BlockComponent
data={props.block.data} data={props.block.data}
blockId={props.block.blockId} blockId={props.block.blockId}
eventUpdate={onChildUpdate} onUpdate={onChildUpdate}
eventPrependBlock={props.eventPrependBlock} onPrependBlock={props.onPrependBlock}
eventAppendBlock={props.eventAppendBlock} onAppendBlock={props.onAppendBlock}
eventRemoveBlock={props.eventRemoveBlock} onRemoveSelf={props.onRemoveSelf}
onActivatePrevious={props.onActivatePrevious}
onActivateNext={props.onActivateNext}
onClick={($event: MouseEvent) => { onClick={($event: MouseEvent) => {
$event.stopPropagation(); $event.stopPropagation();
activate(); activate();

View file

@ -19,9 +19,9 @@ export const SbBlockOrdering = defineComponent({
type: String, type: String,
default: null, default: null,
}, },
eventRemoveBlock: { type: Function, default: () => {} }, onRemove: { type: Function, default: () => {} },
eventMoveUp: { type: Function, default: () => {} }, onMoveUp: { type: Function, default: () => {} },
eventMoveDown: { type: Function, default: () => {} }, onMoveDown: { type: Function, default: () => {} },
}, },
setup(props) { setup(props) {
@ -55,9 +55,9 @@ export const SbBlockOrdering = defineComponent({
style={styles} style={styles}
onClick={($event: MouseEvent) => $event.stopPropagation()} onClick={($event: MouseEvent) => $event.stopPropagation()}
> >
<SbButton onClick={props.eventMoveUp}>{props.sortable === 'vertical' ? '↑' : '←'}</SbButton> <SbButton onClick={props.onMoveUp}>{props.sortable === 'vertical' ? '↑' : '←'}</SbButton>
<SbButton onClick={props.eventRemoveBlock}>x</SbButton> <SbButton onClick={props.onRemove}>x</SbButton>
<SbButton onClick={props.eventMoveDown}>{props.sortable === 'vertical' ? '↓' : '→'}</SbButton> <SbButton onClick={props.onMoveDown}>{props.sortable === 'vertical' ? '↓' : '→'}</SbButton>
</div> </div>
); );
}, },

View file

@ -46,7 +46,7 @@ export const SbBlockPicker = defineComponent({
<SbModal <SbModal
open={open.value} open={open.value}
onClick={($event: MouseEvent) => $event.stopPropagation()} onClick={($event: MouseEvent) => $event.stopPropagation()}
eventClose={() => { onClose={() => {
open.value = false; open.value = false;
}} }}
> >

View file

@ -0,0 +1,74 @@
import {
watch,
defineComponent,
ref,
} from 'vue';
import { SbButton } from './Button';
import './ContextMenu.scss';
interface ContextMenuProps {
onClose: () => void;
onOpen: () => void;
}
export const SbContextMenu = defineComponent({
name: 'sb-context-menu',
props: {
onClose: { type: Function, default: () => {} },
onOpen: { type: Function, default: () => {} },
},
setup(props: ContextMenuProps, context) {
const opened = ref(false);
const close = () => { opened.value = false; }
const closeOnEscape = ($event: KeyboardEvent) => {
if ($event.key === 'Escape') {
close();
}
};
const open = () => {
opened.value = true;
document.addEventListener('click', close);
document.addEventListener('keypress', closeOnEscape);
}
const toggle = () => { opened.value = !opened.value; }
watch(opened, () => {
if (!opened.value) {
document.removeEventListener('click', close);
document.removeEventListener('keypress', closeOnEscape);
}
});
return () => (
<div class="sb-context-menu">
{
context.slots.context({
opened,
toggle,
close,
open,
}) ||
<SbButton onClick={toggle}>Menu</SbButton>
}
<dialog
open={opened.value}
onClose={close}
onClick={($event: Event) => {
// Make sure clicks inside do not autoclose this
}}
>
{context.slots.default({
opened,
toggle,
close,
open,
}) || null}
</dialog>
</div>
);
},
});

View file

@ -0,0 +1,2 @@
.sb-main-menu {
}

View file

@ -0,0 +1,34 @@
import {
defineComponent,
PropType,
} from 'vue';
import { Block } from '../blocks';
import { TreeBlockSelect } from './TreeBlockSelect';
import './MainMenu.scss';
interface MainMenuProps {
block: Block;
}
export const SbMainMenu = defineComponent({
name: 'sb-main-menu',
props: {
block: {
type: (null as unknown) as PropType<Block>,
required: true,
},
},
setup(props: MainMenuProps, context) {
return () => (
<div class="sb-main-menu">
<TreeBlockSelect
block={block}
/>
</div>
);
},
});

View file

@ -7,7 +7,7 @@ import './Modal.scss';
interface ModalProps { interface ModalProps {
open: boolean; open: boolean;
eventClose: () => void; onClose: () => void;
} }
export const SbModal = defineComponent({ export const SbModal = defineComponent({
@ -18,7 +18,7 @@ export const SbModal = defineComponent({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
eventClose: { type: Function, default: () => {} }, onClose: { type: Function, default: () => {} },
}, },
setup(props: ModalProps, context) { setup(props: ModalProps, context) {
@ -33,7 +33,7 @@ export const SbModal = defineComponent({
class="sb-modal__overlay" class="sb-modal__overlay"
onClick={($event: MouseEvent) => { onClick={($event: MouseEvent) => {
$event.stopPropagation(); $event.stopPropagation();
props.eventClose(); props.onClose();
}} }}
> >
<div class="sb-modal__content"> <div class="sb-modal__content">

View file

@ -18,13 +18,14 @@ import { BlockLibrary } from '../use-dynamic-blocks';
import { EditorDimensions, useResizeObserver } from '../use-resize-observer'; import { EditorDimensions, useResizeObserver } from '../use-resize-observer';
import { ActiveBlock } from '../use-activation'; import { ActiveBlock } from '../use-activation';
import { SbMainMenu } from './MainMenu';
import { SbBlock } from './Block'; import { SbBlock } from './Block';
import './Schlechtenburg.scss'; import './Schlechtenburg.scss';
export interface SchlechtenburgProps { export interface SchlechtenburgProps {
customBlocks: BlockDefinition[]; customBlocks: BlockDefinition[];
eventUpdate: (b: Block<any>) => void; onUpdate: (b: Block<any>) => void;
block: Block<any>; block: Block<any>;
mode: SbMode; mode: SbMode;
} }
@ -37,7 +38,7 @@ export const Schlechtenburg = defineComponent({
props: { props: {
customBlocks: { type: Array as PropType<BlockDefinition[]>, default: () => [] }, customBlocks: { type: Array as PropType<BlockDefinition[]>, default: () => [] },
block: { type: Object as PropType<Block<any>>, required: true }, block: { type: Object as PropType<Block<any>>, required: true },
eventUpdate: { type: Function, default: () => {} }, onUpdate: { type: Function, default: () => {} },
mode: { mode: {
type: String as PropType<SbMode>, type: String as PropType<SbMode>,
validator(value: any) { validator(value: any) {
@ -75,9 +76,12 @@ export const Schlechtenburg = defineComponent({
class="sb-main" class="sb-main"
ref={el} ref={el}
> >
<SbMainMenu
block={props.block}
/>
<SbBlock <SbBlock
block={props.block} block={props.block}
eventUpdate={props.eventUpdate} onUpdate={props.onUpdate}
/> />
</div> </div>
); );

View file

@ -0,0 +1,2 @@
.sb-tree-block-select {
}

View file

@ -0,0 +1,38 @@
import {
defineComponent,
PropType,
} from 'vue';
import { Block } from '../blocks';
import { SbContextMenu } from './ContextMenu';
import { SbButton } from './Button';
import './TreeBlockSelect.scss';
interface TreeBlockSelectProps {
block: Block;
}
export const SbTreeBlockSelect = defineComponent({
name: 'sb-main-menu',
props: {
block: {
type: (null as unknown) as PropType<Block>,
required: true,
},
},
setup(props: TreeBlockSelectProps, context) {
return () => (
<SbContextMenu
class="sb-tree-block-select"
v-slots={{
context: ({ toggle }) => <SbButton onClick={toggle}>Tree</SbButton>,
default: () => <ul>
<li>Test</li>
</ul>,
}}
></SbContextMenu>
);
},
});

View file

@ -29,7 +29,7 @@ export default defineComponent({
props: { props: {
...blockProps, ...blockProps,
eventUpdate: { type: Function, default: () => {} }, onUpdate: { type: Function, default: () => {} },
data: { data: {
type: (null as unknown) as PropType<ImageData>, type: (null as unknown) as PropType<ImageData>,
default: getDefaultData, default: getDefaultData,
@ -61,7 +61,7 @@ export default defineComponent({
if (fileInput.value && fileInput.value.files && fileInput.value.files.length) { if (fileInput.value && fileInput.value.files && fileInput.value.files.length) {
const reader = new FileReader(); const reader = new FileReader();
reader.addEventListener('load', () => { reader.addEventListener('load', () => {
props.eventUpdate({ props.onUpdate({
src: reader.result, src: reader.result,
alt: props.data.alt, alt: props.data.alt,
description: props.data.description, description: props.data.description,
@ -73,7 +73,7 @@ export default defineComponent({
}; };
const onDescriptionUpdate = (description) => { const onDescriptionUpdate = (description) => {
props.eventUpdate({ props.onUpdate({
...props.data, ...props.data,
description, description,
}); });
@ -101,7 +101,7 @@ export default defineComponent({
/> />
<SbBlock <SbBlock
block={localData.description} block={localData.description}
eventUpdate={(updated: Block) => onDescriptionUpdate(updated)} onUpdate={(updated: Block) => onDescriptionUpdate(updated)}
/> />
</> </>
: <SbButton onClick={selectImage}>Select Image</SbButton>} : <SbButton onClick={selectImage}>Select Image</SbButton>}

View file

@ -33,7 +33,7 @@ export default defineComponent({
props: { props: {
...blockProps, ...blockProps,
eventUpdate: { type: Function, default: () => {} }, onUpdate: { type: Function, default: () => {} },
data: { data: {
type: (null as unknown) as PropType<LayoutData>, type: (null as unknown) as PropType<LayoutData>,
default: getDefaultData, default: getDefaultData,
@ -60,7 +60,7 @@ export default defineComponent({
const toggleOrientation = () => { const toggleOrientation = () => {
console.log('toggle'); console.log('toggle');
props.eventUpdate({ props.onUpdate({
orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical', orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical',
}); });
}; };
@ -70,7 +70,7 @@ export default defineComponent({
if (index === -1) { if (index === -1) {
return; return;
} }
props.eventUpdate({ props.onUpdate({
children: [ children: [
...localData.children.slice(0, index), ...localData.children.slice(0, index),
{ {
@ -87,7 +87,7 @@ export default defineComponent({
...localData.children, ...localData.children,
block, block,
]; ];
props.eventUpdate({ children: [...localData.children] }); props.onUpdate({ children: [...localData.children] });
activate(block.blockId); activate(block.blockId);
}; };
@ -97,7 +97,7 @@ export default defineComponent({
block, block,
...localData.children.slice(index + 1), ...localData.children.slice(index + 1),
]; ];
props.eventUpdate({ children: [...localData.children] }); props.onUpdate({ children: [...localData.children] });
activate(block.blockId); activate(block.blockId);
}; };
@ -106,13 +106,25 @@ export default defineComponent({
...localData.children.slice(0, index), ...localData.children.slice(0, index),
...localData.children.slice(index + 1), ...localData.children.slice(index + 1),
]; ];
props.eventUpdate({ children: [...localData.children] }); props.onUpdate({ children: [...localData.children] });
const newActiveIndex = Math.max(index - 1, 0); const newActiveIndex = Math.max(index - 1, 0);
activate(localData.children[newActiveIndex].blockId); activate(localData.children[newActiveIndex].blockId);
}; };
const moveUp = (index: number) => { const activateBlock = (index: number) => {
const safeIndex =
Math.max(
Math.min(
localData.children.length - 1,
index,
),
0,
);
activate(localData.children[safeIndex].blockId);
};
const moveBackward = (index: number) => {
if (index === 0) { if (index === 0) {
return; return;
} }
@ -126,10 +138,10 @@ export default defineComponent({
...localData.children.slice(index + 1), ...localData.children.slice(index + 1),
]; ];
props.eventUpdate({ children: [...localData.children] }); props.onUpdate({ children: [...localData.children] });
}; };
const moveDown = (index: number) => { const moveForward = (index: number) => {
if (index === localData.children.length - 1) { if (index === localData.children.length - 1) {
return; return;
} }
@ -143,7 +155,7 @@ export default defineComponent({
...localData.children.slice(index + 2), ...localData.children.slice(index + 2),
]; ];
props.eventUpdate({ children: [...localData.children] }); props.onUpdate({ children: [...localData.children] });
}; };
return () => ( return () => (
@ -160,17 +172,19 @@ export default defineComponent({
{...{ key: child.blockId }} {...{ key: child.blockId }}
data-order={index} data-order={index}
block={child} block={child}
eventUpdate={(updated: Block) => onChildUpdate(child, updated)} onUpdate={(updated: Block) => onChildUpdate(child, updated)}
eventPrependBlock={(block: Block) => insertBlock(index - 1, block)} onRemoveSelf={() => removeBlock(index)}
eventAppendBlock={(block: Block) => insertBlock(index, block)} onPrependBlock={(block: Block) => insertBlock(index - 1, block)}
removable onAppendBlock={(block: Block) => insertBlock(index, block)}
onActivatePrevious={(block: Block) => activateBlock(index - 1,)}
onActivateNext={(block: Block) => activateBlock(index + 1,)}
> >
{{ {{
'context-toolbar': () => 'context-toolbar': () =>
<SbBlockOrdering <SbBlockOrdering
eventMoveUp={() => moveUp(index)} onMoveBackward={() => moveBackward(index)}
eventMoveDown={() => moveDown(index)} onMoveForward={() => moveForward(index)}
eventRemoveBlock={() => removeBlock(index)} onRemove={() => removeBlock(index)}
sortable={props.sortable} sortable={props.sortable}
/>, />,
}} }}

View file

@ -28,9 +28,11 @@ import './style.scss';
interface ParagraphProps extends BlockProps { interface ParagraphProps extends BlockProps {
data: ParagraphData; data: ParagraphData;
eventUpdate: (b?: ParagraphData) => void; onUpdate: (b?: ParagraphData) => void;
eventAppendBlock: (b?: BlockData) => void; onAppendBlock: (b?: BlockData) => void;
eventRemoveBlock: () => void; onRemoveSelf: () => void;
onActivateNext: () => void;
onActivatePrevious: () => void;
} }
export default defineComponent({ export default defineComponent({
@ -44,9 +46,11 @@ export default defineComponent({
type: (null as unknown) as PropType<ParagraphData>, type: (null as unknown) as PropType<ParagraphData>,
default: getDefaultData, default: getDefaultData,
}, },
eventUpdate: { type: Function, default: () => {} }, onUpdate: { type: Function, default: () => {} },
eventAppendBlock: { type: Function, default: () => {} }, onAppendBlock: { type: Function, default: () => {} },
eventRemoveBlock: { type: Function, default: () => {} }, onRemoveSelf: { type: Function, default: () => {} },
onActivateNext: { type: Function, default: () => {} },
onActivatePrevious: { type: Function, default: () => {} },
}, },
setup(props: ParagraphProps) { setup(props: ParagraphProps) {
@ -98,7 +102,7 @@ export default defineComponent({
})); }));
const setAlignment = ($event: Event) => { const setAlignment = ($event: Event) => {
props.eventUpdate({ props.onUpdate({
value: localData.value, value: localData.value,
align: ($event.target as HTMLSelectElement).value, align: ($event.target as HTMLSelectElement).value,
}); });
@ -111,16 +115,16 @@ export default defineComponent({
const onBlur = () => { const onBlur = () => {
localData.focused = false; localData.focused = false;
props.eventUpdate({ props.onUpdate({
value: localData.value, value: localData.value,
align: localData.align, align: localData.align,
}); });
}; };
const onKeydown = ($event: KeyboardEvent) => { const onKeydown = ($event: KeyboardEvent) => {
if (props.eventAppendBlock && $event.key === 'Enter' && !$event.shiftKey) { if ($event.key === 'Enter' && !$event.shiftKey) {
const blockId = `${+(new Date())}`; const blockId = `${+(new Date())}`;
props.eventAppendBlock({ props.onAppendBlock({
blockId, blockId,
name: 'sb-paragraph', name: 'sb-paragraph',
data: getDefaultData(), data: getDefaultData(),
@ -133,8 +137,23 @@ export default defineComponent({
}; };
const onKeyup = ($event: KeyboardEvent) => { const onKeyup = ($event: KeyboardEvent) => {
if (props.eventRemoveBlock && $event.key === 'Backspace' && localData.value === '') { if ($event.key === 'Backspace' && localData.value === '') {
props.eventRemoveBlock(); props.onRemoveSelf();
}
const selection = window.getSelection();
const node = selection.focusNode;
const childNodes = Array.from(inputEl.value.childNodes);
const index = childNodes.indexOf(node);
if (node === inputEl.value || index === 0 || index === childNodes.length -1) {
switch ($event.key) {
case 'ArrowDown':
props.onActivateNext();
break;
case 'ArrowUp':
props.onActivatePrevious();
break;
}
} }
}; };

View file

@ -8,12 +8,10 @@ import {
import { Schlechtenburg, Block, SbMode } from '../packages/core/lib'; import { Schlechtenburg, Block, SbMode } from '../packages/core/lib';
/* import SbLayout from '../packages/layout/lib';
import SbHeading from '../packages/heading/lib'; import SbHeading from '../packages/heading/lib';
import SbParagraph from '../packages/paragraph/lib'; import SbParagraph from '../packages/paragraph/lib';
import SbImage from '../packages/image/lib'; import SbImage from '../packages/image/lib';
*/
import SbLayout from '../packages/layout/lib';
import './App.scss'; import './App.scss';
@ -41,16 +39,14 @@ export default defineComponent({
case SbMode.Edit: case SbMode.Edit:
return <Schlechtenburg return <Schlechtenburg
block={block} block={block}
eventUpdate={(newBlock: Block<any>) => { onUpdate={(newBlock: Block<any>) => {
block.data = newBlock.data; block.data = newBlock.data;
}} }}
customBlocks={[ customBlocks={[
SbLayout, SbLayout,
/*
SbHeading, SbHeading,
SbImage, SbImage,
SbParagraph, SbParagraph,
*/
]} ]}
key="edit" key="edit"
mode="edit" mode="edit"
@ -67,7 +63,6 @@ export default defineComponent({
}); });
return () => { return () => {
console.log('render App');
return <div id="app"> return <div id="app">
<select <select
value={activeTab.value} value={activeTab.value}