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;
}
export interface BlockProps<T> {
export interface BlockProps {
blockId: string;
data: T;
data: any;
}
export interface Block<T> extends BlockProps<T> {
export interface Block extends BlockProps {
name: string;
}

View file

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

View file

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

View file

@ -46,7 +46,7 @@ export const SbBlockPicker = defineComponent({
<SbModal
open={open.value}
onClick={($event: MouseEvent) => $event.stopPropagation()}
eventClose={() => {
onClose={() => {
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 {
open: boolean;
eventClose: () => void;
onClose: () => void;
}
export const SbModal = defineComponent({
@ -18,7 +18,7 @@ export const SbModal = defineComponent({
type: Boolean,
default: false,
},
eventClose: { type: Function, default: () => {} },
onClose: { type: Function, default: () => {} },
},
setup(props: ModalProps, context) {
@ -33,7 +33,7 @@ export const SbModal = defineComponent({
class="sb-modal__overlay"
onClick={($event: MouseEvent) => {
$event.stopPropagation();
props.eventClose();
props.onClose();
}}
>
<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 { ActiveBlock } from '../use-activation';
import { SbMainMenu } from './MainMenu';
import { SbBlock } from './Block';
import './Schlechtenburg.scss';
export interface SchlechtenburgProps {
customBlocks: BlockDefinition[];
eventUpdate: (b: Block<any>) => void;
onUpdate: (b: Block<any>) => void;
block: Block<any>;
mode: SbMode;
}
@ -37,7 +38,7 @@ export const Schlechtenburg = defineComponent({
props: {
customBlocks: { type: Array as PropType<BlockDefinition[]>, default: () => [] },
block: { type: Object as PropType<Block<any>>, required: true },
eventUpdate: { type: Function, default: () => {} },
onUpdate: { type: Function, default: () => {} },
mode: {
type: String as PropType<SbMode>,
validator(value: any) {
@ -75,9 +76,12 @@ export const Schlechtenburg = defineComponent({
class="sb-main"
ref={el}
>
<SbMainMenu
block={props.block}
/>
<SbBlock
block={props.block}
eventUpdate={props.eventUpdate}
onUpdate={props.onUpdate}
/>
</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: {
...blockProps,
eventUpdate: { type: Function, default: () => {} },
onUpdate: { type: Function, default: () => {} },
data: {
type: (null as unknown) as PropType<ImageData>,
default: getDefaultData,
@ -61,7 +61,7 @@ export default defineComponent({
if (fileInput.value && fileInput.value.files && fileInput.value.files.length) {
const reader = new FileReader();
reader.addEventListener('load', () => {
props.eventUpdate({
props.onUpdate({
src: reader.result,
alt: props.data.alt,
description: props.data.description,
@ -73,7 +73,7 @@ export default defineComponent({
};
const onDescriptionUpdate = (description) => {
props.eventUpdate({
props.onUpdate({
...props.data,
description,
});
@ -101,7 +101,7 @@ export default defineComponent({
/>
<SbBlock
block={localData.description}
eventUpdate={(updated: Block) => onDescriptionUpdate(updated)}
onUpdate={(updated: Block) => onDescriptionUpdate(updated)}
/>
</>
: <SbButton onClick={selectImage}>Select Image</SbButton>}

View file

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

View file

@ -28,9 +28,11 @@ import './style.scss';
interface ParagraphProps extends BlockProps {
data: ParagraphData;
eventUpdate: (b?: ParagraphData) => void;
eventAppendBlock: (b?: BlockData) => void;
eventRemoveBlock: () => void;
onUpdate: (b?: ParagraphData) => void;
onAppendBlock: (b?: BlockData) => void;
onRemoveSelf: () => void;
onActivateNext: () => void;
onActivatePrevious: () => void;
}
export default defineComponent({
@ -44,9 +46,11 @@ export default defineComponent({
type: (null as unknown) as PropType<ParagraphData>,
default: getDefaultData,
},
eventUpdate: { type: Function, default: () => {} },
eventAppendBlock: { type: Function, default: () => {} },
eventRemoveBlock: { type: Function, default: () => {} },
onUpdate: { type: Function, default: () => {} },
onAppendBlock: { type: Function, default: () => {} },
onRemoveSelf: { type: Function, default: () => {} },
onActivateNext: { type: Function, default: () => {} },
onActivatePrevious: { type: Function, default: () => {} },
},
setup(props: ParagraphProps) {
@ -98,7 +102,7 @@ export default defineComponent({
}));
const setAlignment = ($event: Event) => {
props.eventUpdate({
props.onUpdate({
value: localData.value,
align: ($event.target as HTMLSelectElement).value,
});
@ -111,16 +115,16 @@ export default defineComponent({
const onBlur = () => {
localData.focused = false;
props.eventUpdate({
props.onUpdate({
value: localData.value,
align: localData.align,
});
};
const onKeydown = ($event: KeyboardEvent) => {
if (props.eventAppendBlock && $event.key === 'Enter' && !$event.shiftKey) {
if ($event.key === 'Enter' && !$event.shiftKey) {
const blockId = `${+(new Date())}`;
props.eventAppendBlock({
props.onAppendBlock({
blockId,
name: 'sb-paragraph',
data: getDefaultData(),
@ -133,8 +137,23 @@ export default defineComponent({
};
const onKeyup = ($event: KeyboardEvent) => {
if (props.eventRemoveBlock && $event.key === 'Backspace' && localData.value === '') {
props.eventRemoveBlock();
if ($event.key === 'Backspace' && localData.value === '') {
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 SbLayout from '../packages/layout/lib';
import SbHeading from '../packages/heading/lib';
import SbParagraph from '../packages/paragraph/lib';
import SbImage from '../packages/image/lib';
*/
import SbLayout from '../packages/layout/lib';
import './App.scss';
@ -41,16 +39,14 @@ export default defineComponent({
case SbMode.Edit:
return <Schlechtenburg
block={block}
eventUpdate={(newBlock: Block<any>) => {
onUpdate={(newBlock: Block<any>) => {
block.data = newBlock.data;
}}
customBlocks={[
SbLayout,
/*
SbHeading,
SbImage,
SbParagraph,
*/
]}
key="edit"
mode="edit"
@ -67,7 +63,6 @@ export default defineComponent({
});
return () => {
console.log('render App');
return <div id="app">
<select
value={activeTab.value}