diff --git a/src/App.tsx b/src/App.tsx
index 46d4d53..4189a94 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,3 +1,4 @@
+import { memo } from "kaioken"
import InfiniteCanvas from "./components/InfinateCanvas"
import { ToastContextProvider } from "./components/Toast"
import { useThemeDetector } from "./utils/useThemeDetector"
@@ -7,7 +8,9 @@ export function App() {
return (
-
+
)
}
+
+const MemoInfyCanvas = memo(InfiniteCanvas)
diff --git a/src/components/ImageCard.tsx b/src/components/ImageCard.tsx
index 1f750ff..bbecaaa 100644
--- a/src/components/ImageCard.tsx
+++ b/src/components/ImageCard.tsx
@@ -88,6 +88,11 @@ export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps
window.removeEventListener('mouseup', _handleResizeMouseUp)
}
+ function _handleClose(_e: Event) {
+ ImagesSignal.default.removeImage(item.id)
+ ImagesSignal.default.images.notify()
+ }
+
return (
-
+
diff --git a/src/components/InfinateCanvas.tsx b/src/components/InfinateCanvas.tsx
index 5a7eb0c..b7976c1 100644
--- a/src/components/InfinateCanvas.tsx
+++ b/src/components/InfinateCanvas.tsx
@@ -1,5 +1,5 @@
import { useRef, useEffect } from "kaioken"
-import { ImagesSignal, NotesSigal, canvasDimentsion } from "../signals"
+import { ImagesSignal, NotesSigal, TextSignal, canvasDimentsion } from "../signals"
import { NoteCard } from "./NoteCard"
import notes from "../signals/notes"
import { MiniMap } from "./MiniMap"
@@ -8,6 +8,8 @@ import images from "../signals/images"
import { CardSelector } from "./cardSelector/CardSelector"
import { Logo } from "./Logo"
import { useThemeDetector } from "../utils/useThemeDetector"
+import { TextItem } from "./TextItem"
+import texts from "../signals/texts"
export default function InfiniteCanvas() {
const containerRef = useRef(null)
@@ -36,6 +38,7 @@ export default function InfiniteCanvas() {
window.addEventListener("scrollend", _updatePosition)
notes.loadLocalStorage()
images.loadLocalStorage()
+ texts.loadLocalStorage()
return () => {
window.removeEventListener("resize", _updateDimensions)
@@ -74,6 +77,14 @@ export default function InfiniteCanvas() {
)
})}
+
+ {Object.keys(TextSignal.default.texts.value).map((itemKey: string) => {
+ const item = TextSignal.default.texts.value[itemKey]
+ return (
+
+ )
+ })}
+
>
diff --git a/src/components/MiniMap.tsx b/src/components/MiniMap.tsx
index 24e524a..c9c60bc 100644
--- a/src/components/MiniMap.tsx
+++ b/src/components/MiniMap.tsx
@@ -3,6 +3,7 @@ import notes, { NoteCardType } from "../signals/notes"
import { canvasDimentsion } from "../signals"
import { LayerEnum } from "../utils/enums"
import images, { ImageCardType } from "../signals/images"
+import texts, { TextCardType } from "../signals/texts"
const _MAP_OFFSET = 20
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 (
+
+ )
+ })}
+
{/* Header Bar */}
diff --git a/src/components/TextItem.tsx b/src/components/TextItem.tsx
new file mode 100644
index 0000000..a7078d4
--- /dev/null
+++ b/src/components/TextItem.tsx
@@ -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
(null)
+ const pRef = useRef(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
+ const val = el.innerHTML.split('
').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 (
+
+
+ )
+}
+
+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 (
+
+ )
+}
+
+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 (
+
+ )
+}
diff --git a/src/components/cardSelector/ImportButton.tsx b/src/components/cardSelector/ImportButton.tsx
index 5a41c7d..7226b8c 100644
--- a/src/components/cardSelector/ImportButton.tsx
+++ b/src/components/cardSelector/ImportButton.tsx
@@ -21,15 +21,15 @@ export function ImportButton() {
let content = readerEvent.target?.result;
// get only the base64 parts and not any identifiers
content = (content as string).split(',')[1]
- const data: Record | Card<'image'>> = convertBase64ToJson(content)
+ const data: Record | Card<'images'>> = convertBase64ToJson(content)
for (let key in data) {
const item = data[key]
- if (item.type == 'image') {
+ if (item.type == 'images') {
const { id, ...rest } = item
images.addImage(rest)
}
- if (item.type == 'note') {
+ if (item.type == 'notes') {
const { id, ...rest } = item
notes.addNote(rest)
}
diff --git a/src/components/cardSelector/TextButton.tsx b/src/components/cardSelector/TextButton.tsx
index 0abe079..bcc28c9 100644
--- a/src/components/cardSelector/TextButton.tsx
+++ b/src/components/cardSelector/TextButton.tsx
@@ -1,11 +1,33 @@
import { Tooltip } from "./Tooltip";
+import { TextSignal } from "../../signals";
+import texts from "../../signals/texts";
+import { updateLocalStorage } from "../../utils/localStorage";
import { defaultClassName } from "./utils";
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 (