Added basic tree discovery
This commit is contained in:
parent
d76f40cf7d
commit
7d6a3730c6
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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({
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
|
|
|
@ -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>
|
/>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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 ],
|
||||||
};
|
};
|
||||||
|
|
12
src/App.tsx
12
src/App.tsx
|
@ -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>;
|
||||||
|
|
|
@ -22,6 +22,8 @@ html {
|
||||||
--fg: var(--black);
|
--fg: var(--black);
|
||||||
|
|
||||||
--interact: #3f9cff;
|
--interact: #3f9cff;
|
||||||
|
|
||||||
|
--z-context-menu: 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
|
|
Loading…
Reference in a new issue