add image support

This commit is contained in:
Triston Armstrong 2024-10-05 12:49:22 -04:00
parent da2c0efefd
commit ae58276f92
10 changed files with 248 additions and 46 deletions

View File

@ -1,5 +1,5 @@
import { useRef } from "kaioken"
import { NotesSigal } from "../signals"
import { ImagesSignal, NotesSigal } from "../signals"
export function CardSelector() {
const containerRef = useRef<HTMLDivElement>(null)
@ -60,7 +60,34 @@ function StickyNote() {
function Image() {
function _handleClick() {
alert("created image")
const input = document.createElement('input')
input.type = 'file'
input.onchange = (e: any) => {
const file = e.target.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = readerEvent => {
const content = readerEvent.target?.result;
let img: string = '';
if (typeof content == 'string') img = content?.split(':')[1]
if (!img) return
ImagesSignal.default.addImage({
type: "image",
title: "New Image",
contents: content as string,
position: {
x: e.pageX - 100,
y: e.pageY + (window.innerHeight / 2) - 100
},
dimensions: {
w: 200,
h: 200
}
})
}
}
input.click()
}
return (
@ -93,3 +120,5 @@ function Image() {
</button>
)
}

View File

@ -0,0 +1,86 @@
import { signal, useRef } from "kaioken"
import { ImagesSignal, focusedItem } from "../signals"
import { useDebounce } from "../utils/useDebounce"
import { LayerEnum } from "../utils/enums"
import images, { ImageCardType } from "../signals/images"
namespace ImageCard {
export interface ImageCardProps {
key: ImageCardType['id']
data: ImageCardType
}
}
export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps) {
const pressed = signal(false)
const newX = useRef(0)
const newY = useRef(0)
const offsetX = useRef(0)
const offsetY = useRef(0)
const { debounce } = useDebounce()
function updateLocalStorage(time?: number) {
debounce(() => {
console.log(itemKey, "updated storage")
localStorage.setItem("images", JSON.stringify(images.images.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 }
ImagesSignal.default.updateImageProperty(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) {
e.preventDefault()
focusedItem.value = itemKey
offsetX.current = e.offsetX
offsetY.current = e.offsetY
pressed.value = true
window.addEventListener('mousemove', _handleMouseMove)
window.addEventListener('mouseup', _handleMouseUp)
}
return (
<div
onmousedown={_handleMouseDown}
className="select-none transition flex flex-col justify-stretch shadow-lg rounded border border-[#3c3c3c] absolute"
style={{
zIndex: `${focusedItem.value == itemKey ? LayerEnum.CARD_ELEVATED : LayerEnum.CARD}`,
width: `${item.dimensions.w}px`,
height: `${item.dimensions.h}px`,
top: `${item.position.y}px`,
left: `${item.position.x}px`,
backgroundColor: '#181818'
}}
>
<button className="text-md" onclick={(_e: Event) => {
ImagesSignal.default.removeImage(item.id)
ImagesSignal.default.images.notify()
updateLocalStorage()
}}>x</button>
<img
src={item.contents}
width={'100%'}
height={'100%'}
alt={item.title}
/>
</div >
)
}

View File

@ -1,9 +1,11 @@
import { useRef, useEffect } from "kaioken"
import { CardSelector } from "./CardSelector"
import { NotesSigal, canvasDimentsion } from "../signals"
import { ImagesSignal, NotesSigal, canvasDimentsion } from "../signals"
import { NoteCard } from "./NoteCard"
import notes from "../signals/notes"
import { MiniMap } from "./MiniMap"
import { ImageCard } from "./ImageCard"
import images from "../signals/images"
export default function InfiniteCanvas() {
const containerRef = useRef<HTMLDivElement>(null)
@ -25,6 +27,7 @@ export default function InfiniteCanvas() {
updateDimensions()
window.addEventListener("resize", updateDimensions)
notes.loadLocalStorage()
images.loadLocalStorage()
return () => {
window.removeEventListener("resize", updateDimensions)
@ -32,6 +35,8 @@ export default function InfiniteCanvas() {
}, [])
return (
<>
<>
<>
<CardSelector />
<MiniMap />
@ -54,8 +59,17 @@ export default function InfiniteCanvas() {
<NoteCard key={itemKey} data={item} />
)
})}
{Object.keys(ImagesSignal.default.images.value).map((itemKey: string) => {
const item = ImagesSignal.default.images.value[itemKey]
return (
<ImageCard key={itemKey} data={item} />
)
})}
</div>
</div>
</>
</>
</>
)
}

View File

@ -1,8 +1,8 @@
import { signal, useEffect, useRef } from "kaioken"
import notes from "../signals/notes"
import { Card } from "../types/Card"
import notes, { NoteCardType } from "../signals/notes"
import { canvasDimentsion } from "../signals"
import { LayerEnum } from "../utils/enums"
import images, { ImageCardType } from "../signals/images"
const _MAP_OFFSET = 20
const _MAP_SCALE_FACTOR = 10
@ -44,8 +44,36 @@ export function MiniMap() {
borderRadius: '4px'
}}>
{Object.keys(images.images.value).map((imageKey: ImageCardType['id']) => {
const image = images.images.value[imageKey]
{Object.keys(notes.notes.value).map((noteKey: Card['id']) => {
function _handleItemClick(_e: MouseEvent) {
window.scrollTo({
left: image.position.x - ((viewportWidth / 2) - (image.dimensions.w / 2)),
top: image.position.y - ((viewportHeight / 2) - (image.dimensions.h / 2)),
behavior: 'smooth'
})
}
return (
<div className={"bg-green-500 hover:bg-blue-500 cursor-pointer"}
onclick={_handleItemClick}
style={{
position: 'absolute',
width: `${image.dimensions.w / _MAP_SCALE_FACTOR}px`,
height: `${image.dimensions.h / _MAP_SCALE_FACTOR}px`,
top: `${(image.position.y / _MAP_SCALE_FACTOR)}px`,
left: `${(image.position.x / _MAP_SCALE_FACTOR)}px`,
border: '1px solid #222',
borderRadius: '2px',
zIndex: `${LayerEnum.MINIMAP + 1}`
}}
></div>
)
})}
{Object.keys(notes.notes.value).map((noteKey: NoteCardType['id']) => {
const note = notes.notes.value[noteKey]
function _handleItemClick(_e: MouseEvent) {

View File

@ -1,14 +1,13 @@
import { signal, useRef } from "kaioken"
import { NotesSigal, focusedItem } from "../signals"
import { Card } from "../types"
import { useDebounce } from "../utils/useDebounce"
import notes from "../signals/notes"
import notes, { NoteCardType } from "../signals/notes"
import { LayerEnum } from "../utils/enums"
namespace NoteCard {
export interface NoteCardProps {
key: Card['id']
data: Card
key: NoteCardType['id']
data: NoteCardType
}
}
@ -23,7 +22,6 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
function updateLocalStorage(time?: number) {
debounce(() => {
console.log(itemKey, "updated storage")
localStorage.setItem("notes", JSON.stringify(notes.notes.value))
}, time)
}
@ -77,6 +75,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
<button className="text-md" onclick={(_e: Event) => {
NotesSigal.default.removeNote(item.id)
NotesSigal.default.notes.notify()
updateLocalStorage()
}}>x</button>
</div>
<hr className="border border-[#3c3c3c]" />

View File

@ -0,0 +1,44 @@
import { signal } from "kaioken"
import { Card } from "../types"
export type ImageCardType = Card<"image">
const images = signal<Record<ImageCardType["id"], ImageCardType>>({})
function loadLocalStorage() {
images.value = JSON.parse(localStorage.getItem("images") ?? "{}")
}
function addImage(data: Omit<ImageCardType, "id">) {
const newCard = {
...data,
id: crypto.randomUUID(),
}
images.value[newCard.id] = newCard
images.notify()
}
function removeImage(id: ImageCardType["id"]) {
delete images.value[id]
images.notify()
}
function updateImageProperty<K extends keyof ImageCardType>(
id: ImageCardType["id"],
property: K,
data: ImageCardType[K]
) {
const newData = {
...images.value[id],
[property]: data,
}
images.value[id] = newData
images.notify()
}
export default {
images,
addImage,
removeImage,
updateImageProperty,
loadLocalStorage,
}

View File

@ -5,3 +5,4 @@ export const focusedItem = signal<string | null>(null)
export const canvasDimentsion = signal({ width: 3000, height: 3000 })
export * as NotesSigal from "./notes"
export * as ImagesSignal from "./images"

View File

@ -1,31 +1,31 @@
import { signal } from "kaioken"
import { Card } from "../types"
const notes = signal<Record<Card["id"], Card>>({})
export type NoteCardType = Card<"note">
const notes = signal<Record<NoteCardType["id"], NoteCardType>>({})
function loadLocalStorage() {
notes.value = JSON.parse(localStorage.getItem("notes") ?? "{}")
}
function addNote(data: Omit<Card, "id">) {
function addNote(data: Omit<NoteCardType, "id">) {
const newCard = {
...data,
id: crypto.randomUUID(),
}
notes.value[newCard.id] = newCard
notes.notify()
//updateLocalStorage()
}
function removeNote(id: Card["id"]) {
function removeNote(id: NoteCardType["id"]) {
delete notes.value[id]
notes.notify()
//updateLocalStorage()
}
function updateNoteProperty<K extends keyof Card>(
id: Card["id"],
function updateNoteProperty<K extends keyof NoteCardType>(
id: NoteCardType["id"],
property: K,
data: Card[K]
data: NoteCardType[K]
) {
const newData = {
...notes.value[id],
@ -33,7 +33,6 @@ function updateNoteProperty<K extends keyof Card>(
}
notes.value[id] = newData
notes.notify()
//updateLocalStorage()
}
export default {
@ -41,6 +40,5 @@ export default {
addNote,
removeNote,
updateNoteProperty,
//updateLocalStorage,
loadLocalStorage,
}

View File

@ -2,11 +2,13 @@ export type CardTypes = "note" | "image"
export type positionCoords = { x: number; y: number }
export type dimensionCoords = { w: number; h: number }
export interface Card {
type Base64 = string
export interface Card<Ttype extends CardTypes> {
id: string
type: CardTypes
type: Ttype
title: string
contents: string
contents: Ttype extends "image" ? Base64 : string
position: positionCoords
dimensions: dimensionCoords
}

View File

@ -12,6 +12,7 @@ function createState(): UseDebounceState {
export function useDebounce() {
if (!sideEffectsEnabled()) return createState()
return useHook("useDebounce", createState, ({ hook, update, isInit }) => {
if (!isInit) return { timer: hook.timer, debounce: hook.debounce }