Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.9.0
0.9.1
13 changes: 11 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@
<head>
<meta charset="utf-8" />
<meta name="google" content="notranslate" />
<link rel="icon" href="/favicon.svg" />
<link
rel="icon"
href="/craftbase_favicon_espresso.svg"
media="(prefers-color-scheme: light)"
/>
<link
rel="icon"
href="/craftbase_favicon_amber.svg"
media="(prefers-color-scheme: dark)"
/>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
Expand All @@ -18,7 +27,7 @@
property="og:description"
content="instant whiteboard without signup"
/>
<link rel="apple-touch-icon" href="/favicon.svg" />
<link rel="apple-touch-icon" href="/craftbase_favicon_espresso.svg" />
<link rel="manifest" href="/manifest.json" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
Expand Down
14 changes: 7 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "craftbase",
"version": "0.9.0",
"version": "0.9.1",
"private": true,
"main": "src/lib.ts",
"module": "src/lib.ts",
Expand Down Expand Up @@ -34,9 +34,8 @@
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-responsive": "10.0.0",
"react-router": "^6.30.4",
"react-router-dom": "^6.30.4",
"shell-quote": "^1.8.4",
"react-router": "^7.18.2",
"react-router-dom": "^7.18.2",
"styled-components": "^6.1.11",
"subscriptions-transport-ws": "^0.11.0",
"two.js": "^0.8.13",
Expand All @@ -56,12 +55,13 @@
"resolutions": {
"micromatch": ">=4.0.8",
"form-data": "^4.0.6",
"brace-expansion": ">=5.0.6",
"brace-expansion": ">=5.0.9",
"immutable": ">=5.1.9",
"lodash": ">=4.18.0",
"postcss": ">=8.5.10",
"shell-quote": ">=1.8.4",
"shell-quote": ">=1.10.0",
"vite": "~6.4.3",
"js-yaml": "^4.2.0",
"js-yaml": "^4.3.1",
"@babel/core": "^7.29.6",
"subscriptions-transport-ws/ws": "^7.5.11"
},
Expand Down
1 change: 1 addition & 0 deletions public/craftbase_favicon_amber.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/craftbase_favicon_espresso.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion public/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Craftbase | Wireframe tool",
"icons": [
{
"src": "favicon.svg",
"src": "craftbase_favicon_espresso.svg",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/elements/arrowLine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useImmer } from 'use-immer'
import Two from 'two.js'

import ElementCreator from '../../factory/arrowLine'
import { readOpacity } from '../../utils/canvasUtils'
import { readOpacity, focusSvgElement } from '../../utils/canvasUtils'

import {
updateX1Y1Vertices,
Expand Down Expand Up @@ -50,7 +50,7 @@ function ArrowLine(props: ElementProps): ReactElement {
? document.getElementById(`${stateRefForGroup.current.id}`)
: null
if (getGroupElementFromDOM) {
getGroupElementFromDOM.focus()
focusSvgElement(getGroupElementFromDOM)
getGroupElementFromDOM.addEventListener('blur', onBlurHandler)
}
} else {
Expand Down
4 changes: 2 additions & 2 deletions src/components/elements/divider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { useImmer } from 'use-immer'
import { strokeTypeToDashes } from '../../utils/misc'

import ElementCreator from '../../factory/divider'
import { readOpacity } from '../../utils/canvasUtils'
import { readOpacity, focusSvgElement } from '../../utils/canvasUtils'
import { scheduleRender } from '../../utils/renderScheduler'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -35,7 +35,7 @@ function Divider(props: ElementProps): ReactElement {
? document.getElementById(`${stateRefForGroup.current.id}`)
: null
if (el) {
el.focus()
focusSvgElement(el)
el.addEventListener('blur', onBlurHandler)
}
} else {
Expand Down
3 changes: 2 additions & 1 deletion src/components/elements/groupobject.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
getGroupFill,
layoutStandaloneText,
readOpacity,
focusSvgElement,
} from '../../utils/canvasUtils'

// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -585,7 +586,7 @@ function GroupedObjectWrapper(props: ElementProps): ReactElement {
groupEl.addEventListener('focus', onFocusHandler)
groupEl.addEventListener('blur', onBlurHandler)
groupEl.addEventListener('keydown', onKeyDown)
groupEl.focus()
focusSvgElement(groupEl)
}

// simulating click behavior
Expand Down
4 changes: 2 additions & 2 deletions src/components/elements/line.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useImmer } from 'use-immer'
import Two from 'two.js'

import ElementCreator from '../../factory/line'
import { readOpacity } from '../../utils/canvasUtils'
import { readOpacity, focusSvgElement } from '../../utils/canvasUtils'

import {
updateX1Y1Vertices,
Expand Down Expand Up @@ -55,7 +55,7 @@ function Line(props: ElementProps): ReactElement {
? document.getElementById(`${stateRefForGroup.current.id}`)
: null
if (getGroupElementFromDOM) {
getGroupElementFromDOM.focus()
focusSvgElement(getGroupElementFromDOM)
getGroupElementFromDOM.addEventListener('blur', onBlurHandler)
}
} else {
Expand Down
48 changes: 44 additions & 4 deletions src/components/elements/newText.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,29 @@ function NewText(props: ElementProps): ReactElement {
two.scene.translation.y -
surfaceCenterY * scale0

// Line count the block was laid out with when editing began. The
// editor's top edge is pinned to THIS block's top, so typing extends
// the box downward instead of growing it symmetrically about the
// block's center (which would walk line 1 upward on every newline).
const startLineCount = (textValueRef.current || '').split('\n')
.length

// The scene keeps the line stack centered on the group origin, so a
// new line moves line 1 up by half a line height. Shift the origin
// down by that same half line whenever the count changes, and the
// block's TOP edge — where the user is typing — stays put. Keeps the
// centered layout model (selection box, font resize, group ops all
// rely on it) while making growth read as downward-only.
let laidOutLineCount = startLineCount
const keepBlockTopFixed = (): void => {
const nextCount = (textValueRef.current || '').split('\n').length
if (nextCount === laidOutLineCount) return
const lineH = lineHeightFor(twoText.size || 36)
group.translation.y +=
((nextCount - laidOutLineCount) * lineH) / 2
laidOutLineCount = nextCount
}

// Camera-dependent geometry — recomputed each two.update (pan/zoom).
let cssFontSize = (twoText.size || 36) * scale0
let lineH = Math.ceil(cssFontSize * 1.6)
Expand Down Expand Up @@ -326,9 +349,9 @@ function NewText(props: ElementProps): ReactElement {
measureSpan.style.lineHeight = `${lineH}px`
}

// Pure DOM: size + place the textarea from the current geometry.
// Pure DOM: size the textarea and hang it from its fixed top anchor.
// No two.update here (so it's safe to call from the update handler).
const autoSizeAndCenter = (): void => {
const autoSizeAndPlace = (): void => {
const val = input.value || 'M'
measureSpan.textContent = val

Expand All @@ -344,24 +367,31 @@ function NewText(props: ElementProps): ReactElement {
measuredH + vertPad * 2,
lineH + vertPad * 2
)
// Height the box had when editing began. `centerY` tracks the
// center of THAT block, so subtracting half of this height gives
// its top edge — a fixed anchor the box grows down from. Both
// terms scale with the camera, so it stays glued under pan/zoom.
const anchorHeight =
Math.max(startLineCount, 1) * lineH + vertPad * 2

input.style.width = `${contentWidth}px`
input.style.height = `${contentHeight}px`
input.style.left = `${leftAnchor}px`
input.style.top = `${centerY - contentHeight / 2}px`
input.style.top = `${centerY - anchorHeight / 2}px`
}

// Re-glue the editor to the text after any render (pan/zoom/content).
const repositionEditor = (): void => {
recomputeGeometry()
autoSizeAndCenter()
autoSizeAndPlace()
}

// On typing: push the value into the hidden Two.js text nodes so the
// SelectionController's box (and our live block rect) reflect it. The
// syncMultilineLayout's two.update fires 'update' → repositionEditor.
const onTextInput = (): void => {
textValueRef.current = input.value
keepBlockTopFixed()
syncMultilineLayout()
repositionEditor()
}
Expand Down Expand Up @@ -428,6 +458,9 @@ function NewText(props: ElementProps): ReactElement {
setTextValue(newContent)
textValueRef.current = newContent

// Idempotent — a no-op unless the final value changed the line
// count without an `input` event (e.g. a programmatic set).
keepBlockTopFixed()
syncMultilineLayout()

const bRect = blockRect()
Expand All @@ -449,14 +482,21 @@ function NewText(props: ElementProps): ReactElement {
...baseMetadata,
content: textValueRef.current,
}
// `y` rides along because keeping the block's top edge fixed
// moves the origin (the block stays centered on it). Without
// persisting it, a reload would re-center the block on the old
// origin and the text would jump back up.
const newY = Math.round(group.translation.y)
updateComponentBulkPropertiesInLocalStore(props.id, {
width: newWidth,
height: newHeight,
y: newY,
metadata: updatedMetadata,
})

if (group.elementData) {
group.elementData.metadata = updatedMetadata
group.elementData.y = newY
}

input.remove()
Expand Down
Loading
Loading