Added basic tree discovery

This commit is contained in:
Benjamin Bädorf 2021-02-23 00:12:06 +01:00
parent d76f40cf7d
commit 7d6a3730c6
No known key found for this signature in database
GPG key ID: 4406E80E13CD656C
12 changed files with 101 additions and 40 deletions

View file

@ -1,10 +1,18 @@
import { Component } from 'vue'; import { Component } from 'vue';
export interface BlockTree {
name: string;
icon?: string;
children?: BlockTree[];
}
export interface BlockDefinition { export interface BlockDefinition {
name: string; name: string;
icon?: string;
getDefaultData: any; getDefaultData: any;
edit: Component; edit: Component;
display: Component; display: Component;
getChildren?: (block: Block) => Block[],
} }
export interface BlockLibraryDefinition { export interface BlockLibraryDefinition {

View file

@ -73,7 +73,7 @@ export const SbBlock = defineComponent({
}; };
return () => { return () => {
const BlockComponent = getBlock(props.block.name) as any; const BlockComponent = getBlock(props.block.name)?.[mode.value] as any;
if (!BlockComponent) { if (!BlockComponent) {
const MissingBlock = SbMissingBlock[mode.value]; const MissingBlock = SbMissingBlock[mode.value];
@ -84,12 +84,10 @@ export const SbBlock = defineComponent({
} }
if (mode.value === SbMode.Display) { if (mode.value === SbMode.Display) {
return () => ( return <BlockComponent
<BlockComponent data={props.block.data}
data={props.block.data} blockId={props.block.blockId}
blockId={props.block.blockId} />;
/>
);
} }
return <div return <div

View file

@ -31,7 +31,6 @@ export default defineComponent({
}, },
setup(props: MissingBlockProps) { setup(props: MissingBlockProps) {
console.log(props, props.name, props.data, props.blockId);
return () => ( return () => (
<div class="sb-missing-block"> <div class="sb-missing-block">
Missing block: {props.name} Missing block: {props.name}

View file

@ -0,0 +1,18 @@
.sb-context {
position: relative;
}
.sb-context-menu {
display: none;
flex-direction: column;
background: var(--grey-0);
border: 1px solid var(--grey-3);
top: 100%;
left: 0;
margin: 0;
z-index: var(--z-context-menu);
&[open] {
display: flex;
}
}

View file

@ -23,28 +23,33 @@ export const SbContextMenu = defineComponent({
setup(props: ContextMenuProps, context) { setup(props: ContextMenuProps, context) {
const opened = ref(false); const opened = ref(false);
const close = () => { opened.value = false; } const open = () => { opened.value = true; };
const close = () => { opened.value = false; };
const closeOnEscape = ($event: KeyboardEvent) => { const closeOnEscape = ($event: KeyboardEvent) => {
if ($event.key === 'Escape') { if ($event.key === 'Escape') {
close(); close();
} }
}; };
const open = () => { const toggle = () => { opened.value ? close() : open() };
opened.value = true;
document.addEventListener('click', close);
document.addEventListener('keypress', closeOnEscape);
}
const toggle = () => { opened.value = !opened.value; }
watch(opened, () => { watch(opened, (curr, prev) => {
if (!opened.value) { if (curr === prev) {
document.removeEventListener('click', close); return;
document.removeEventListener('keypress', closeOnEscape); }
if (!curr) {
document.body.removeEventListener('click', close);
document.body.removeEventListener('keypress', closeOnEscape);
} else {
setTimeout(() => {
document.body.addEventListener('click', close);
document.body.addEventListener('keypress', closeOnEscape);
});
} }
}); });
return () => ( return () => (
<div class="sb-context-menu"> <div class="sb-context">
{ {
context.slots.context({ context.slots.context({
opened, opened,
@ -55,10 +60,12 @@ export const SbContextMenu = defineComponent({
<SbButton onClick={toggle}>Menu</SbButton> <SbButton onClick={toggle}>Menu</SbButton>
} }
<dialog <dialog
open={opened.value} class="sb-context-menu"
open={opened.value ? true : undefined}
onClose={close} onClose={close}
onClick={($event: Event) => { onClick={($event: Event) => {
// Make sure clicks inside do not autoclose this // Make sure clicks inside do not autoclose this
$event.stopPropagation();
}} }}
> >
{context.slots.default({ {context.slots.default({

View file

@ -4,7 +4,7 @@ import {
} from 'vue'; } from 'vue';
import { Block } from '../blocks'; import { Block } from '../blocks';
import { TreeBlockSelect } from './TreeBlockSelect'; import { SbTreeBlockSelect } from './TreeBlockSelect';
import './MainMenu.scss'; import './MainMenu.scss';
@ -25,8 +25,8 @@ export const SbMainMenu = defineComponent({
setup(props: MainMenuProps, context) { setup(props: MainMenuProps, context) {
return () => ( return () => (
<div class="sb-main-menu"> <div class="sb-main-menu">
<TreeBlockSelect <SbTreeBlockSelect
block={block} block={props.block}
/> />
</div> </div>
); );

View file

@ -67,18 +67,16 @@ export const Schlechtenburg = defineComponent({
provide(BlockLibrary, blockLibrary); provide(BlockLibrary, blockLibrary);
watch(props.block, () => {
console.log('Update', props.block);
});
return () => ( return () => (
<div <div
class="sb-main" class="sb-main"
ref={el} ref={el}
> >
<SbMainMenu {
block={props.block} mode.value === SbMode.Edit
/> ? <SbMainMenu block={props.block} />
: null
}
<SbBlock <SbBlock
block={props.block} block={props.block}
onUpdate={props.onUpdate} onUpdate={props.onUpdate}

View file

@ -1,8 +1,14 @@
import { import {
computed,
defineComponent, defineComponent,
PropType, PropType,
} from 'vue'; } from 'vue';
import { Block } from '../blocks'; import {
Block,
BlockTree,
} from '../blocks';
import { useDynamicBlocks } from '../use-dynamic-blocks';
import { SbContextMenu } from './ContextMenu'; import { SbContextMenu } from './ContextMenu';
import { SbButton } from './Button'; import { SbButton } from './Button';
@ -23,16 +29,34 @@ export const SbTreeBlockSelect = defineComponent({
}, },
setup(props: TreeBlockSelectProps, context) { setup(props: TreeBlockSelectProps, context) {
const { getBlock } = useDynamicBlocks();
const getTreeForBlock = (block: Block): BlockTree => {
const getBlockChildren = getBlock(block.name)?.getChildren;
// TODO: vue-jxs apparently cannot parse arrow functions here
const getChildren = getBlockChildren || function ({ data }) { return data?.children; };
const children = getChildren(block) || [];
return {
name: block.name,
children: children.map(getTreeForBlock),
};
};
const tree = computed(() => getTreeForBlock(props.block));
const treeToHtml = (tree: BlockTree) => <li>
{tree.name}
{tree.children.length ? <ul>{tree.children.map(treeToHtml)}</ul> : null}
</li>;
return () => ( return () => (
<SbContextMenu <SbContextMenu
class="sb-tree-block-select" class="sb-tree-block-select"
v-slots={{ v-slots={{
context: ({ toggle }) => <SbButton onClick={toggle}>Tree</SbButton>, context: ({ toggle }) => <SbButton onClick={toggle}>Tree</SbButton>,
default: () => <ul> default: () => <ul>{treeToHtml(tree.value)}</ul>,
<li>Test</li>
</ul>,
}} }}
></SbContextMenu> />
); );
}, },
}); });

View file

@ -10,7 +10,7 @@ export const BlockLibrary = Symbol('Schlechtenburg block library');
export function useDynamicBlocks() { export function useDynamicBlocks() {
const mode = inject(Mode, ref(SbMode.Edit)); const mode = inject(Mode, ref(SbMode.Edit));
const customBlocks: BlockLibraryDefinition = inject(BlockLibrary, reactive({})); const customBlocks: BlockLibraryDefinition = inject(BlockLibrary, reactive({}));
const getBlock = (name: string) => customBlocks[name]?.[mode.value]; const getBlock = (name: string) => customBlocks[name];
return { return {
mode, mode,

View file

@ -6,4 +6,5 @@ export default {
getDefaultData, getDefaultData,
edit: defineAsyncComponent(() => import('./edit')), edit: defineAsyncComponent(() => import('./edit')),
display: defineAsyncComponent(() => import('./display')), display: defineAsyncComponent(() => import('./display')),
getChildren: (block) => [ block.data.description ],
}; };

View file

@ -49,13 +49,19 @@ export default defineComponent({
SbParagraph, SbParagraph,
]} ]}
key="edit" key="edit"
mode="edit" mode={SbMode.Edit}
/>; />;
case SbMode.Edit: case SbMode.Display:
return <Schlechtenburg return <Schlechtenburg
block={block} block={block}
customBlocks={[
SbLayout,
SbHeading,
SbImage,
SbParagraph,
]}
key="display" key="display"
mode="display" mode={SbMode.Display}
/>; />;
case 'data': case 'data':
return <pre><code>{ JSON.stringify(block, null, 2) }</code></pre>; return <pre><code>{ JSON.stringify(block, null, 2) }</code></pre>;

View file

@ -22,6 +22,8 @@ html {
--fg: var(--black); --fg: var(--black);
--interact: #3f9cff; --interact: #3f9cff;
--z-context-menu: 3000;
} }
body { body {