Slightly better interface
This commit is contained in:
parent
d00383892f
commit
b81f0c6673
|
@ -3,5 +3,4 @@
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
margin-top: 60px;
|
|
||||||
}
|
}
|
||||||
|
|
36
src/App.tsx
36
src/App.tsx
|
@ -1,7 +1,7 @@
|
||||||
import {
|
import {
|
||||||
defineComponent,
|
defineComponent,
|
||||||
reactive,
|
reactive,
|
||||||
watchEffect,
|
ref,
|
||||||
} from '@vue/composition-api';
|
} from '@vue/composition-api';
|
||||||
import Schlechtenburg from '@components/Schlechtenburg';
|
import Schlechtenburg from '@components/Schlechtenburg';
|
||||||
import { BlockData } from './components/TreeElement';
|
import { BlockData } from './components/TreeElement';
|
||||||
|
@ -12,6 +12,7 @@ export default defineComponent({
|
||||||
name: 'App',
|
name: 'App',
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
|
const activeTab = ref('edit');
|
||||||
const block = reactive({
|
const block = reactive({
|
||||||
name: 'sb-layout',
|
name: 'sb-layout',
|
||||||
blockId: `${+(new Date())}`,
|
blockId: `${+(new Date())}`,
|
||||||
|
@ -23,20 +24,39 @@ export default defineComponent({
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<Schlechtenburg
|
<select
|
||||||
block={block}
|
value={activeTab.value}
|
||||||
{...{
|
{...{
|
||||||
on: {
|
on: {
|
||||||
update: (newBlock: BlockData) => {
|
change: ($event: Event) => {
|
||||||
block.name = newBlock.name;
|
activeTab.value = ($event.target as HTMLSelectElement).value;
|
||||||
block.blockId = newBlock.blockId;
|
|
||||||
block.data = newBlock.data;
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
|
>
|
||||||
|
<option>edit</option>
|
||||||
|
<option>display</option>
|
||||||
|
<option>json</option>
|
||||||
|
</select>
|
||||||
|
<Schlechtenburg
|
||||||
|
vShow={activeTab.value === 'edit'}
|
||||||
|
block={block}
|
||||||
|
eventUpdate={(newBlock: BlockData) => {
|
||||||
|
block.name = newBlock.name;
|
||||||
|
block.blockId = newBlock.blockId;
|
||||||
|
block.data = newBlock.data;
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<pre><code>{JSON.stringify(block, null, 2)}</code></pre>
|
<Schlechtenburg
|
||||||
|
vShow={activeTab.value === 'display'}
|
||||||
|
block={block}
|
||||||
|
mode="display"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<pre vShow={activeTab.value === 'json'}>
|
||||||
|
<code>{JSON.stringify(block, null, 2)}</code>
|
||||||
|
</pre>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
5
src/components/Schlechtenburg.scss
Normal file
5
src/components/Schlechtenburg.scss
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
.sb-main {
|
||||||
|
padding: 50px 20px;
|
||||||
|
|
||||||
|
background-color: var(--bg);
|
||||||
|
}
|
|
@ -9,6 +9,8 @@ import {
|
||||||
model,
|
model,
|
||||||
ActiveBlock,
|
ActiveBlock,
|
||||||
BlockData,
|
BlockData,
|
||||||
|
SbMode,
|
||||||
|
Mode,
|
||||||
BlockDefinition,
|
BlockDefinition,
|
||||||
BlockLibraryDefinition,
|
BlockLibraryDefinition,
|
||||||
BlockLibrary,
|
BlockLibrary,
|
||||||
|
@ -21,9 +23,13 @@ import SbParagraph from '@user/Paragraph/index';
|
||||||
import SbImage from '@user/Image/index';
|
import SbImage from '@user/Image/index';
|
||||||
import SbHeading from '@user/Heading/index';
|
import SbHeading from '@user/Heading/index';
|
||||||
|
|
||||||
|
import './Schlechtenburg.scss';
|
||||||
|
|
||||||
export interface SchlechtenburgProps {
|
export interface SchlechtenburgProps {
|
||||||
customBlocks: BlockDefinition[];
|
customBlocks: BlockDefinition[];
|
||||||
|
eventUpdate: (b?: BlockData) => void;
|
||||||
block: BlockData;
|
block: BlockData;
|
||||||
|
mode: SbMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -32,11 +38,25 @@ export default defineComponent({
|
||||||
model,
|
model,
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
customBlocks: { type: (null as unknown) as PropType<BlockDefinition[]>, default: () => [] },
|
customBlocks: { type: Array as PropType<BlockDefinition[]>, default: () => [] },
|
||||||
block: { type: (null as unknown) as PropType<BlockData>, required: true },
|
block: { type: Object as PropType<BlockData>, required: true },
|
||||||
|
eventUpdate: {
|
||||||
|
type: (Function as unknown) as (b?: BlockData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
validator(value: string) {
|
||||||
|
return ['edit', 'display'].includes(value);
|
||||||
|
},
|
||||||
|
default: 'edit',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, context) {
|
setup(props: SchlechtenburgProps, context) {
|
||||||
|
const mode = ref(props.mode);
|
||||||
|
provide(Mode, mode);
|
||||||
|
|
||||||
const activeBlock = ref(null);
|
const activeBlock = ref(null);
|
||||||
provide(ActiveBlock, activeBlock);
|
provide(ActiveBlock, activeBlock);
|
||||||
|
|
||||||
|
@ -56,15 +76,12 @@ export default defineComponent({
|
||||||
provide(BlockLibrary, blockLibrary);
|
provide(BlockLibrary, blockLibrary);
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<SbBlock
|
<div class="sb-main">
|
||||||
class="sb-main"
|
<SbBlock
|
||||||
block={props.block}
|
block={props.block}
|
||||||
{...{
|
eventUpdate={props.eventUpdate}
|
||||||
on: {
|
/>
|
||||||
update: (block: BlockDefinition) => context.emit('update', block),
|
</div>
|
||||||
},
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -6,9 +6,6 @@ import {
|
||||||
computed,
|
computed,
|
||||||
} from '@vue/composition-api';
|
} from '@vue/composition-api';
|
||||||
|
|
||||||
export const ActiveBlock = Symbol('Schlechtenburg active block');
|
|
||||||
export const BlockLibrary = Symbol('Schlechtenburg block library');
|
|
||||||
|
|
||||||
export interface BlockDefinition {
|
export interface BlockDefinition {
|
||||||
name: string;
|
name: string;
|
||||||
getDefaultData: any;
|
getDefaultData: any;
|
||||||
|
@ -38,16 +35,32 @@ export const model = {
|
||||||
|
|
||||||
export const blockProps = {
|
export const blockProps = {
|
||||||
blockId: { type: String, required: true },
|
blockId: { type: String, required: true },
|
||||||
|
eventUpdate: {
|
||||||
|
type: (Function as unknown) as (b: any) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
data: { type: Object, default: () => ({}) },
|
data: { type: Object, default: () => ({}) },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum SbMode {
|
||||||
|
Edit = 'edit',
|
||||||
|
Display = 'display',
|
||||||
|
}
|
||||||
|
export const Mode = Symbol('Schlechtenburg mode');
|
||||||
|
export const BlockLibrary = Symbol('Schlechtenburg block library');
|
||||||
export function useDynamicBlocks() {
|
export function useDynamicBlocks() {
|
||||||
|
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];
|
const getBlock = (name: string) => customBlocks[name][mode.value];
|
||||||
|
|
||||||
return { customBlocks, getBlock };
|
return {
|
||||||
|
mode,
|
||||||
|
customBlocks,
|
||||||
|
getBlock,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const ActiveBlock = Symbol('Schlechtenburg active block');
|
||||||
export function useActivation(currentBlockId: string) {
|
export function useActivation(currentBlockId: string) {
|
||||||
const activeBlockId: Ref<string|null> = inject(ActiveBlock, ref(null));
|
const activeBlockId: Ref<string|null> = inject(ActiveBlock, ref(null));
|
||||||
const isActive = computed(() => activeBlockId.value === currentBlockId);
|
const isActive = computed(() => activeBlockId.value === currentBlockId);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
justify-items: stretch;
|
justify-items: stretch;
|
||||||
min-height: 50px;
|
min-height: 50px;
|
||||||
|
|
||||||
> .sb-toolbar {
|
> * > .sb-toolbar {
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
@ -13,10 +13,9 @@
|
||||||
&_active {
|
&_active {
|
||||||
outline: 1px solid var(--grey-2);
|
outline: 1px solid var(--grey-2);
|
||||||
|
|
||||||
> .sb-toolbar {
|
> * > .sb-toolbar {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
pointer-events: all;
|
pointer-events: all;
|
||||||
outline: 1px solid var(--grey-2);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import {
|
||||||
BlockData,
|
BlockData,
|
||||||
useDynamicBlocks,
|
useDynamicBlocks,
|
||||||
useActivation,
|
useActivation,
|
||||||
BlockDefinition,
|
|
||||||
} from '@components/TreeElement';
|
} from '@components/TreeElement';
|
||||||
|
|
||||||
import './Block.scss';
|
import './Block.scss';
|
||||||
|
@ -17,6 +16,18 @@ export default defineComponent({
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
block: { type: (null as unknown) as PropType<BlockData>, required: true },
|
block: { type: (null as unknown) as PropType<BlockData>, required: true },
|
||||||
|
eventUpdate: {
|
||||||
|
type: (Function as unknown) as (b?: BlockData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
eventInsertBlock: {
|
||||||
|
type: (Function as unknown) as (b?: BlockData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
eventAppendBlock: {
|
||||||
|
type: (Function as unknown) as (b?: BlockData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
|
@ -28,7 +39,7 @@ export default defineComponent({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const onChildUpdate = (updated: {[key: string]: any}) => {
|
const onChildUpdate = (updated: {[key: string]: any}) => {
|
||||||
context.emit('update', {
|
props.eventUpdate({
|
||||||
...props.block,
|
...props.block,
|
||||||
data: {
|
data: {
|
||||||
...props.block.data,
|
...props.block.data,
|
||||||
|
@ -37,27 +48,31 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const Block = getBlock(props.block.name).edit as any;
|
const Block = getBlock(props.block.name) as any;
|
||||||
|
|
||||||
return () => <Block
|
return () => <div class={classes.value}>
|
||||||
class={classes.value}
|
<div class="sb-block__edit-cover"></div>
|
||||||
data={props.block.data}
|
<div class="sb-block__mover"></div>
|
||||||
block-id={props.block.blockId}
|
<Block
|
||||||
{...{
|
data={props.block.data}
|
||||||
attrs: context.attrs,
|
block-id={props.block.blockId}
|
||||||
on: {
|
eventUpdate={onChildUpdate}
|
||||||
...context.listeners,
|
eventInsertBlock={props.eventInsertBlock}
|
||||||
update: onChildUpdate,
|
eventAppendBlock={props.eventAppendBlock}
|
||||||
'insert-block': (block: BlockDefinition) => context.emit('insert-block', block),
|
{...{
|
||||||
'append-block': (block: BlockDefinition) => context.emit('append-block', block),
|
attrs: context.attrs,
|
||||||
},
|
on: {
|
||||||
nativeOn: {
|
...context.listeners,
|
||||||
click: ($event: MouseEvent) => {
|
update: onChildUpdate,
|
||||||
$event.stopPropagation();
|
|
||||||
activate();
|
|
||||||
},
|
},
|
||||||
},
|
nativeOn: {
|
||||||
}}
|
click: ($event: MouseEvent) => {
|
||||||
/>;
|
$event.stopPropagation();
|
||||||
|
activate();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
.sb-block-picker {
|
||||||
|
}
|
|
@ -1,9 +1,16 @@
|
||||||
import { computed, defineComponent } from '@vue/composition-api';
|
import {
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
ref,
|
||||||
|
} from '@vue/composition-api';
|
||||||
import {
|
import {
|
||||||
useDynamicBlocks,
|
useDynamicBlocks,
|
||||||
BlockDefinition,
|
BlockDefinition,
|
||||||
} from '../TreeElement';
|
} from '../TreeElement';
|
||||||
|
|
||||||
|
import SbButton from './Button';
|
||||||
|
import SbModal from './Modal';
|
||||||
|
|
||||||
import './BlockPicker.scss';
|
import './BlockPicker.scss';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
|
@ -12,26 +19,45 @@ export default defineComponent({
|
||||||
props: {},
|
props: {},
|
||||||
|
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
|
const open = ref(false);
|
||||||
const { customBlocks } = useDynamicBlocks();
|
const { customBlocks } = useDynamicBlocks();
|
||||||
|
|
||||||
const blockList = computed(() => Object.keys(customBlocks).map((key) => customBlocks[key]));
|
const blockList = computed(() => Object.keys(customBlocks).map((key) => customBlocks[key]));
|
||||||
|
|
||||||
|
const selectBlock = (block: BlockDefinition) => () => {
|
||||||
|
open.value = false;
|
||||||
|
context.emit('picked-block', {
|
||||||
|
name: block.name,
|
||||||
|
blockId: `${+(new Date())}`,
|
||||||
|
data: block.getDefaultData(),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
return () => (
|
return () => (
|
||||||
<div class="sb-block-picker">
|
<div
|
||||||
{...blockList.value.map((block: BlockDefinition) => (
|
class="sb-block-picker"
|
||||||
<button
|
onClick={($event: MouseEvent) => $event.stopPropagation()}
|
||||||
type="button"
|
>
|
||||||
{...{
|
<SbButton
|
||||||
on: {
|
type="button"
|
||||||
click: () => context.emit('picked-block', {
|
onClick={() => {
|
||||||
name: block.name,
|
open.value = true;
|
||||||
blockId: `${+(new Date())}`,
|
console.log(open);
|
||||||
data: block.getDefaultData(),
|
}}
|
||||||
}),
|
>Add a block</SbButton>
|
||||||
},
|
<SbModal
|
||||||
}}
|
open={open.value}
|
||||||
>{block.name}</button>
|
eventClose={() => {
|
||||||
))}
|
open.value = false;
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{...blockList.value.map((block: BlockDefinition) => (
|
||||||
|
<SbButton
|
||||||
|
type="button"
|
||||||
|
onClick={selectBlock(block)}
|
||||||
|
>{block.name}</SbButton>
|
||||||
|
))}
|
||||||
|
</SbModal>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
10
src/components/internal/Button.scss
Normal file
10
src/components/internal/Button.scss
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
.sb-button {
|
||||||
|
border: 0;
|
||||||
|
padding: 8px 12px;
|
||||||
|
background-color: var(--grey-0);
|
||||||
|
border: 1px solid var(--grey-2);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border: 1px solid var(--interact);
|
||||||
|
}
|
||||||
|
}
|
23
src/components/internal/Button.tsx
Normal file
23
src/components/internal/Button.tsx
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { defineComponent } from '@vue/composition-api';
|
||||||
|
|
||||||
|
import './Button.scss';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'sb-button',
|
||||||
|
|
||||||
|
inheritAttrs: false,
|
||||||
|
|
||||||
|
setup(props, context) {
|
||||||
|
return () => (
|
||||||
|
<button
|
||||||
|
class="sb-button"
|
||||||
|
{...{
|
||||||
|
attrs: context.attrs,
|
||||||
|
on: context.listeners,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{context.slots.default()}
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
31
src/components/internal/Modal.scss
Normal file
31
src/components/internal/Modal.scss
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
.sb-modal {
|
||||||
|
&__overlay {
|
||||||
|
background-color: var(--grey-3-t);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 10vh 10vw;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
width: 900px;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
max-height: 100%;
|
||||||
|
background-color: var(--grey-0);
|
||||||
|
padding: 24px 32px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_open #{&}__overlay {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
45
src/components/internal/Modal.tsx
Normal file
45
src/components/internal/Modal.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
computed,
|
||||||
|
ref,
|
||||||
|
} from '@vue/composition-api';
|
||||||
|
|
||||||
|
import './Modal.scss';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'sb-modal',
|
||||||
|
|
||||||
|
props: {
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
eventClose: {
|
||||||
|
type: (Function as unknown) as () => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props, context) {
|
||||||
|
const classes = computed(() => ({
|
||||||
|
'sb-modal': true,
|
||||||
|
'sb-modal_open': props.open,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class={classes.value}>
|
||||||
|
<div
|
||||||
|
class="sb-modal__overlay"
|
||||||
|
onClick={($event: MouseEvent) => {
|
||||||
|
$event.stopPropagation();
|
||||||
|
props.eventClose();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div class="sb-modal__content">
|
||||||
|
{context.slots.default()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,7 +1,7 @@
|
||||||
.sb-toolbar {
|
.sb-toolbar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 100%;
|
bottom: 100%;
|
||||||
width: 100%;
|
width: auto;
|
||||||
height: 40px;
|
max-width: 100%;
|
||||||
background-color: var(--grey-1);
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props: ImageProps) {
|
setup(props: ImageProps, context) {
|
||||||
const localData = reactive({
|
const localData = reactive({
|
||||||
src: props.data.src,
|
src: props.data.src,
|
||||||
alt: props.data.alt,
|
alt: props.data.alt,
|
||||||
|
@ -56,7 +56,9 @@ export default defineComponent({
|
||||||
|
|
||||||
const onImageSelect = () => {
|
const onImageSelect = () => {
|
||||||
if (fileInput.value && fileInput.value.files && fileInput.value.files.length) {
|
if (fileInput.value && fileInput.value.files && fileInput.value.files.length) {
|
||||||
localData.src = window.URL.createObjectURL(fileInput.value.files[0]);
|
context.emit('update', {
|
||||||
|
src: window.URL.createObjectURL(fileInput.value.files[0]),
|
||||||
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
57
src/components/user/Layout/display.tsx
Normal file
57
src/components/user/Layout/display.tsx
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
import {
|
||||||
|
reactive,
|
||||||
|
computed,
|
||||||
|
defineComponent,
|
||||||
|
watch,
|
||||||
|
PropType,
|
||||||
|
} from '@vue/composition-api';
|
||||||
|
import {
|
||||||
|
model,
|
||||||
|
blockProps,
|
||||||
|
useActivation,
|
||||||
|
BlockData,
|
||||||
|
} from '@components/TreeElement';
|
||||||
|
|
||||||
|
import SbBlock from '@internal/Block';
|
||||||
|
import SbToolbar from '@internal/Toolbar';
|
||||||
|
import SbBlockPlaceholder from '@internal/BlockPlaceholder';
|
||||||
|
|
||||||
|
import {
|
||||||
|
LayoutData,
|
||||||
|
LayoutProps,
|
||||||
|
getDefaultData,
|
||||||
|
} from './util';
|
||||||
|
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'sb-layout-display',
|
||||||
|
|
||||||
|
model,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
...blockProps,
|
||||||
|
data: {
|
||||||
|
type: (null as unknown) as PropType<LayoutData>,
|
||||||
|
default: getDefaultData,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props: LayoutProps, context) {
|
||||||
|
const classes = computed(() => ({
|
||||||
|
'sb-layout': true,
|
||||||
|
[`sb-layout_${props.data.orientation}`]: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<div class={classes.value}>
|
||||||
|
{...props.data.children.map((child, index) => (
|
||||||
|
<SbBlock
|
||||||
|
key={child.blockId}
|
||||||
|
block={child}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -7,12 +7,13 @@ import {
|
||||||
} from '@vue/composition-api';
|
} from '@vue/composition-api';
|
||||||
import {
|
import {
|
||||||
model,
|
model,
|
||||||
blockProps,
|
|
||||||
useActivation,
|
useActivation,
|
||||||
BlockData,
|
BlockData,
|
||||||
|
blockProps,
|
||||||
} from '@components/TreeElement';
|
} from '@components/TreeElement';
|
||||||
|
|
||||||
import SbBlock from '@internal/Block';
|
import SbBlock from '@internal/Block';
|
||||||
|
import SbButton from '@internal/Button';
|
||||||
import SbToolbar from '@internal/Toolbar';
|
import SbToolbar from '@internal/Toolbar';
|
||||||
import SbBlockPlaceholder from '@internal/BlockPlaceholder';
|
import SbBlockPlaceholder from '@internal/BlockPlaceholder';
|
||||||
|
|
||||||
|
@ -31,6 +32,10 @@ export default defineComponent({
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
...blockProps,
|
...blockProps,
|
||||||
|
eventUpdate: {
|
||||||
|
type: (Function as unknown) as (b?: LayoutData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
data: {
|
data: {
|
||||||
type: (null as unknown) as PropType<LayoutData>,
|
type: (null as unknown) as PropType<LayoutData>,
|
||||||
default: getDefaultData,
|
default: getDefaultData,
|
||||||
|
@ -56,14 +61,14 @@ export default defineComponent({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const toggleOrientation = () => {
|
const toggleOrientation = () => {
|
||||||
context.emit('update', {
|
props.eventUpdate({
|
||||||
orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical',
|
orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onChildUpdate = (child: BlockData, updated: BlockData) => {
|
const onChildUpdate = (child: BlockData, updated: BlockData) => {
|
||||||
const index = localData.children.indexOf(child);
|
const index = localData.children.indexOf(child);
|
||||||
context.emit('update', {
|
props.eventUpdate({
|
||||||
children: [
|
children: [
|
||||||
...localData.children.slice(0, index),
|
...localData.children.slice(0, index),
|
||||||
{
|
{
|
||||||
|
@ -76,7 +81,7 @@ export default defineComponent({
|
||||||
};
|
};
|
||||||
|
|
||||||
const appendBlock = (block: BlockData) => {
|
const appendBlock = (block: BlockData) => {
|
||||||
context.emit('update', {
|
props.eventUpdate({
|
||||||
children: [
|
children: [
|
||||||
...localData.children,
|
...localData.children,
|
||||||
block,
|
block,
|
||||||
|
@ -99,14 +104,14 @@ export default defineComponent({
|
||||||
return () => (
|
return () => (
|
||||||
<div class={classes.value}>
|
<div class={classes.value}>
|
||||||
<SbToolbar slot="toolbar">
|
<SbToolbar slot="toolbar">
|
||||||
<button
|
<SbButton
|
||||||
type="button"
|
type="button"
|
||||||
{...{
|
{...{
|
||||||
on: {
|
nativeOn: {
|
||||||
click: toggleOrientation,
|
click: toggleOrientation,
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
>{localData.orientation}</button>
|
>{localData.orientation}</SbButton>
|
||||||
</SbToolbar>
|
</SbToolbar>
|
||||||
|
|
||||||
{...localData.children.map((child, index) => (
|
{...localData.children.map((child, index) => (
|
||||||
|
|
|
@ -3,6 +3,6 @@ import { getDefaultData } from './util';
|
||||||
export default {
|
export default {
|
||||||
name: 'sb-layout',
|
name: 'sb-layout',
|
||||||
getDefaultData,
|
getDefaultData,
|
||||||
edit: () => import('./edit'),
|
edit: () => import('./edit.tsx'),
|
||||||
display: () => import('./edit'),
|
display: () => import('./display.tsx'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
.sb-layout {
|
.sb-layout {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-basis: 100%;
|
||||||
|
|
||||||
&_vertical {
|
&_vertical {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -8,4 +9,8 @@
|
||||||
&_horizontal {
|
&_horizontal {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> * {
|
||||||
|
flex-basis: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ export interface LayoutData {
|
||||||
|
|
||||||
export interface LayoutProps extends BlockProps {
|
export interface LayoutProps extends BlockProps {
|
||||||
data: LayoutData;
|
data: LayoutData;
|
||||||
|
eventUpdate: (b?: BlockData) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getDefaultData: () => LayoutData = () => ({
|
export const getDefaultData: () => LayoutData = () => ({
|
||||||
|
|
58
src/components/user/Paragraph/display.tsx
Normal file
58
src/components/user/Paragraph/display.tsx
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import {
|
||||||
|
defineComponent,
|
||||||
|
reactive,
|
||||||
|
computed,
|
||||||
|
ref,
|
||||||
|
Ref,
|
||||||
|
onMounted,
|
||||||
|
watch,
|
||||||
|
PropType,
|
||||||
|
} from '@vue/composition-api';
|
||||||
|
import {
|
||||||
|
model,
|
||||||
|
blockProps,
|
||||||
|
useActivation,
|
||||||
|
} from '@components/TreeElement';
|
||||||
|
|
||||||
|
import SbToolbar from '@internal/Toolbar';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getDefaultData,
|
||||||
|
ParagraphData,
|
||||||
|
ParagraphProps,
|
||||||
|
} from './util';
|
||||||
|
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'sb-paragraph-edit',
|
||||||
|
|
||||||
|
model,
|
||||||
|
|
||||||
|
props: {
|
||||||
|
...blockProps,
|
||||||
|
data: {
|
||||||
|
type: (null as unknown) as PropType<ParagraphData>,
|
||||||
|
default: getDefaultData,
|
||||||
|
},
|
||||||
|
eventUpdate: {
|
||||||
|
type: (Function as unknown) as (b?: ParagraphData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
eventInsertBlock: {
|
||||||
|
type: (Function as unknown) as (b?: ParagraphData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props: ParagraphProps, context) {
|
||||||
|
const classes = computed(() => ({
|
||||||
|
'sb-paragraph': true,
|
||||||
|
[`sb-paragraph_align-${props.data.align}`]: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return () => (
|
||||||
|
<p class={classes}>{props.data.value}</p>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
|
@ -35,6 +35,14 @@ export default defineComponent({
|
||||||
type: (null as unknown) as PropType<ParagraphData>,
|
type: (null as unknown) as PropType<ParagraphData>,
|
||||||
default: getDefaultData,
|
default: getDefaultData,
|
||||||
},
|
},
|
||||||
|
eventUpdate: {
|
||||||
|
type: (Function as unknown) as (b?: ParagraphData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
|
eventInsertBlock: {
|
||||||
|
type: (Function as unknown) as (b?: ParagraphData) => void,
|
||||||
|
default: () => () => undefined,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props: ParagraphProps, context) {
|
setup(props: ParagraphProps, context) {
|
||||||
|
@ -81,7 +89,7 @@ export default defineComponent({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const setAlignment = ($event: Event) => {
|
const setAlignment = ($event: Event) => {
|
||||||
context.emit('update', { align: ($event.target as HTMLSelectElement).value });
|
props.eventUpdate({ align: ($event.target as HTMLSelectElement).value });
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFocus = () => {
|
const onFocus = () => {
|
||||||
|
@ -90,7 +98,7 @@ export default defineComponent({
|
||||||
|
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
localData.focused = false;
|
localData.focused = false;
|
||||||
context.emit('update', {
|
props.eventUpdate({
|
||||||
value: localData.value,
|
value: localData.value,
|
||||||
});
|
});
|
||||||
activate(null);
|
activate(null);
|
||||||
|
@ -99,7 +107,7 @@ export default defineComponent({
|
||||||
const onKeypress = ($event: KeyboardEvent) => {
|
const onKeypress = ($event: KeyboardEvent) => {
|
||||||
if ($event.key === 'Enter') {
|
if ($event.key === 'Enter') {
|
||||||
const blockId = `${+(new Date())}`;
|
const blockId = `${+(new Date())}`;
|
||||||
context.emit('insert-block', {
|
props.eventInsertBlock({
|
||||||
blockId,
|
blockId,
|
||||||
name: 'sb-paragraph',
|
name: 'sb-paragraph',
|
||||||
data: getDefaultData(),
|
data: getDefaultData(),
|
||||||
|
|
|
@ -4,5 +4,5 @@ export default {
|
||||||
name: 'sb-paragraph',
|
name: 'sb-paragraph',
|
||||||
getDefaultData,
|
getDefaultData,
|
||||||
edit: () => import('./edit'),
|
edit: () => import('./edit'),
|
||||||
display: () => import('./edit'),
|
display: () => import('./display'),
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
.sb-paragraph {
|
.sb-paragraph {
|
||||||
|
flex-basis: 100%;
|
||||||
|
|
||||||
&__input {
|
&__input {
|
||||||
display: block;
|
display: block;
|
||||||
width: 100%;
|
flex-basis: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
&_align {
|
&_align {
|
||||||
|
|
3
src/directives/activation-cover.js
Normal file
3
src/directives/activation-cover.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export default {
|
||||||
|
|
||||||
|
};
|
|
@ -3,7 +3,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
--bg: white;
|
--grey-0: white;
|
||||||
--grey-1-t: rgba(0, 0, 0, 0.05);
|
--grey-1-t: rgba(0, 0, 0, 0.05);
|
||||||
--grey-1: rgb(242, 242, 242);
|
--grey-1: rgb(242, 242, 242);
|
||||||
--grey-2-t: rgba(0, 0, 0, 0.1);
|
--grey-2-t: rgba(0, 0, 0, 0.1);
|
||||||
|
@ -15,4 +15,13 @@ html {
|
||||||
--grey-5-t: rgba(0, 0, 0, 0.7);
|
--grey-5-t: rgba(0, 0, 0, 0.7);
|
||||||
--grey-5: rgb(75, 75, 75);
|
--grey-5: rgb(75, 75, 75);
|
||||||
--black: rgba(0, 0, 0, 0.9);
|
--black: rgba(0, 0, 0, 0.9);
|
||||||
|
|
||||||
|
--bg: var(--grey-1);
|
||||||
|
--fg: var(--black);
|
||||||
|
|
||||||
|
--interact: #3f9cff;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue