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 { useRef } from "kaioken"
|
||||||
import { NotesSigal } from "../signals"
|
import { ImagesSignal, NotesSigal } from "../signals"
|
||||||
|
|
||||||
export function CardSelector() {
|
export function CardSelector() {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
@ -60,7 +60,34 @@ function StickyNote() {
|
|||||||
|
|
||||||
function Image() {
|
function Image() {
|
||||||
function _handleClick() {
|
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 (
|
return (
|
||||||
@ -93,3 +120,5 @@ function Image() {
|
|||||||
</button>
|
</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 { useRef, useEffect } from "kaioken"
|
||||||
import { CardSelector } from "./CardSelector"
|
import { CardSelector } from "./CardSelector"
|
||||||
import { NotesSigal, canvasDimentsion } from "../signals"
|
import { ImagesSignal, NotesSigal, 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"
|
||||||
|
import { ImageCard } from "./ImageCard"
|
||||||
|
import images from "../signals/images"
|
||||||
|
|
||||||
export default function InfiniteCanvas() {
|
export default function InfiniteCanvas() {
|
||||||
const containerRef = useRef<HTMLDivElement>(null)
|
const containerRef = useRef<HTMLDivElement>(null)
|
||||||
@ -25,6 +27,7 @@ export default function InfiniteCanvas() {
|
|||||||
updateDimensions()
|
updateDimensions()
|
||||||
window.addEventListener("resize", updateDimensions)
|
window.addEventListener("resize", updateDimensions)
|
||||||
notes.loadLocalStorage()
|
notes.loadLocalStorage()
|
||||||
|
images.loadLocalStorage()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("resize", updateDimensions)
|
window.removeEventListener("resize", updateDimensions)
|
||||||
@ -32,6 +35,8 @@ export default function InfiniteCanvas() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<>
|
||||||
<>
|
<>
|
||||||
<CardSelector />
|
<CardSelector />
|
||||||
<MiniMap />
|
<MiniMap />
|
||||||
@ -54,8 +59,17 @@ export default function InfiniteCanvas() {
|
|||||||
<NoteCard key={itemKey} data={item} />
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
</>
|
||||||
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { signal, useEffect, useRef } from "kaioken"
|
import { signal, useEffect, useRef } from "kaioken"
|
||||||
import notes from "../signals/notes"
|
import notes, { NoteCardType } from "../signals/notes"
|
||||||
import { Card } from "../types/Card"
|
|
||||||
import { canvasDimentsion } from "../signals"
|
import { canvasDimentsion } from "../signals"
|
||||||
import { LayerEnum } from "../utils/enums"
|
import { LayerEnum } from "../utils/enums"
|
||||||
|
import images, { ImageCardType } from "../signals/images"
|
||||||
|
|
||||||
const _MAP_OFFSET = 20
|
const _MAP_OFFSET = 20
|
||||||
const _MAP_SCALE_FACTOR = 10
|
const _MAP_SCALE_FACTOR = 10
|
||||||
@ -44,8 +44,36 @@ export function MiniMap() {
|
|||||||
borderRadius: '4px'
|
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]
|
const note = notes.notes.value[noteKey]
|
||||||
|
|
||||||
function _handleItemClick(_e: MouseEvent) {
|
function _handleItemClick(_e: MouseEvent) {
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
import { signal, useRef } from "kaioken"
|
import { signal, useRef } from "kaioken"
|
||||||
import { NotesSigal, focusedItem } from "../signals"
|
import { NotesSigal, focusedItem } from "../signals"
|
||||||
import { Card } from "../types"
|
|
||||||
import { useDebounce } from "../utils/useDebounce"
|
import { useDebounce } from "../utils/useDebounce"
|
||||||
import notes from "../signals/notes"
|
import notes, { NoteCardType } from "../signals/notes"
|
||||||
import { LayerEnum } from "../utils/enums"
|
import { LayerEnum } from "../utils/enums"
|
||||||
|
|
||||||
namespace NoteCard {
|
namespace NoteCard {
|
||||||
export interface NoteCardProps {
|
export interface NoteCardProps {
|
||||||
key: Card['id']
|
key: NoteCardType['id']
|
||||||
data: Card
|
data: NoteCardType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,7 +22,6 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
|
|
||||||
function updateLocalStorage(time?: number) {
|
function updateLocalStorage(time?: number) {
|
||||||
debounce(() => {
|
debounce(() => {
|
||||||
console.log(itemKey, "updated storage")
|
|
||||||
localStorage.setItem("notes", JSON.stringify(notes.notes.value))
|
localStorage.setItem("notes", JSON.stringify(notes.notes.value))
|
||||||
}, time)
|
}, time)
|
||||||
}
|
}
|
||||||
@ -77,6 +75,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
|
|||||||
<button className="text-md" onclick={(_e: Event) => {
|
<button className="text-md" onclick={(_e: Event) => {
|
||||||
NotesSigal.default.removeNote(item.id)
|
NotesSigal.default.removeNote(item.id)
|
||||||
NotesSigal.default.notes.notify()
|
NotesSigal.default.notes.notify()
|
||||||
|
updateLocalStorage()
|
||||||
}}>x</button>
|
}}>x</button>
|
||||||
</div>
|
</div>
|
||||||
<hr className="border border-[#3c3c3c]" />
|
<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 const canvasDimentsion = signal({ width: 3000, height: 3000 })
|
||||||
|
|
||||||
export * as NotesSigal from "./notes"
|
export * as NotesSigal from "./notes"
|
||||||
|
export * as ImagesSignal from "./images"
|
||||||
|
@ -1,31 +1,31 @@
|
|||||||
import { signal } from "kaioken"
|
import { signal } from "kaioken"
|
||||||
import { Card } from "../types"
|
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() {
|
function loadLocalStorage() {
|
||||||
notes.value = JSON.parse(localStorage.getItem("notes") ?? "{}")
|
notes.value = JSON.parse(localStorage.getItem("notes") ?? "{}")
|
||||||
}
|
}
|
||||||
|
|
||||||
function addNote(data: Omit<Card, "id">) {
|
function addNote(data: Omit<NoteCardType, "id">) {
|
||||||
const newCard = {
|
const newCard = {
|
||||||
...data,
|
...data,
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
}
|
}
|
||||||
notes.value[newCard.id] = newCard
|
notes.value[newCard.id] = newCard
|
||||||
notes.notify()
|
notes.notify()
|
||||||
//updateLocalStorage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeNote(id: Card["id"]) {
|
function removeNote(id: NoteCardType["id"]) {
|
||||||
delete notes.value[id]
|
delete notes.value[id]
|
||||||
notes.notify()
|
notes.notify()
|
||||||
//updateLocalStorage()
|
|
||||||
}
|
}
|
||||||
function updateNoteProperty<K extends keyof Card>(
|
function updateNoteProperty<K extends keyof NoteCardType>(
|
||||||
id: Card["id"],
|
id: NoteCardType["id"],
|
||||||
property: K,
|
property: K,
|
||||||
data: Card[K]
|
data: NoteCardType[K]
|
||||||
) {
|
) {
|
||||||
const newData = {
|
const newData = {
|
||||||
...notes.value[id],
|
...notes.value[id],
|
||||||
@ -33,7 +33,6 @@ function updateNoteProperty<K extends keyof Card>(
|
|||||||
}
|
}
|
||||||
notes.value[id] = newData
|
notes.value[id] = newData
|
||||||
notes.notify()
|
notes.notify()
|
||||||
//updateLocalStorage()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -41,6 +40,5 @@ export default {
|
|||||||
addNote,
|
addNote,
|
||||||
removeNote,
|
removeNote,
|
||||||
updateNoteProperty,
|
updateNoteProperty,
|
||||||
//updateLocalStorage,
|
|
||||||
loadLocalStorage,
|
loadLocalStorage,
|
||||||
}
|
}
|
||||||
|
@ -2,11 +2,13 @@ export type CardTypes = "note" | "image"
|
|||||||
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 }
|
||||||
|
|
||||||
export interface Card {
|
type Base64 = string
|
||||||
|
|
||||||
|
export interface Card<Ttype extends CardTypes> {
|
||||||
id: string
|
id: string
|
||||||
type: CardTypes
|
type: Ttype
|
||||||
title: string
|
title: string
|
||||||
contents: string
|
contents: Ttype extends "image" ? Base64 : string
|
||||||
position: positionCoords
|
position: positionCoords
|
||||||
dimensions: dimensionCoords
|
dimensions: dimensionCoords
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ function createState(): UseDebounceState {
|
|||||||
|
|
||||||
export function useDebounce() {
|
export function useDebounce() {
|
||||||
if (!sideEffectsEnabled()) return createState()
|
if (!sideEffectsEnabled()) return createState()
|
||||||
|
|
||||||
return useHook("useDebounce", createState, ({ hook, update, isInit }) => {
|
return useHook("useDebounce", createState, ({ hook, update, isInit }) => {
|
||||||
if (!isInit) return { timer: hook.timer, debounce: hook.debounce }
|
if (!isInit) return { timer: hook.timer, debounce: hook.debounce }
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user