diff --git a/src/App.scss b/src/App.scss index 4e300a6..6b25823 100644 --- a/src/App.scss +++ b/src/App.scss @@ -3,5 +3,4 @@ -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; color: #2c3e50; - margin-top: 60px; } diff --git a/src/App.tsx b/src/App.tsx index c4d49d1..6819529 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ import { defineComponent, reactive, - watchEffect, + ref, } from '@vue/composition-api'; import Schlechtenburg from '@components/Schlechtenburg'; import { BlockData } from './components/TreeElement'; @@ -12,6 +12,7 @@ export default defineComponent({ name: 'App', setup() { + const activeTab = ref('edit'); const block = reactive({ name: 'sb-layout', blockId: `${+(new Date())}`, @@ -23,20 +24,39 @@ export default defineComponent({ return () => (
- { - block.name = newBlock.name; - block.blockId = newBlock.blockId; - block.data = newBlock.data; + change: ($event: Event) => { + activeTab.value = ($event.target as HTMLSelectElement).value; }, }, }} + > + + + + + { + block.name = newBlock.name; + block.blockId = newBlock.blockId; + block.data = newBlock.data; + }} /> -
{JSON.stringify(block, null, 2)}
+ + +
+          {JSON.stringify(block, null, 2)}
+        
); }, diff --git a/src/components/Schlechtenburg.scss b/src/components/Schlechtenburg.scss new file mode 100644 index 0000000..461e2fc --- /dev/null +++ b/src/components/Schlechtenburg.scss @@ -0,0 +1,5 @@ +.sb-main { + padding: 50px 20px; + + background-color: var(--bg); +} diff --git a/src/components/Schlechtenburg.tsx b/src/components/Schlechtenburg.tsx index 8ab7a64..f61d9e9 100644 --- a/src/components/Schlechtenburg.tsx +++ b/src/components/Schlechtenburg.tsx @@ -9,6 +9,8 @@ import { model, ActiveBlock, BlockData, + SbMode, + Mode, BlockDefinition, BlockLibraryDefinition, BlockLibrary, @@ -21,9 +23,13 @@ import SbParagraph from '@user/Paragraph/index'; import SbImage from '@user/Image/index'; import SbHeading from '@user/Heading/index'; +import './Schlechtenburg.scss'; + export interface SchlechtenburgProps { customBlocks: BlockDefinition[]; + eventUpdate: (b?: BlockData) => void; block: BlockData; + mode: SbMode; } export default defineComponent({ @@ -32,11 +38,25 @@ export default defineComponent({ model, props: { - customBlocks: { type: (null as unknown) as PropType, default: () => [] }, - block: { type: (null as unknown) as PropType, required: true }, + customBlocks: { type: Array as PropType, default: () => [] }, + block: { type: Object as PropType, 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); provide(ActiveBlock, activeBlock); @@ -56,15 +76,12 @@ export default defineComponent({ provide(BlockLibrary, blockLibrary); return () => ( - context.emit('update', block), - }, - }} - /> +
+ +
); }, }); diff --git a/src/components/TreeElement.ts b/src/components/TreeElement.ts index e7bcf60..c782da8 100644 --- a/src/components/TreeElement.ts +++ b/src/components/TreeElement.ts @@ -6,9 +6,6 @@ import { computed, } from '@vue/composition-api'; -export const ActiveBlock = Symbol('Schlechtenburg active block'); -export const BlockLibrary = Symbol('Schlechtenburg block library'); - export interface BlockDefinition { name: string; getDefaultData: any; @@ -38,16 +35,32 @@ export const model = { export const blockProps = { blockId: { type: String, required: true }, + eventUpdate: { + type: (Function as unknown) as (b: any) => void, + default: () => () => undefined, + }, 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() { + const mode = inject(Mode, ref(SbMode.Edit)); 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) { const activeBlockId: Ref = inject(ActiveBlock, ref(null)); const isActive = computed(() => activeBlockId.value === currentBlockId); diff --git a/src/components/internal/Block.scss b/src/components/internal/Block.scss index 1349d1e..1e0a32c 100644 --- a/src/components/internal/Block.scss +++ b/src/components/internal/Block.scss @@ -5,7 +5,7 @@ justify-items: stretch; min-height: 50px; - > .sb-toolbar { + > * > .sb-toolbar { opacity: 0; pointer-events: none; } @@ -13,10 +13,9 @@ &_active { outline: 1px solid var(--grey-2); - > .sb-toolbar { + > * > .sb-toolbar { opacity: 1; pointer-events: all; - outline: 1px solid var(--grey-2); } } } diff --git a/src/components/internal/Block.tsx b/src/components/internal/Block.tsx index b81a7ae..b7d9833 100644 --- a/src/components/internal/Block.tsx +++ b/src/components/internal/Block.tsx @@ -7,7 +7,6 @@ import { BlockData, useDynamicBlocks, useActivation, - BlockDefinition, } from '@components/TreeElement'; import './Block.scss'; @@ -17,6 +16,18 @@ export default defineComponent({ props: { block: { type: (null as unknown) as PropType, 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) { @@ -28,7 +39,7 @@ export default defineComponent({ })); const onChildUpdate = (updated: {[key: string]: any}) => { - context.emit('update', { + props.eventUpdate({ ...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 () => context.emit('insert-block', block), - 'append-block': (block: BlockDefinition) => context.emit('append-block', block), - }, - nativeOn: { - click: ($event: MouseEvent) => { - $event.stopPropagation(); - activate(); + return () =>
+
+
+ ; + nativeOn: { + click: ($event: MouseEvent) => { + $event.stopPropagation(); + activate(); + }, + }, + }} + /> +
; }, }); diff --git a/src/components/internal/BlockPicker.scss b/src/components/internal/BlockPicker.scss index e69de29..40ef556 100644 --- a/src/components/internal/BlockPicker.scss +++ b/src/components/internal/BlockPicker.scss @@ -0,0 +1,2 @@ +.sb-block-picker { +} diff --git a/src/components/internal/BlockPicker.tsx b/src/components/internal/BlockPicker.tsx index 0b29175..e2e749d 100644 --- a/src/components/internal/BlockPicker.tsx +++ b/src/components/internal/BlockPicker.tsx @@ -1,9 +1,16 @@ -import { computed, defineComponent } from '@vue/composition-api'; +import { + computed, + defineComponent, + ref, +} from '@vue/composition-api'; import { useDynamicBlocks, BlockDefinition, } from '../TreeElement'; +import SbButton from './Button'; +import SbModal from './Modal'; + import './BlockPicker.scss'; export default defineComponent({ @@ -12,26 +19,45 @@ export default defineComponent({ props: {}, setup(props, context) { + const open = ref(false); const { customBlocks } = useDynamicBlocks(); 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 () => ( -
- {...blockList.value.map((block: BlockDefinition) => ( - - ))} +
$event.stopPropagation()} + > + { + open.value = true; + console.log(open); + }} + >Add a block + { + open.value = false; + }} + > + {...blockList.value.map((block: BlockDefinition) => ( + {block.name} + ))} +
); }, diff --git a/src/components/internal/Button.scss b/src/components/internal/Button.scss new file mode 100644 index 0000000..625ae48 --- /dev/null +++ b/src/components/internal/Button.scss @@ -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); + } +} diff --git a/src/components/internal/Button.tsx b/src/components/internal/Button.tsx new file mode 100644 index 0000000..4d447b2 --- /dev/null +++ b/src/components/internal/Button.tsx @@ -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 () => ( + + ); + }, +}); diff --git a/src/components/internal/Modal.scss b/src/components/internal/Modal.scss new file mode 100644 index 0000000..c336a80 --- /dev/null +++ b/src/components/internal/Modal.scss @@ -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; + } +} diff --git a/src/components/internal/Modal.tsx b/src/components/internal/Modal.tsx new file mode 100644 index 0000000..75298ef --- /dev/null +++ b/src/components/internal/Modal.tsx @@ -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 () => ( +
+
{ + $event.stopPropagation(); + props.eventClose(); + }} + > +
+ {context.slots.default()} +
+
+
+ ); + }, +}); diff --git a/src/components/internal/Toolbar.scss b/src/components/internal/Toolbar.scss index 91e998e..5266a67 100644 --- a/src/components/internal/Toolbar.scss +++ b/src/components/internal/Toolbar.scss @@ -1,7 +1,7 @@ .sb-toolbar { position: absolute; bottom: 100%; - width: 100%; - height: 40px; - background-color: var(--grey-1); + width: auto; + max-width: 100%; + height: auto; } diff --git a/src/components/user/Image/edit.tsx b/src/components/user/Image/edit.tsx index ffee726..9df58d3 100644 --- a/src/components/user/Image/edit.tsx +++ b/src/components/user/Image/edit.tsx @@ -34,7 +34,7 @@ export default defineComponent({ }, }, - setup(props: ImageProps) { + setup(props: ImageProps, context) { const localData = reactive({ src: props.data.src, alt: props.data.alt, @@ -56,7 +56,9 @@ export default defineComponent({ const onImageSelect = () => { 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]), + }); } }; diff --git a/src/components/user/Layout/display.tsx b/src/components/user/Layout/display.tsx new file mode 100644 index 0000000..ccc4839 --- /dev/null +++ b/src/components/user/Layout/display.tsx @@ -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, + default: getDefaultData, + }, + }, + + setup(props: LayoutProps, context) { + const classes = computed(() => ({ + 'sb-layout': true, + [`sb-layout_${props.data.orientation}`]: true, + })); + + return () => ( +
+ {...props.data.children.map((child, index) => ( + + ))} +
+ ); + }, +}); diff --git a/src/components/user/Layout/edit.tsx b/src/components/user/Layout/edit.tsx index c17c026..c94cf0d 100644 --- a/src/components/user/Layout/edit.tsx +++ b/src/components/user/Layout/edit.tsx @@ -7,12 +7,13 @@ import { } from '@vue/composition-api'; import { model, - blockProps, useActivation, BlockData, + blockProps, } from '@components/TreeElement'; import SbBlock from '@internal/Block'; +import SbButton from '@internal/Button'; import SbToolbar from '@internal/Toolbar'; import SbBlockPlaceholder from '@internal/BlockPlaceholder'; @@ -31,6 +32,10 @@ export default defineComponent({ props: { ...blockProps, + eventUpdate: { + type: (Function as unknown) as (b?: LayoutData) => void, + default: () => () => undefined, + }, data: { type: (null as unknown) as PropType, default: getDefaultData, @@ -56,14 +61,14 @@ export default defineComponent({ })); const toggleOrientation = () => { - context.emit('update', { + props.eventUpdate({ orientation: localData.orientation === 'vertical' ? 'horizontal' : 'vertical', }); }; const onChildUpdate = (child: BlockData, updated: BlockData) => { const index = localData.children.indexOf(child); - context.emit('update', { + props.eventUpdate({ children: [ ...localData.children.slice(0, index), { @@ -76,7 +81,7 @@ export default defineComponent({ }; const appendBlock = (block: BlockData) => { - context.emit('update', { + props.eventUpdate({ children: [ ...localData.children, block, @@ -99,14 +104,14 @@ export default defineComponent({ return () => (
- + >{localData.orientation} {...localData.children.map((child, index) => ( diff --git a/src/components/user/Layout/index.ts b/src/components/user/Layout/index.ts index 3539233..a8cfd47 100644 --- a/src/components/user/Layout/index.ts +++ b/src/components/user/Layout/index.ts @@ -3,6 +3,6 @@ import { getDefaultData } from './util'; export default { name: 'sb-layout', getDefaultData, - edit: () => import('./edit'), - display: () => import('./edit'), + edit: () => import('./edit.tsx'), + display: () => import('./display.tsx'), }; diff --git a/src/components/user/Layout/style.scss b/src/components/user/Layout/style.scss index c51aa9f..6cde76e 100644 --- a/src/components/user/Layout/style.scss +++ b/src/components/user/Layout/style.scss @@ -1,5 +1,6 @@ .sb-layout { display: flex; + flex-basis: 100%; &_vertical { flex-direction: column; @@ -8,4 +9,8 @@ &_horizontal { flex-direction: row; } + + > * { + flex-basis: 100%; + } } diff --git a/src/components/user/Layout/util.ts b/src/components/user/Layout/util.ts index 9535f4a..642dece 100644 --- a/src/components/user/Layout/util.ts +++ b/src/components/user/Layout/util.ts @@ -10,6 +10,7 @@ export interface LayoutData { export interface LayoutProps extends BlockProps { data: LayoutData; + eventUpdate: (b?: BlockData) => void; } export const getDefaultData: () => LayoutData = () => ({ diff --git a/src/components/user/Paragraph/display.tsx b/src/components/user/Paragraph/display.tsx new file mode 100644 index 0000000..3389083 --- /dev/null +++ b/src/components/user/Paragraph/display.tsx @@ -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, + 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 () => ( +

{props.data.value}

+ ); + }, +}); diff --git a/src/components/user/Paragraph/edit.tsx b/src/components/user/Paragraph/edit.tsx index 4364873..d54f639 100644 --- a/src/components/user/Paragraph/edit.tsx +++ b/src/components/user/Paragraph/edit.tsx @@ -35,6 +35,14 @@ export default defineComponent({ type: (null as unknown) as PropType, 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) { @@ -81,7 +89,7 @@ export default defineComponent({ })); const setAlignment = ($event: Event) => { - context.emit('update', { align: ($event.target as HTMLSelectElement).value }); + props.eventUpdate({ align: ($event.target as HTMLSelectElement).value }); }; const onFocus = () => { @@ -90,7 +98,7 @@ export default defineComponent({ const onBlur = () => { localData.focused = false; - context.emit('update', { + props.eventUpdate({ value: localData.value, }); activate(null); @@ -99,7 +107,7 @@ export default defineComponent({ const onKeypress = ($event: KeyboardEvent) => { if ($event.key === 'Enter') { const blockId = `${+(new Date())}`; - context.emit('insert-block', { + props.eventInsertBlock({ blockId, name: 'sb-paragraph', data: getDefaultData(), diff --git a/src/components/user/Paragraph/index.ts b/src/components/user/Paragraph/index.ts index 712a24c..6a5c756 100644 --- a/src/components/user/Paragraph/index.ts +++ b/src/components/user/Paragraph/index.ts @@ -4,5 +4,5 @@ export default { name: 'sb-paragraph', getDefaultData, edit: () => import('./edit'), - display: () => import('./edit'), + display: () => import('./display'), }; diff --git a/src/components/user/Paragraph/style.scss b/src/components/user/Paragraph/style.scss index 82ad7d2..f24d14e 100644 --- a/src/components/user/Paragraph/style.scss +++ b/src/components/user/Paragraph/style.scss @@ -1,7 +1,9 @@ .sb-paragraph { + flex-basis: 100%; + &__input { display: block; - width: 100%; + flex-basis: 100%; } &_align { diff --git a/src/directives/activation-cover.js b/src/directives/activation-cover.js new file mode 100644 index 0000000..6d916df --- /dev/null +++ b/src/directives/activation-cover.js @@ -0,0 +1,3 @@ +export default { + +}; diff --git a/src/main.scss b/src/main.scss index e327f4e..1a25562 100644 --- a/src/main.scss +++ b/src/main.scss @@ -3,7 +3,7 @@ } html { - --bg: white; + --grey-0: white; --grey-1-t: rgba(0, 0, 0, 0.05); --grey-1: rgb(242, 242, 242); --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: rgb(75, 75, 75); --black: rgba(0, 0, 0, 0.9); + + --bg: var(--grey-1); + --fg: var(--black); + + --interact: #3f9cff; +} + +body { + margin: 0; }