generated from Klectr/KTemplate
add image support
This commit is contained in:
parent
da2c0efefd
commit
ae58276f92
@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
86
src/components/ImageCard.tsx
Normal file
86
src/components/ImageCard.tsx
Normal 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 >
|
||||
|
||||
)
|
||||
}
|
@ -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)
|
||||
@ -33,29 +36,40 @@ export default function InfiniteCanvas() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<CardSelector />
|
||||
<MiniMap />
|
||||
<>
|
||||
<>
|
||||
<CardSelector />
|
||||
<MiniMap />
|
||||
|
||||
<div
|
||||
className="h-screen w-full absolute top-0 left-0"
|
||||
>
|
||||
<div
|
||||
className="absolute top-0 left-0"
|
||||
ref={containerRef}
|
||||
style={{
|
||||
width: `${canvasDimentsion.value.width}px`,
|
||||
height: `${canvasDimentsion.value.width}px`,
|
||||
backgroundSize: "30px 30px",
|
||||
backgroundImage: "radial-gradient(circle, rgba(255, 255, 255, 0.2) 1px, transparent 1px)",
|
||||
}}>
|
||||
{Object.keys(NotesSigal.default.notes.value).map((itemKey: string) => {
|
||||
const item = NotesSigal.default.notes.value[itemKey]
|
||||
return (
|
||||
<NoteCard key={itemKey} data={item} />
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="h-screen w-full absolute top-0 left-0"
|
||||
>
|
||||
<div
|
||||
className="absolute top-0 left-0"
|
||||
ref={containerRef}
|
||||
style={{
|
||||
width: `${canvasDimentsion.value.width}px`,
|
||||
height: `${canvasDimentsion.value.width}px`,
|
||||
backgroundSize: "30px 30px",
|
||||
backgroundImage: "radial-gradient(circle, rgba(255, 255, 255, 0.2) 1px, transparent 1px)",
|
||||
}}>
|
||||
{Object.keys(NotesSigal.default.notes.value).map((itemKey: string) => {
|
||||
const item = NotesSigal.default.notes.value[itemKey]
|
||||
return (
|
||||
<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>
|
||||
</>
|
||||
</>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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]" />
|
||||
|
@ -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,
|
||||
}
|
@ -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"
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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 }
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user