generated from Klectr/KTemplate
feat/text-support #9
@ -1,3 +1,4 @@
|
|||||||
|
import { memo } from "kaioken"
|
||||||
import InfiniteCanvas from "./components/InfinateCanvas"
|
import InfiniteCanvas from "./components/InfinateCanvas"
|
||||||
import { ToastContextProvider } from "./components/Toast"
|
import { ToastContextProvider } from "./components/Toast"
|
||||||
import { useThemeDetector } from "./utils/useThemeDetector"
|
import { useThemeDetector } from "./utils/useThemeDetector"
|
||||||
@ -7,7 +8,9 @@ export function App() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastContextProvider>
|
<ToastContextProvider>
|
||||||
<InfiniteCanvas />
|
<MemoInfyCanvas />
|
||||||
</ToastContextProvider>
|
</ToastContextProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MemoInfyCanvas = memo(InfiniteCanvas)
|
||||||
|
@ -88,6 +88,11 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
|||||||
window.removeEventListener('mouseup', _handleResizeMouseUp)
|
window.removeEventListener('mouseup', _handleResizeMouseUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function _handleClose(_e: Event) {
|
||||||
|
ImagesSignal.default.removeImage(item.id)
|
||||||
|
ImagesSignal.default.images.notify()
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
onmousedown={_handleMouseDown}
|
onmousedown={_handleMouseDown}
|
||||||
@ -105,11 +110,9 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
|
|||||||
backgroundPosition: 'center'
|
backgroundPosition: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<button className="flex justify-center items-center hover:bg-blue-500 rounded w-5 h-5 dark:text-[#777] dark:hover:text-white text-white text-md absolute right-0 top-0" onclick={(_e: Event) => {
|
<button
|
||||||
ImagesSignal.default.removeImage(item.id)
|
className="flex justify-center items-center hover:bg-blue-500 rounded w-5 h-5 dark:text-[#777] dark:hover:text-white text-white text-md absolute right-0 top-0"
|
||||||
ImagesSignal.default.images.notify()
|
onclick={_handleClose}>x</button>
|
||||||
debounceLSUpdate()
|
|
||||||
}}>x</button>
|
|
||||||
|
|
||||||
<ExpandIcon cb={_handleResizeMouseDown} />
|
<ExpandIcon cb={_handleResizeMouseDown} />
|
||||||
</div >
|
</div >
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useRef, useEffect } from "kaioken"
|
import { useRef, useEffect } from "kaioken"
|
||||||
import { ImagesSignal, NotesSigal, canvasDimentsion } from "../signals"
|
import { ImagesSignal, NotesSigal, TextSignal, canvasDimentsion } from "../signals"
|
||||||
import { NoteCard } from "./NoteCard"
|
import { NoteCard } from "./NoteCard"
|
||||||
import notes from "../signals/notes"
|
import notes from "../signals/notes"
|
||||||
import { MiniMap } from "./MiniMap"
|
import { MiniMap } from "./MiniMap"
|
||||||
@ -8,6 +8,8 @@ import images from "../signals/images"
|
|||||||
import { CardSelector } from "./cardSelector/CardSelector"
|
import { CardSelector } from "./cardSelector/CardSelector"
|
||||||
import { Logo } from "./Logo"
|
import { Logo } from "./Logo"
|
||||||
import { useThemeDetector } from "../utils/useThemeDetector"
|
import { useThemeDetector } from "../utils/useThemeDetector"
|
||||||
|
import { TextItem } from "./TextItem"
|
||||||
|
import texts from "../signals/texts"
|
||||||
|
|
||||||
export default function InfiniteCanvas() {
|
export default function InfiniteCanvas() {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
@ -36,6 +38,7 @@ export default function InfiniteCanvas() {
|
|||||||
window.addEventListener("scrollend", _updatePosition)
|
window.addEventListener("scrollend", _updatePosition)
|
||||||
notes.loadLocalStorage()
|
notes.loadLocalStorage()
|
||||||
images.loadLocalStorage()
|
images.loadLocalStorage()
|
||||||
|
texts.loadLocalStorage()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", _updateDimensions)
|
window.removeEventListener("resize", _updateDimensions)
|
||||||
@ -74,6 +77,14 @@ export default function InfiniteCanvas() {
|
|||||||
<ImageCard key={itemKey} data={item} />
|
<ImageCard key={itemKey} data={item} />
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{Object.keys(TextSignal.default.texts.value).map((itemKey: string) => {
|
||||||
|
const item = TextSignal.default.texts.value[itemKey]
|
||||||
|
return (
|
||||||
|
<TextItem key={itemKey} data={item} />
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
@ -3,6 +3,7 @@ import notes, { NoteCardType } from "../signals/notes"
|
|||||||
import { canvasDimentsion } from "../signals"
|
import { canvasDimentsion } from "../signals"
|
||||||
import { LayerEnum } from "../utils/enums"
|
import { LayerEnum } from "../utils/enums"
|
||||||
import images, { ImageCardType } from "../signals/images"
|
import images, { ImageCardType } from "../signals/images"
|
||||||
|
import texts, { TextCardType } from "../signals/texts"
|
||||||
|
|
||||||
const _MAP_OFFSET = 20
|
const _MAP_OFFSET = 20
|
||||||
const _MAP_SCALE_FACTOR = 10
|
const _MAP_SCALE_FACTOR = 10
|
||||||
@ -111,6 +112,34 @@ export function MiniMap() {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{Object.keys(texts.texts.value).map((textKey: TextCardType['id']) => {
|
||||||
|
const text = texts.texts.value[textKey]
|
||||||
|
const el = useRef(null)
|
||||||
|
|
||||||
|
function _handleItemClick(_e: MouseEvent) {
|
||||||
|
window.scrollTo({
|
||||||
|
left: text.position.x - ((viewportWidth / 2) - (text.dimensions.w / 2)),
|
||||||
|
top: text.position.y - ((viewportHeight / 2) - (text.dimensions.h / 2)),
|
||||||
|
behavior: 'smooth'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={el} className={"bg-indigo-500 hover:bg-blue-500 cursor-pointer rounded"}
|
||||||
|
onclick={_handleItemClick}
|
||||||
|
style={{
|
||||||
|
position: 'absolute',
|
||||||
|
width: `${text.dimensions.w / _MAP_SCALE_FACTOR}px`,
|
||||||
|
height: `${text.dimensions.h / _MAP_SCALE_FACTOR}px`,
|
||||||
|
top: `${(text.position.y / _MAP_SCALE_FACTOR)}px`,
|
||||||
|
left: `${(text.position.x / _MAP_SCALE_FACTOR)}px`,
|
||||||
|
border: '1px solid #222',
|
||||||
|
zIndex: `${LayerEnum.MINIMAP + 1}`
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={'pointer-events-none absolute bg-blue-200 bg-opacity-10 border dark:border-blue-800 border-blue-500 bg-blue-500 rounded'}
|
className={'pointer-events-none absolute bg-blue-200 bg-opacity-10 border dark:border-blue-800 border-blue-500 bg-blue-500 rounded'}
|
||||||
style={{
|
style={{
|
||||||
|
@ -129,7 +129,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
<div
|
<div
|
||||||
onmousedown={_handleFocusCard}
|
onmousedown={_handleFocusCard}
|
||||||
style={cardPositionStyle}
|
style={cardPositionStyle}
|
||||||
className="overflow-hidden text-[#333] dark:bg-[#1a1a1a] dark:border-[#1c1c1c] bg-[#eee] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute"
|
className="overflow-hidden text-[#333] dark:bg-[#1a1a1a] dark:border-[#1c1c1c] bg-[#efeff0] select-none transition flex flex-col justify-stretch shadow-md rounded border border-[#ddd] absolute"
|
||||||
>
|
>
|
||||||
<div className="overflow-hidden flex-1 flex flex-col gap-1">
|
<div className="overflow-hidden flex-1 flex flex-col gap-1">
|
||||||
{/* Header Bar */}
|
{/* Header Bar */}
|
||||||
|
235
src/components/TextItem.tsx
Normal file
235
src/components/TextItem.tsx
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
import { signal, useEffect, useLayoutEffect, useRef } from "kaioken"
|
||||||
|
import { TextSignal, focusedItem } from "../signals"
|
||||||
|
import { useDebounce } from "../utils/useDebounce"
|
||||||
|
import texts, { TextCardType } from "../signals/texts"
|
||||||
|
import { LayerEnum } from "../utils/enums"
|
||||||
|
import { Card } from "../types"
|
||||||
|
import { useThemeDetector } from "../utils/useThemeDetector"
|
||||||
|
|
||||||
|
namespace TextItem {
|
||||||
|
export interface TextCardProps {
|
||||||
|
key: TextCardType['id']
|
||||||
|
data: TextCardType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TextItem({ key: itemKey, data: item }: TextItem.TextCardProps) {
|
||||||
|
const { debounce } = useDebounce()
|
||||||
|
const pressed = signal(false)
|
||||||
|
const newX = useRef(0)
|
||||||
|
const newY = useRef(0)
|
||||||
|
const offsetX = useRef(0)
|
||||||
|
const offsetY = useRef(0)
|
||||||
|
const initialResizeX = useRef(0)
|
||||||
|
const elRef = useRef<HTMLDivElement>(null)
|
||||||
|
const pRef = useRef<HTMLParagraphElement>(null)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const elDems = elRef.current?.getBoundingClientRect()
|
||||||
|
const elW = elDems?.width ?? 100
|
||||||
|
const elH = elDems?.height ?? 100
|
||||||
|
const newDems: Card<'texts'>['dimensions'] = { w: elW, h: elH }
|
||||||
|
TextSignal.default.updateTextProperty(itemKey, 'dimensions', newDems)
|
||||||
|
TextSignal.default.texts.notify()
|
||||||
|
}, [elRef.current, item.fontSize])
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function updateLocalStorage(time?: number) {
|
||||||
|
debounce(() => {
|
||||||
|
localStorage.setItem("texts", JSON.stringify(texts.texts.value))
|
||||||
|
}, time)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleMouseMove(e: MouseEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
if (!pressed.value) return
|
||||||
|
|
||||||
|
newX.current = e.pageX - offsetX.current
|
||||||
|
newY.current = e.pageY - offsetY.current
|
||||||
|
const newPos = { x: newX.current, y: newY.current }
|
||||||
|
|
||||||
|
TextSignal.default.updateTextProperty(itemKey, 'position', newPos)
|
||||||
|
updateLocalStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleMouseUp(e: MouseEvent) {
|
||||||
|
e.preventDefault()
|
||||||
|
pressed.value = false
|
||||||
|
window.removeEventListener('mousemove', _handleMouseMove)
|
||||||
|
window.removeEventListener('mouseup', _handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleMouseDown(e: MouseEvent) {
|
||||||
|
focusedItem.value = itemKey
|
||||||
|
offsetX.current = e.offsetX
|
||||||
|
offsetY.current = e.offsetY
|
||||||
|
pressed.value = true
|
||||||
|
window.addEventListener('mousemove', _handleMouseMove)
|
||||||
|
window.addEventListener('mouseup', _handleMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleResizeMove(e: MouseEvent) {
|
||||||
|
const { pageX } = e
|
||||||
|
const newX = initialResizeX.current - pageX
|
||||||
|
const newFontSize = Math.floor(-newX + item.fontSize)
|
||||||
|
|
||||||
|
TextSignal.default.updateTextProperty(itemKey, 'fontSize', newFontSize)
|
||||||
|
TextSignal.default.texts.notify()
|
||||||
|
updateLocalStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleResizeMouseDown(e: MouseEvent) {
|
||||||
|
e.stopPropagation()
|
||||||
|
initialResizeX.current = e.pageX
|
||||||
|
pressed.value = true
|
||||||
|
window.addEventListener('mousemove', _handleResizeMove)
|
||||||
|
window.addEventListener('mouseup', _handleResizeMouseUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleResizeMouseUp(_e: MouseEvent) {
|
||||||
|
pressed.value = false
|
||||||
|
window.removeEventListener('mousemove', _handleResizeMove)
|
||||||
|
window.removeEventListener('mouseup', _handleResizeMouseUp)
|
||||||
|
updateLocalStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleClose(e: MouseEvent) {
|
||||||
|
e.stopPropagation()
|
||||||
|
TextSignal.default.removeText(item.id)
|
||||||
|
TextSignal.default.texts.notify()
|
||||||
|
}
|
||||||
|
|
||||||
|
function _handleContentInput(e: InputEvent) {
|
||||||
|
const el = e.target as HTMLParagraphElement & Pick<HTMLInputElement, 'oninput'>
|
||||||
|
const val = el.innerHTML.split('<br>').join('\n')
|
||||||
|
texts.updateTextProperty(itemKey, 'contents', val)
|
||||||
|
updateLocalStorage()
|
||||||
|
}
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (!pRef.current) return
|
||||||
|
pRef.current.textContent = item.contents
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
function _handleClick(e: MouseEvent) {
|
||||||
|
const target = e.target as HTMLElement
|
||||||
|
|
||||||
|
if (target === elRef.current) return
|
||||||
|
if (elRef.current?.contains(target)) return
|
||||||
|
|
||||||
|
const classes = '.text-container, .text-p, .text-pre, .text-close'
|
||||||
|
if (target.closest(classes)) return
|
||||||
|
if (target.matches(classes)) return
|
||||||
|
|
||||||
|
focusedItem.value = null
|
||||||
|
document.removeEventListener('mousedown', _handleClick)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (focusedItem.value !== itemKey) return
|
||||||
|
document.addEventListener('mousedown', _handleClick)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', _handleClick)
|
||||||
|
}
|
||||||
|
}, [focusedItem.value])
|
||||||
|
|
||||||
|
const outline = `${focusedItem.value === item.id ? '1px solid' : ''}`
|
||||||
|
const fontSize = `${item.fontSize / 6}px`
|
||||||
|
const zIndex = `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
ref={elRef}
|
||||||
|
onmousedown={_handleMouseDown}
|
||||||
|
className="text-container transition flex flex-col justify-stretch rounded absolute"
|
||||||
|
style={{
|
||||||
|
outline: outline,
|
||||||
|
fontSize: fontSize,
|
||||||
|
zIndex: zIndex,
|
||||||
|
top: `${item.position.y}px`,
|
||||||
|
left: `${item.position.x}px`,
|
||||||
|
userSelect: focusedItem.value === itemKey ? 'text' : 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<pre
|
||||||
|
className={'text-pre px-2 w-full select-none drop-shadow relative'}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
ref={pRef}
|
||||||
|
//@ts-expect-error
|
||||||
|
oninput={_handleContentInput}
|
||||||
|
contentEditable
|
||||||
|
className={'text-p inline-block px-2 w-full select-none drop-shadow relative'}>
|
||||||
|
</p>
|
||||||
|
</pre>
|
||||||
|
|
||||||
|
<CloseIcon cb={_handleClose} item={item} />
|
||||||
|
<ExpandIcon cb={_handleResizeMouseDown} item={item} />
|
||||||
|
</div >
|
||||||
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace CloseIcon {
|
||||||
|
export interface Props {
|
||||||
|
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null | undefined,
|
||||||
|
item: TextSignal.TextCardType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function CloseIcon({ item, cb }: CloseIcon.Props) {
|
||||||
|
const isDark = useThemeDetector()
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
onclick={cb}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill={isDark ? 'black' : 'white'}
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
className="text-close cursor-pointer w-4 h-4 absolute top-[-8px] right-[-8px]"
|
||||||
|
style={{
|
||||||
|
display: focusedItem.value === item.id ? 'unset' : 'none'
|
||||||
|
}}
|
||||||
|
|
||||||
|
>
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="m15 9-6 6" />
|
||||||
|
<path d="m9 9 6 6" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace ExpandIcon {
|
||||||
|
export interface Props {
|
||||||
|
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null | undefined,
|
||||||
|
item: TextSignal.TextCardType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function ExpandIcon({ cb, item }: ExpandIcon.Props) {
|
||||||
|
const isDark = useThemeDetector()
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
onmousedown={cb}
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="24"
|
||||||
|
height="24"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill={isDark ? 'black' : 'white'}
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="2"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
className="h-4 w-4 absolute right-[-6px] bottom-[-6px] cursor-se-resize"
|
||||||
|
style={{
|
||||||
|
display: focusedItem.value === item.id ? 'unset' : 'none'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<rect width="18" height="18" x="3" y="3" rx="2" />
|
||||||
|
</svg>
|
||||||
|
)
|
||||||
|
}
|
@ -21,15 +21,15 @@ export function ImportButton() {
|
|||||||
let content = readerEvent.target?.result;
|
let content = readerEvent.target?.result;
|
||||||
// get only the base64 parts and not any identifiers
|
// get only the base64 parts and not any identifiers
|
||||||
content = (content as string).split(',')[1]
|
content = (content as string).split(',')[1]
|
||||||
const data: Record<string, Card<'note'> | Card<'image'>> = convertBase64ToJson(content)
|
const data: Record<string, Card<'notes'> | Card<'images'>> = convertBase64ToJson(content)
|
||||||
for (let key in data) {
|
for (let key in data) {
|
||||||
const item = data[key]
|
const item = data[key]
|
||||||
if (item.type == 'image') {
|
if (item.type == 'images') {
|
||||||
const { id, ...rest } = item
|
const { id, ...rest } = item
|
||||||
images.addImage(rest)
|
images.addImage(rest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item.type == 'note') {
|
if (item.type == 'notes') {
|
||||||
const { id, ...rest } = item
|
const { id, ...rest } = item
|
||||||
notes.addNote(rest)
|
notes.addNote(rest)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,33 @@
|
|||||||
import { Tooltip } from "./Tooltip";
|
import { Tooltip } from "./Tooltip";
|
||||||
|
import { TextSignal } from "../../signals";
|
||||||
|
import texts from "../../signals/texts";
|
||||||
|
import { updateLocalStorage } from "../../utils/localStorage";
|
||||||
import { defaultClassName } from "./utils";
|
import { defaultClassName } from "./utils";
|
||||||
|
|
||||||
export function TextButton() {
|
export function TextButton() {
|
||||||
|
|
||||||
|
function _handleClick(e: MouseEvent) {
|
||||||
|
TextSignal.default.addText({
|
||||||
|
fontSize: 84,
|
||||||
|
type: "texts",
|
||||||
|
title: "New Note",
|
||||||
|
contents: "todo: fill me",
|
||||||
|
position: {
|
||||||
|
x: e.pageX - 100,
|
||||||
|
y: e.pageY + (window.innerHeight / 2) - 100
|
||||||
|
},
|
||||||
|
dimensions: {
|
||||||
|
w: 200,
|
||||||
|
h: 100
|
||||||
|
}
|
||||||
|
})
|
||||||
|
updateLocalStorage("texts", texts.texts.value)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Tooltip message="Create a Text Node">
|
<Tooltip message="Create a Text Node">
|
||||||
<svg
|
<svg
|
||||||
|
onclick={_handleClick}
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
width="24"
|
width="24"
|
||||||
height="24"
|
height="24"
|
||||||
|
@ -2,7 +2,7 @@ import { signal } from "kaioken"
|
|||||||
import { Card } from "../types"
|
import { Card } from "../types"
|
||||||
import { focusedItem } from "."
|
import { focusedItem } from "."
|
||||||
|
|
||||||
export type ImageCardType = Card<"image">
|
export type ImageCardType = Card<"images">
|
||||||
|
|
||||||
const images = signal<Record<ImageCardType["id"], ImageCardType>>({})
|
const images = signal<Record<ImageCardType["id"], ImageCardType>>({})
|
||||||
|
|
||||||
|
@ -6,3 +6,4 @@ export const canvasDimentsion = signal({ width: 3000, height: 3000 })
|
|||||||
|
|
||||||
export * as NotesSigal from "./notes"
|
export * as NotesSigal from "./notes"
|
||||||
export * as ImagesSignal from "./images"
|
export * as ImagesSignal from "./images"
|
||||||
|
export * as TextSignal from "./texts"
|
||||||
|
@ -2,7 +2,7 @@ import { signal } from "kaioken"
|
|||||||
import { Card } from "../types"
|
import { Card } from "../types"
|
||||||
import { focusedItem } from "."
|
import { focusedItem } from "."
|
||||||
|
|
||||||
export type NoteCardType = Card<"note">
|
export type NoteCardType = Card<"notes">
|
||||||
|
|
||||||
const notes = signal<Record<NoteCardType["id"], NoteCardType>>({})
|
const notes = signal<Record<NoteCardType["id"], NoteCardType>>({})
|
||||||
|
|
||||||
|
48
src/signals/texts.ts
Normal file
48
src/signals/texts.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
import { signal } from "kaioken"
|
||||||
|
import { Card } from "../types"
|
||||||
|
import { focusedItem } from "."
|
||||||
|
|
||||||
|
export type TextCardType = Card<"texts"> & {
|
||||||
|
fontSize: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const texts = signal<Record<TextCardType["id"], TextCardType>>({})
|
||||||
|
|
||||||
|
function loadLocalStorage() {
|
||||||
|
texts.value = JSON.parse(localStorage.getItem("texts") ?? "{}")
|
||||||
|
}
|
||||||
|
|
||||||
|
function addText(data: Omit<TextCardType, "id">) {
|
||||||
|
const newCard = {
|
||||||
|
...data,
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
}
|
||||||
|
texts.value[newCard.id] = newCard
|
||||||
|
texts.notify()
|
||||||
|
focusedItem.value = newCard.id
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeText(id: TextCardType["id"]) {
|
||||||
|
delete texts.value[id]
|
||||||
|
texts.notify()
|
||||||
|
}
|
||||||
|
function updateTextProperty<K extends keyof TextCardType>(
|
||||||
|
id: TextCardType["id"],
|
||||||
|
property: K,
|
||||||
|
data: TextCardType[K]
|
||||||
|
) {
|
||||||
|
const newData = {
|
||||||
|
...texts.value[id],
|
||||||
|
[property]: data,
|
||||||
|
}
|
||||||
|
texts.value[id] = newData
|
||||||
|
texts.notify()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
texts,
|
||||||
|
addText,
|
||||||
|
removeText,
|
||||||
|
updateTextProperty,
|
||||||
|
loadLocalStorage,
|
||||||
|
}
|
@ -13,6 +13,10 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
*:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
overflow: scroll;
|
overflow: scroll;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
export type CardTypes = "note" | "image"
|
export type CardTypes = "notes" | "images" | "texts"
|
||||||
export type positionCoords = { x: number; y: number }
|
export type positionCoords = { x: number; y: number }
|
||||||
export type dimensionCoords = { w: number; h: number }
|
export type dimensionCoords = { w: number; h: number }
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
import { CardTypes } from "../types"
|
||||||
|
|
||||||
export function updateLocalStorage(
|
export function updateLocalStorage(
|
||||||
location: "notes" | "images",
|
location: CardTypes,
|
||||||
collection: unknown[] | Record<string, unknown>
|
collection: unknown[] | Record<string, unknown>
|
||||||
) {
|
) {
|
||||||
localStorage.setItem(location, JSON.stringify(collection))
|
localStorage.setItem(location, JSON.stringify(collection))
|
||||||
|
Loading…
Reference in New Issue
Block a user