Skip to content

Commit

Permalink
Merge branch 'main' of https://github.com/devlzl/Present into slidebar
Browse files Browse the repository at this point in the history
  • Loading branch information
DexterJun committed Jan 5, 2024
2 parents c236fb5 + 5a6a782 commit 92f046a
Show file tree
Hide file tree
Showing 23 changed files with 504 additions and 107 deletions.
2 changes: 1 addition & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"printWidth": 150,
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": false,
Expand Down
27 changes: 26 additions & 1 deletion src/BlockHub/Block/Block.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { MapStore } from '@Kernel/Store/MapStore'
import { blockHub } from '../BlockHub'
import { RichTextController } from '@RichText/RichText'
import { AttributeValue } from '@Kernel/Store/TextStore'
import { intersectAttributes } from '@Utils/intersectAttributes'

export class Block {
static id = 0

store = new MapStore()
protected store = new MapStore()
protected controllerMap: { [key: string]: RichTextController } = {}

constructor(type: string, x: number, y: number, width: number, height: number, rotate: number = 0) {
const store = this.store
Expand Down Expand Up @@ -73,4 +77,25 @@ export class Block {
set rotate(rotate: number) {
this.props.set('rotate', rotate)
}

getBlockFormat() {
const controllers = Object.values(this.controllerMap)
const attributesList = controllers.map((controller) => controller.getCommonAttributes())
return intersectAttributes(attributesList)
}

formatBlock(name: string, value: AttributeValue) {
for (const controller of Object.values(this.controllerMap)) {
controller.format(name, value)
}
}

getController(...params: any): RichTextController {
// should be overridden by subclass
return {
isFocus: () => false,
getCommonAttributes: () => ({}),
format: (name: string, value: AttributeValue) => {},
}
}
}
2 changes: 1 addition & 1 deletion src/BlockHub/BlockHub.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class BlockHub {
private _blockMap = Object.create(null)

addBlock(block: Block) {
const id = block.store.get('id') as number
const id = block.id
this._blockMap[id] = block
}

Expand Down
20 changes: 5 additions & 15 deletions src/BlockHub/TableBlock/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
import { type TableBlock } from './TableBlock'
import RichText from '@RichText/RichText.vue'
import { type ArrayStore } from '@Kernel/Store/ArrayStore'
import type { AttributeValue, TextStore } from '@Kernel/Store/TextStore'
import { ref, shallowRef } from 'vue'
import type { TextStore } from '@Kernel/Store/TextStore'
import { shallowRef } from 'vue'
const { block } = defineProps<{
block: TableBlock
Expand All @@ -17,31 +17,21 @@ for (const row of block.data) {
}
tableData.value.push(rowData as Array<TextStore>)
}
const currentCell = ref({ row: 0, column: 0 })
function format(name: string, value: AttributeValue) {
const { row, column } = currentCell.value
const controller = block.getController(row, column)
controller.format(name, value)
}
</script>

<template>
<div
class="table absolute"
class="table absolute border border-dashed border-secondary-border"
:style="{
left: `${block.x}px`,
top: `${block.y}px`,
padding: '30px',
}"
>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('bold', true)">
bold
</button>
<input type="color" @input="(event) => format('color', (event.target as HTMLInputElement).value)" />
<div class="row" v-for="(row, rowIndex) of tableData">
<div class="cell inline-block border w-[100px]" v-for="(cell, columnIndex) of row">
<RichText
@click="currentCell = { row: rowIndex, column: columnIndex }"
@click="block.updateCurrentCoord(rowIndex, columnIndex)"
:textStore="(cell as TextStore)"
:bindController="(controller) => block.bindController(rowIndex, columnIndex, controller)"
/>
Expand Down
13 changes: 9 additions & 4 deletions src/BlockHub/TableBlock/TableBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export class TableBlock extends Block {
private _row: number
private _column: number
private _data: ArrayStore
private _controllerMap: { [key: string]: RichTextController } = {}
private _currentCoord = { row: 0, column: 0 }

constructor(x: number, y: number, row: number, column: number) {
super('Table', x, y, 500, 300)
Expand All @@ -33,11 +33,16 @@ export class TableBlock extends Block {
return this._data
}

updateCurrentCoord(row: number, column: number) {
this._currentCoord = { row, column }
}

bindController = (row: number, column: number, controller: RichTextController) => {
this._controllerMap[`${row}-${column}`] = controller
this.controllerMap[`${row}-${column}`] = controller
}

getController(row: number, column: number) {
return this._controllerMap[`${row}-${column}`]
getController() {
const { row, column } = this._currentCoord
return this.controllerMap[`${row}-${column}`]
}
}
35 changes: 1 addition & 34 deletions src/BlockHub/TextBoxBlock/TextBox.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script setup lang="ts">
import { type AttributeValue } from '@Kernel/Store/TextStore'
import { type TextBoxBlock } from './TextBoxBlock'
import RichText from '@RichText/RichText.vue'
import { ref } from 'vue'
Expand All @@ -10,10 +9,6 @@ const { block } = defineProps<{
const { x, y, textStore, bindController } = block
function format(name: string, value: AttributeValue) {
block.controller?.format(name, value)
}
const width = ref(block.width)
const height = ref(block.height)
block.props.events.update.on(({ key, to }) => {
Expand All @@ -33,37 +28,9 @@ block.props.events.update.on(({ key, to }) => {
top: `${y}px`,
width: `${width}px`,
height: `${height}px`,
padding: '30px',
}"
>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('bold', true)">
bold
</button>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('italic', true)">
italic
</button>
<button
class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2"
@click="format('underline', true)"
>
underline
</button>
<button class="border border-primary rounded-sm text-primary bg-white hover:bg-primary hover:text-white m-2 px-2" @click="format('strike', true)">
strike
</button>
<select @change="(event) => format('fontSize', (event.target as HTMLSelectElement).value)">
<option v-for="option in [14, 20, 30, 50, 100]" :value="option">
{{ option }}
</option>
</select>
<input type="color" @input="(event) => format('color', (event.target as HTMLInputElement).value)" />
<input type="color" @input="(event) => format('background', (event.target as HTMLInputElement).value)" />

<br />
width
<input type="range" value="500" min="0" max="1000" @input="(event) => (block.width = Number((event.target as HTMLInputElement).value))" /><br />
height
<input type="range" value="200" min="0" max="400" @input="(event) => (block.height = Number((event.target as HTMLInputElement).value))" /><br />

<RichText :textStore="textStore" :bindController="bindController" />
</div>
</template>
10 changes: 4 additions & 6 deletions src/BlockHub/TextBoxBlock/TextBoxBlock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { type MapStore } from '@Kernel/Store/MapStore'
import { RichTextController } from '@RichText/RichText'

export class TextBoxBlock extends Block {
_controller?: RichTextController

constructor(x: number, y: number) {
super('TextBox', x, y, 500, 200)
;(this.store.get('props') as MapStore).set('text', new TextStore())
Expand All @@ -15,11 +13,11 @@ export class TextBoxBlock extends Block {
return (this.store.get('props') as MapStore).get('text') as TextStore
}

get controller() {
return this._controller
bindController = (controller: RichTextController) => {
this.controllerMap[this.id] = controller
}

bindController = (controller: RichTextController) => {
this._controller = controller
getController() {
return this.controllerMap[this.id]
}
}
61 changes: 61 additions & 0 deletions src/Kernel/BlockSelection.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { EventManager } from './EventManager'
import { Block } from '@BlockHub/Block/Block'

export type BlkSlctnType = Block

export type SelectionEventType = 'select' | 'unselect' | 'update'

export class BlockSelection {
private _selectedBlocks: Array<BlkSlctnType> = []

get blocks(): BlkSlctnType[] {
return this._selectedBlocks
}

events = {
update: new EventManager<SelectionEventType>(),
}

isSelected(block: BlkSlctnType): boolean {
return this._selectedBlocks.findIndex((b) => b === block) !== -1
}

add(block: BlkSlctnType) {
this._selectedBlocks.push(block)
this.events.update.emit('update')
}

remove(block: BlkSlctnType) {
this._selectedBlocks.splice(this._selectedBlocks.indexOf(block), 1)
this.events.update.emit('update')
}

replace(blocks: BlkSlctnType[]) {
this._selectedBlocks = blocks
this.events.update.emit('update')
}

clear() {
this._selectedBlocks = []
this.events.update.emit('update')
}

toggle(block: BlkSlctnType) {
if (this.isSelected(block)) {
this.remove(block)
} else {
this.add(block)
}
}

batchToggle(blocks: BlkSlctnType[]) {
blocks.forEach((block) => {
this.toggle(block)
})
}

focus(block: BlkSlctnType) {
this.clear()
this.add(block)
}
}
34 changes: 33 additions & 1 deletion src/Kernel/Store/TextStore.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { EventManager } from '@Kernel/EventManager'
import { Command } from '@Kernel/HistoryManager'
import { history } from '@Kernel/index'
import { intersectAttributes } from '@Utils/intersectAttributes'

export type AttributeValue = string | number | boolean
interface Attributes {
Expand All @@ -23,6 +24,21 @@ export class TextStore {
let currentAtom = this._store[0]
for (let i = 1; i < this._store.length; i++) {
const atom = this._store[i]
if (atom.text.length === 0) {
continue
}

if (atom.text === '\n') {
result.push(currentAtom)
currentAtom = atom
continue
}
if (currentAtom.text === '\n') {
result.push(currentAtom)
currentAtom = this._store[i]
continue
}

if (JSON.stringify(currentAtom.attributes) === JSON.stringify(atom.attributes)) {
currentAtom.text += atom.text
} else {
Expand Down Expand Up @@ -80,7 +96,10 @@ export class TextStore {
}
const a2 = {
text: newAtom.text,
attributes: { ...atom.attributes, ...newAtom.attributes },
attributes: {
...newAtom.attributes,
...(delta > 0 ? atom.attributes : this._store[i - 1]?.attributes),
},
}
const a3 = {
text: atom.text.slice(delta),
Expand Down Expand Up @@ -115,6 +134,10 @@ export class TextStore {
}

private _formatAtoms(index: number, length: number, attributes: Attributes) {
if (length === 0) {
// controller.formatAll will invoke this no matter _store whether empty
return
}
const left = this._slice(0, index)
const target = this._slice(index, length)
const right = this._slice(index + length, this.length - index - length)
Expand Down Expand Up @@ -143,6 +166,11 @@ export class TextStore {
return structuredClone(this._store)
}

get commonAttributes() {
const attributesList = this.atoms.map((atom) => atom.attributes)
return intersectAttributes(attributesList)
}

insert(index: number, atom: TextAtom) {
const command = new Command(
() => this._insertAtom(index, atom),
Expand All @@ -169,6 +197,10 @@ export class TextStore {
history.exec(command)
}

getAtoms(index: number, length: number) {
return this._slice(index, length)
}

toPlain(): string {
return this._store.reduce((text, current) => text + current.text, '')
}
Expand Down
23 changes: 22 additions & 1 deletion src/Kernel/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,29 @@ 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'

interface RichTextStateChange {
selection: {
index: number
length: number
}
atoms: Array<TextAtom>
}

export const kernel = reactive({
currentIndex: 0,
slides: [new Slide([new TextBoxBlock(100, 50), new TableBlock(400, 300, 4, 3), new PictureBlock(100, 300)])],
slides: [
new Slide([
new TextBoxBlock(100, 50),
new TableBlock(400, 300, 4, 3),
new PictureBlock(100, 300),
]),
],

richTextObserver: new EventManager<RichTextStateChange>(),

get currentSlide() {
return this.slides[this.currentIndex]
Expand All @@ -21,3 +40,5 @@ export const kernel = reactive({
})

export const history = new HistoryManager()

export const selectionBlk = new BlockSelection()
Loading

0 comments on commit 92f046a

Please sign in to comment.