Skip to content

Commit

Permalink
feat: basic ToolBox (#50)
Browse files Browse the repository at this point in the history
* feat: ToolBox with controllers

* feat: insert table and picture by ToolBar
  • Loading branch information
devlzl authored Jan 10, 2024
1 parent 706d534 commit 9f01a5d
Show file tree
Hide file tree
Showing 17 changed files with 274 additions and 95 deletions.
14 changes: 0 additions & 14 deletions src/BlockHub/PictureBlock/Picture.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { pickFile } from '@Utils/pickFile'
import { type PictureBlock } from './PictureBlock'
import { ref } from 'vue'
Expand All @@ -18,13 +17,6 @@ block.props.events.update.on(({ key, to }) => {
rotate.value = to as number
}
})
async function insertPicture() {
const file = await pickFile('image/*')
if (file) {
block.url = URL.createObjectURL(file)
}
}
</script>

<template>
Expand All @@ -37,12 +29,6 @@ async function insertPicture() {
height: `${height}px`,
}"
>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="insertPicture"
>
add picture
</button>
<input
type="range"
value="0"
Expand Down
2 changes: 1 addition & 1 deletion src/BlockHub/TextBoxBlock/TextBox.vue
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ block.props.events.update.on(({ key, to }) => {
top: `${y}px`,
width: `${width}px`,
height: `${height}px`,
padding: '30px',
padding: '4px',
}"
>
<RichText :textStore="textStore" :bindController="bindController" />
Expand Down
5 changes: 3 additions & 2 deletions src/BlockHub/TextBoxBlock/TextBoxBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { TextStore } from '@Kernel/Store/TextStore'
import { Block } from '../Block/Block'
import { type MapStore } from '@Kernel/Store/MapStore'
import { RichTextController } from '@RichText/RichText'
import { TEXT_BOX_DEFAULT_HEIGHT, TEXT_BOX_DEFAULT_WIDTH } from '@Const/block'

export class TextBoxBlock extends Block {
constructor(x: number, y: number) {
super('TextBox', x, y, 500, 200)
constructor(x: number, y: number, width: number = TEXT_BOX_DEFAULT_WIDTH, height: number = TEXT_BOX_DEFAULT_HEIGHT) {
super('TextBox', x, y, width, height)
;(this.store.get('props') as MapStore).set('text', new TextStore())
}

Expand Down
2 changes: 2 additions & 0 deletions src/Const/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const TEXT_BOX_DEFAULT_WIDTH = 300
export const TEXT_BOX_DEFAULT_HEIGHT = 100
6 changes: 6 additions & 0 deletions src/Kernel/Slide.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { type Block } from '@BlockHub/Block/Block'
import { EventManager } from './EventManager'

export class Slide {
static id = 0

private _id: number
private _blocks: Array<Block>

events = {
blockChange: new EventManager(),
}

constructor(blocks: Array<Block> = []) {
this._id = Slide.id++
this._blocks = blocks
Expand All @@ -21,5 +26,6 @@ export class Slide {

addBlock(block: Block) {
this._blocks.push(block)
this.events.blockChange.emit()
}
}
57 changes: 57 additions & 0 deletions src/Kernel/ToolBox/ToolBox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { EventManager } from '@Kernel/EventManager'
import { controllers } from './controller/_index'

export type ToolType = 'Default' | 'TextBox' | 'Shape' | 'Picture' | 'Table' | 'Pen'

export class ToolBox {
private _currentToolType: ToolType = 'Default'
private _slideElement?: HTMLElement

events = {
toolChange: new EventManager<ToolType>(),
}

mount(slideElement: HTMLElement) {
this._slideElement = slideElement
this._bindEvents()
}

private get _currentController() {
return controllers[this._currentToolType]
}

private _bindEvents() {
const eventNames = ['Click', 'MouseDown', 'MouseMove', 'MouseUp'] as const
const previousHandlerMap = Object.create(null)
const _addListener = () => {
eventNames.forEach((eventName) => {
const handler = this._currentController[`handle${eventName}`].bind(this._currentController)
if (eventName === 'MouseUp') {
document.addEventListener('mouseup', handler)
} else {
this._slideElement?.addEventListener(
eventName.toLowerCase() as Lowercase<(typeof eventNames)[number]>,
handler
)
}
previousHandlerMap[eventName] = handler
})
}
const _removeListener = () => {
eventNames.forEach((eventName) => {
const handler = previousHandlerMap[eventName]
if (eventName === 'MouseUp') {
document.removeEventListener('mouseup', handler)
} else {
this._slideElement?.removeEventListener(eventName.toLowerCase(), previousHandlerMap[eventName])
}
})
}
_addListener()
this.events.toolChange.on((toolType) => {
this._currentToolType = toolType
_removeListener()
_addListener()
})
}
}
16 changes: 16 additions & 0 deletions src/Kernel/ToolBox/controller/DefaultToolController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ToolController } from './_ToolController'

export class DefaultToolController extends ToolController {
handleClick(): void {
console.log('default click')
}
handleMouseDown(): void {
console.log('default mousedown')
}
handleMouseMove(): void {
console.log('default move')
}
handleMouseUp(): void {
console.log('defualt up')
}
}
16 changes: 16 additions & 0 deletions src/Kernel/ToolBox/controller/PictureToolController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { ToolController } from './_ToolController'

export class PictureToolController extends ToolController {
handleClick(): void {
throw new Error('Method not implemented.')
}
handleMouseDown(): void {
throw new Error('Method not implemented.')
}
handleMouseMove(): void {
throw new Error('Method not implemented.')
}
handleMouseUp(): void {
throw new Error('Method not implemented.')
}
}
44 changes: 44 additions & 0 deletions src/Kernel/ToolBox/controller/TextBoxToolController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { TextBoxBlock } from '@BlockHub/TextBoxBlock/TextBoxBlock'
import { ToolController } from './_ToolController'
import { slideManager, toolBox } from '@Kernel/index'
import { TEXT_BOX_DEFAULT_HEIGHT, TEXT_BOX_DEFAULT_WIDTH } from '@Const/block'

export class TextBoxToolController extends ToolController {
private _currentBlock?: TextBoxBlock
private _dragging = false

handleClick() {}

handleMouseDown(event: MouseEvent) {
const { offsetX, offsetY } = event
this._currentBlock = new TextBoxBlock(offsetX, offsetY, 0, 0)
slideManager.currentSlide.addBlock(this._currentBlock)
}

handleMouseMove(event: MouseEvent): void {
const block = this._currentBlock
const { x, y } = (event.currentTarget as HTMLElement).getBoundingClientRect()
const { clientX, clientY } = event
const slideX = clientX - x
const slideY = clientY - y
if (block && slideX > block.x && slideY > block.y) {
this._dragging = true
block.width = slideX - block.x
block.height = slideY - block.y
}
}

handleMouseUp(): void {
const block = this._currentBlock
if (!block) {
return
}
if (this._dragging) {
this._dragging = false
} else {
block.width = TEXT_BOX_DEFAULT_WIDTH
block.height = TEXT_BOX_DEFAULT_HEIGHT
}
toolBox.events.toolChange.emit('Default')
}
}
6 changes: 6 additions & 0 deletions src/Kernel/ToolBox/controller/_ToolController.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export abstract class ToolController {
abstract handleClick(event: MouseEvent): void
abstract handleMouseDown(event: MouseEvent): void
abstract handleMouseMove(event: MouseEvent): void
abstract handleMouseUp(event: MouseEvent): void
}
10 changes: 10 additions & 0 deletions src/Kernel/ToolBox/controller/_index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { DefaultToolController } from './DefaultToolController'
import { PictureToolController } from './PictureToolController'
import { TextBoxToolController } from './TextBoxToolController'
import { ToolController } from './_ToolController'

export const controllers: { [key: string]: ToolController } = {
Default: new DefaultToolController(),
Picture: new PictureToolController(),
TextBox: new TextBoxToolController(),
}
11 changes: 4 additions & 7 deletions src/Kernel/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import { TextBoxBlock } from '@BlockHub/TextBoxBlock/TextBoxBlock'
import { HistoryManager } from './HistoryManager'
import { Slide } from './Slide'
import { TableBlock } from '@BlockHub/TableBlock/TableBlock'
import { PictureBlock } from '@BlockHub/PictureBlock/PictureBlock'
import { BlockSelection } from './BlockSelection'
import { EventManager } from './EventManager'
import { TextAtom } from './Store/TextStore'
import { SlideManager } from './SlideManager'
import { ToolBox } from './ToolBox/ToolBox'

interface RichTextStateChange {
selection: {
Expand All @@ -18,11 +17,7 @@ interface RichTextStateChange {

export type SlideMode = 'edit' | 'start' | 'current'

export const slideManager = new SlideManager([
new Slide([new TextBoxBlock(100, 50), new TableBlock(400, 300, 4, 3), new PictureBlock(100, 300)]),
new Slide([new TextBoxBlock(100, 300)]),
new Slide([new TextBoxBlock(400, 300)]),
])
export const slideManager = new SlideManager([new Slide([new TextBoxBlock(300, 50)])])

export const richTextObserver = new EventManager<RichTextStateChange>()

Expand All @@ -31,3 +26,5 @@ export const history = new HistoryManager()
export const selectionBlk = new BlockSelection()

export const slideShowMode = new EventManager<SlideMode>()

export const toolBox = new ToolBox()
39 changes: 27 additions & 12 deletions src/UserInterface/Slide/Slide.vue
Original file line number Diff line number Diff line change
@@ -1,35 +1,50 @@
<script setup lang="ts">
import { ref } from 'vue'
import { ref, onMounted, shallowRef } from 'vue'
import { selectionBlk, slideManager } from '@Kernel/index'
import { BlockViews } from '@BlockHub/BlockHub'
import { type BlkSlctnType } from '@Kernel/BlockSelection'
import { shallowRef } from 'vue'
import { toolBox } from '@Kernel/index'
import { triggerRef } from 'vue'
const slideRef = ref<HTMLElement | null>(null)
onMounted(() => {
toolBox.mount(slideRef.value as HTMLElement)
})
const slide = shallowRef(slideManager.currentSlide)
const blocks = shallowRef(slide.value.blocks)
const updateBlocks = () => {
blocks.value = slide.value.blocks
triggerRef(blocks)
}
slideManager.events.update.on(() => {
slide.value = slideManager.currentSlide
slide.value.events.blockChange.on(updateBlocks)
})
slide.value.events.blockChange.on(() => {
updateBlocks()
})
const handleBlockClick = (evt: PointerEvent, block: BlkSlctnType) => {
if (evt.metaKey) {
return selectionBlk.toggle(block)
}
selectionBlk.focus(block)
// if (evt.metaKey) {
// return selectionBlk.toggle(block)
// }
// selectionBlk.focus(block)
}
const selected = ref(new Map())
selectionBlk.events.update.on((evtType) => {
if (evtType !== 'update') {
return
}
selected.value = new Map(selectionBlk.blocks.map((blk) => [blk.id, blk]))
// if (evtType !== 'update') {
// return
// }
// selected.value = new Map(selectionBlk.blocks.map((blk) => [blk.id, blk]))
})
</script>

<template>
<div id="slide-wrapper" class="relative w-[960px] h-[540px] bg-white shadow-lg">
<div ref="slideRef" id="slide-wrapper" class="relative w-[960px] h-[540px] bg-white shadow-lg">
<component
v-for="block of slide.blocks"
v-for="block of blocks"
:key="block.id"
:is="BlockViews[block.type]"
:block="block"
Expand Down
Loading

0 comments on commit 9f01a5d

Please sign in to comment.