Compare commits

..

No commits in common. "a506da11ad7577f48275fa2d20f1c7296486bb31" and "ad8ed2f596d680db94d4d3d3aca1c13a6d568be0" have entirely different histories.

23 changed files with 122 additions and 200 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -1,28 +0,0 @@
import globals from "globals"
import pluginJs from "@eslint/js"
import tseslint from "typescript-eslint"
import pluginReact from "eslint-plugin-react"
export default [
{ files: ["./src/**/*.{js,mjs,cjs,ts,jsx,tsx}"] },
{ plugins: { react: pluginReact } },
{
languageOptions: {
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
globals: globals.browser,
},
},
pluginJs.configs.recommended,
...tseslint.configs.recommended,
pluginReact.configs.flat.recommended,
{
rules: {
"react/react-in-jsx-scope": "off",
"react/no-unknown-property": "off",
},
},
]

View File

@ -18,21 +18,14 @@
"tiny-markdown-editor": "^0.1.26"
},
"devDependencies": {
"@eslint/js": "^9.13.0",
"@tauri-apps/cli": "^1",
"autoprefixer": "^10.4.18",
"commitizen": "^4.3.1",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^9.13.0",
"eslint-plugin-react": "^7.37.2",
"globals": "^15.11.0",
"postcss": "^8.4.35",
"prettier": "^3.3.3",
"tailwindcss": "^3.4.1",
"typescript": "^5.0.2",
"typescript-eslint": "^8.11.0",
"vite": "^5.0.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-kaioken": "^0.13.1"
},
"config": {

View File

@ -1,12 +1,14 @@
import { useClickOutside, useMouse } from "@kaioken-core/hooks";
import { useClickOutside, useKeyStroke, useMouse } from "@kaioken-core/hooks";
import { Portal, signal, useEffect, useRef } from "kaioken";
interface ContextMenuPortalProps {
namespace ContextMenuPortal {
export interface Props {
children: JSX.Children
open: boolean
closeAction: (() => void) | null | undefined
}
}
export function ContextMenuPortal({ children, open, closeAction }: ContextMenuPortalProps) {
export function ContextMenuPortal({ children, open, closeAction }: ContextMenuPortal.Props) {
const { mouse } = useMouse()
const pos = signal({ x: 0, y: 0 })
const ref = useRef<HTMLDivElement>(null)

View File

@ -6,14 +6,15 @@ import images, { ImageCardType } from "../signals/images"
import { updateLocalStorage } from "../utils/localStorage"
import { useThemeDetector } from "../utils/useThemeDetector"
import { ContextMenuPortal } from "./ContextMenuPortal"
import { CardTypes } from "../types"
interface ImageCardProps {
namespace ImageCard {
export interface ImageCardProps {
key: ImageCardType['id']
data: ImageCardType
}
}
export function ImageCard({ key: itemKey, data: item }: ImageCardProps) {
export function ImageCard({ key: itemKey, data: item }: ImageCard.ImageCardProps) {
const { debounce } = useDebounce()
const pressed = signal(false)
const newX = useRef(0)
@ -26,7 +27,7 @@ export function ImageCard({ key: itemKey, data: item }: ImageCardProps) {
function debounceLSUpdate(time?: number) {
debounce(() => {
updateLocalStorage(CardTypes.IMAGES, images.images).notify()
updateLocalStorage("images", images.images.value)
}, time)
}
@ -89,7 +90,7 @@ export function ImageCard({ key: itemKey, data: item }: ImageCardProps) {
window.removeEventListener('mouseup', _handleResizeMouseUp)
}
function _handleClose() {
function _handleClose(_e: Event) {
ImagesSignal.default.removeImage(item.id)
ImagesSignal.default.images.notify()
debounceLSUpdate()
@ -160,7 +161,7 @@ export function ImageCard({ key: itemKey, data: item }: ImageCardProps) {
function ExpandIcon({ cb }: {
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => void) | null | undefined
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null | undefined
}) {
const isDarkTheme = useThemeDetector()
return (

View File

@ -110,7 +110,7 @@ function getInitialPosition(canvasDimensions: typeof canvasDimentsion): ScrollTo
try {
initPosition = JSON.parse(localStorage.getItem("pos") ?? "")
} catch (e) {
console.error("no local storage for pos " + e)
console.error("no local storage for pos")
}
if (!initPosition) return defaultScroll

View File

@ -2,12 +2,14 @@ import { Editor, Listener } from 'tiny-markdown-editor'
import './md.css'
import { useEffect, useRef } from "kaioken"
interface MarkDownEditorProps {
namespace MarkDownEditor {
export interface Props {
initial: string
onChange: Listener<'change'>
}
}
export function MarkDownEditor({ initial, onChange }: MarkDownEditorProps) {
export function MarkDownEditor({ initial, onChange }: MarkDownEditor.Props) {
const elRef = useRef<HTMLDivElement>(null)
useEffect(() => {

View File

@ -24,7 +24,7 @@ export function MiniMap() {
useEffect(() => {
function _handleScroll() {
function _handleScroll(_e: Event) {
scrollX.value = window.scrollX
scrollY.value = window.scrollY
}
@ -47,7 +47,7 @@ export function MiniMap() {
const image = images.images.value[imageKey]
const el = useRef(null)
function _handleItemClick() {
function _handleItemClick(_e: MouseEvent) {
const newLeft = image.position.x - ((viewportWidth / 2) - (image.dimensions.w / 2))
const newTop = image.position.y - ((viewportHeight / 2) - (image.dimensions.h / 2))
@ -65,7 +65,7 @@ export function MiniMap() {
const newZIndex = LayerEnum.MINIMAP + 1
return (
<div key={imageKey} ref={el} className={"absolute dark:bg-green-500 bg-green-300 dark:hover:bg-blue-500 hover:bg-blue-300 cursor-pointer border dark:border-[#222] border-green-500 rounded"}
<div ref={el} className={"absolute dark:bg-green-500 bg-green-300 dark:hover:bg-blue-500 hover:bg-blue-300 cursor-pointer border dark:border-[#222] border-green-500 rounded"}
onclick={_handleItemClick}
style={{
width: `${newWidth}px`,
@ -82,7 +82,7 @@ export function MiniMap() {
{Object.keys(notes.notes.value).map((noteKey: NoteCardType['id']) => {
const note = notes.notes.value[noteKey]
function _handleItemClick() {
function _handleItemClick(_e: MouseEvent) {
const newLeft = note.position.x - ((viewportWidth / 2) - (note.dimensions.w / 2))
const newTop = note.position.y - ((viewportHeight / 2) - (note.dimensions.h / 2))
window.scrollTo({
@ -99,7 +99,7 @@ export function MiniMap() {
const newZIndex = LayerEnum.MINIMAP + 1
return (
<div key={noteKey} className={"absolute dark:bg-gray-500 bg-gray-300 hover:bg-blue-500 cursor-pointer border dark:border-[#222] border-gray-400 rounded"}
<div className={"absolute dark:bg-gray-500 bg-gray-300 hover:bg-blue-500 cursor-pointer border dark:border-[#222] border-gray-400 rounded"}
onclick={_handleItemClick}
style={{
width: `${newWidth}px`,
@ -116,7 +116,7 @@ export function MiniMap() {
const text = texts.texts.value[textKey]
const el = useRef(null)
function _handleItemClick() {
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)),
@ -125,7 +125,7 @@ export function MiniMap() {
}
return (
<div key={textKey} ref={el} className={"bg-indigo-500 hover:bg-blue-500 cursor-pointer rounded"}
<div ref={el} className={"bg-indigo-500 hover:bg-blue-500 cursor-pointer rounded"}
onclick={_handleItemClick}
style={{
position: 'absolute',

View File

@ -12,12 +12,14 @@ import { createFileAndExport } from "../utils/createFileAndExport"
import { ContextMenuPortal } from "./ContextMenuPortal"
import { HelpIcon } from "./icons/HelpIcon"
interface NoteCardProps {
namespace NoteCard {
export interface NoteCardProps {
key: NoteCardType['id']
data: NoteCardType
}
}
export function NoteCard({ key: itemKey, data: item }: NoteCardProps) {
export function NoteCard({ key: itemKey, data: item }: NoteCard.NoteCardProps) {
const saved = signal(true)
const pressed = signal(false)
const newX = useRef(0)
@ -101,7 +103,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCardProps) {
saved.value = false
}
function _handleClose() {
function _handleClose(_e: Event) {
NotesSigal.default.removeNote(item.id)
NotesSigal.default.notes.notify()
updateLocalStorage()
@ -115,7 +117,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCardProps) {
createFileAndExport("Note", item.contents, "text/markdown")
}
function _handleExportClick() {
function _handleExportClick(_e: MouseEvent) {
_exportFile()
}
@ -147,11 +149,11 @@ export function NoteCard({ key: itemKey, data: item }: NoteCardProps) {
switch (e.key) {
case 'Delete':
e.preventDefault()
_handleClose()
_handleClose(e)
break
case 'Backspace':
e.preventDefault()
_handleClose()
_handleClose(e)
break
case 'e':
e.preventDefault()
@ -271,7 +273,7 @@ export function NoteCard({ key: itemKey, data: item }: NoteCardProps) {
}
function ExpandIcon({ cb }: {
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => void) | null | undefined
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null | undefined
}) {
const isDarkTheme = useThemeDetector()

View File

@ -3,15 +3,17 @@ import { TextSignal, focusedItem } from "../signals"
import { useDebounce } from "../utils/useDebounce"
import texts, { TextCardType } from "../signals/texts"
import { LayerEnum } from "../utils/enums"
import { Card, CardTypes } from "../types"
import { Card } from "../types"
import { useThemeDetector } from "../utils/useThemeDetector"
interface TextCardProps {
namespace TextItem {
export interface TextCardProps {
key: TextCardType['id']
data: TextCardType
}
}
export function TextItem({ key: itemKey, data: item }: TextCardProps) {
export function TextItem({ key: itemKey, data: item }: TextItem.TextCardProps) {
const { debounce } = useDebounce()
const pressed = signal(false)
const newX = useRef(0)
@ -26,7 +28,7 @@ export function TextItem({ key: itemKey, data: item }: TextCardProps) {
const elDems = elRef.current?.getBoundingClientRect()
const elW = elDems?.width ?? 100
const elH = elDems?.height ?? 100
const newDems: Card<CardTypes.TEXTS>['dimensions'] = { w: elW, h: elH }
const newDems: Card<'texts'>['dimensions'] = { w: elW, h: elH }
TextSignal.default.updateTextProperty(itemKey, 'dimensions', newDems)
TextSignal.default.texts.notify()
}, [elRef.current, item.fontSize])
@ -35,7 +37,7 @@ export function TextItem({ key: itemKey, data: item }: TextCardProps) {
function updateLocalStorage(time?: number) {
debounce(() => {
localStorage.setItem(CardTypes.TEXTS, JSON.stringify(texts.texts.value))
localStorage.setItem("texts", JSON.stringify(texts.texts.value))
}, time)
}
@ -85,7 +87,7 @@ export function TextItem({ key: itemKey, data: item }: TextCardProps) {
window.addEventListener('mouseup', _handleResizeMouseUp)
}
function _handleResizeMouseUp() {
function _handleResizeMouseUp(_e: MouseEvent) {
pressed.value = false
window.removeEventListener('mousemove', _handleResizeMove)
window.removeEventListener('mouseup', _handleResizeMouseUp)
@ -155,7 +157,7 @@ export function TextItem({ key: itemKey, data: item }: TextCardProps) {
>
<p
ref={pRef}
//@ts-expect-error oninput doesnt exist on the props type
//@ts-expect-error
oninput={_handleContentInput}
contentEditable
className={'text-p inline-block px-2 w-full select-none drop-shadow relative'}>
@ -169,12 +171,13 @@ export function TextItem({ key: itemKey, data: item }: TextCardProps) {
)
}
interface CloseIconProps {
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => void) | null | undefined,
namespace CloseIcon {
export interface Props {
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null | undefined,
item: TextSignal.TextCardType
}
}
function CloseIcon({ item, cb }: CloseIconProps) {
function CloseIcon({ item, cb }: CloseIcon.Props) {
const isDark = useThemeDetector()
return (
<svg
@ -201,12 +204,13 @@ function CloseIcon({ item, cb }: CloseIconProps) {
)
}
interface ExpandIconProps {
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => void) | null | undefined,
namespace ExpandIcon {
export interface Props {
cb: ((this: GlobalEventHandlers, ev: MouseEvent) => any) | null | undefined,
item: TextSignal.TextCardType
}
}
function ExpandIcon({ cb, item }: ExpandIconProps) {
function ExpandIcon({ cb, item }: ExpandIcon.Props) {
const isDark = useThemeDetector()
return (
<svg

View File

@ -8,7 +8,6 @@ import {
useEffect,
useState,
} from "kaioken"
import { noop } from "kaioken/utils"
type Toast = {
ts: number
@ -22,15 +21,11 @@ const defaultDuration = 3000
const ToastContext = createContext<{
showToast: (type: Toast["type"], message: string) => void
}>({ showToast: noop })
}>(null as any)
export const useToast = () => useContext(ToastContext)
interface ToastProviderProps {
children: JSX.Children
}
export function ToastContextProvider({ children }: ToastProviderProps) {
export const ToastContextProvider: Kaioken.FC = ({ children }) => {
const [toasts, setToasts] = useState<Toast[]>([])
useEffect(() => {

View File

@ -1,6 +1,5 @@
import { ImagesSignal } from "../../signals"
import images from "../../signals/images"
import { CardTypes } from "../../types"
import { updateLocalStorage } from "../../utils/localStorage"
import { useToast } from "../Toast"
import { Tooltip } from "./Tooltip"
@ -14,16 +13,12 @@ export function ImageCardButton() {
input.accept = "image/*"
input.multiple = false
input.onchange = (e: Event) => {
const el = e.target as HTMLInputElement
if (!el.files?.length) return
if (!el.files.length) return
const file = el.files[0]
input.onchange = (e: any) => {
const file = e.target.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function(readerEvent) {
const image = document.createElement('img')
let image = document.createElement('img')
image.onload = function() {
const { width, height } = image
@ -38,7 +33,7 @@ export function ImageCardButton() {
if (!img) return
const imgId = ImagesSignal.default.addImage({
type: CardTypes.IMAGES,
type: "image",
title: "New Image",
contents: content as string,
position: {
@ -52,7 +47,7 @@ export function ImageCardButton() {
})
try {
updateLocalStorage(CardTypes.IMAGES, images.images).notify()
updateLocalStorage("images", images.images.value)
} catch (e: unknown) {
if (e instanceof DOMException) {
if (e.name !== 'QuotaExceededError') return

View File

@ -1,7 +1,6 @@
import images, { ImageCardType } from "../../signals/images"
import notes, { NoteCardType } from "../../signals/notes"
import texts, { TextCardType } from "../../signals/texts"
import { Card, CardTypes } from "../../types"
import images from "../../signals/images"
import notes from "../../signals/notes"
import { Card } from "../../types"
import { convertBase64ToJson } from "../../utils/convertBase64ToJson"
import { updateLocalStorage } from "../../utils/localStorage"
import { Tooltip } from "./Tooltip"
@ -10,55 +9,36 @@ import { defaultClassName } from "./utils"
export function ImportButton() {
function _handleImport() {
// guard clause to prevent overwriting existing cards
if (images.images.value || notes.notes.value) {
const isConfirmed = confirm("Are you sure you want to overwrite your existing cards?")
if (!isConfirmed) return
}
const input = document.createElement('input')
input.type = 'file'
input.accept = ".json"
input.multiple = false
input.onchange = (e: Event) => {
if (e.target === null) return
const el = e.target as HTMLInputElement
if (!el.files?.length) return
const file = el.files[0]
input.onchange = (e: any) => {
const file = e.target.files[0]
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function(readerEvent) {
let content = readerEvent.target?.result;
// get only the base64 parts and not any identifiers
content = (content as string).split(',')[1]
const data: Record<string, Card<CardTypes>> = convertBase64ToJson(content)
console.log(data)
for (const key in data) {
const data: Record<string, Card<'notes'> | Card<'images'>> = convertBase64ToJson(content)
for (let key in data) {
const item = data[key]
if (item.type == 'images') {
const { id, ...rest } = item
console.log(id, rest)
switch (item.type) {
case CardTypes.IMAGES:
console.log("adding image: ", rest)
images.addImage(rest as ImageCardType)
break;
case CardTypes.NOTES:
notes.addNote(rest as NoteCardType)
break;
case CardTypes.TEXTS:
texts.addText(rest as TextCardType)
break;
default:
break;
images.addImage(rest)
}
if (item.type == 'notes') {
const { id, ...rest } = item
notes.addNote(rest)
}
}
console.log("images: ", images.images.value)
updateLocalStorage(CardTypes.NOTES, notes.notes).notify()
updateLocalStorage(CardTypes.IMAGES, images.images).notify()
updateLocalStorage(CardTypes.TEXTS, texts.texts).notify()
updateLocalStorage('notes', notes.notes.value)
updateLocalStorage('images', images.images.value)
notes.notes.notify()
images.images.notify()
}
}
input.click()

View File

@ -21,7 +21,7 @@ export function TextButton() {
h: 100
}
})
updateLocalStorage("texts", texts.texts).notify()
updateLocalStorage("texts", texts.texts.value)
}
return (

View File

@ -1,8 +1,9 @@
interface ExportIconProps {
namespace ExportIcon {
export interface Props {
className?: string
}
}
export function ExportIcon({ className }: ExportIconProps) {
export function ExportIcon({ className }: ExportIcon.Props) {
return (
<svg

View File

@ -1,9 +1,10 @@
interface HelpIconProps {
namespace HelpIcon {
export interface Props {
onMouseOver?: () => void
onMouseOut?: () => void
}
}
export function HelpIcon({ onMouseOver, onMouseOut }: HelpIconProps) {
export function HelpIcon({ onMouseOver, onMouseOut }: HelpIcon.Props) {
return (
<svg

View File

@ -1,8 +1,8 @@
import { signal } from "kaioken"
import { Card, CardTypes } from "../types"
import { Card } from "../types"
import { focusedItem } from "."
export type ImageCardType = Card<CardTypes.IMAGES>
export type ImageCardType = Card<"images">
const images = signal<Record<ImageCardType["id"], ImageCardType>>({})
@ -15,7 +15,6 @@ function addImage(data: Omit<ImageCardType, "id">) {
...data,
id: crypto.randomUUID(),
}
console.log("adding image: ", newCard)
images.value[newCard.id] = newCard
images.notify()
focusedItem.value = newCard.id

View File

@ -1,8 +1,8 @@
import { signal } from "kaioken"
import { Card, CardTypes } from "../types"
import { Card } from "../types"
import { focusedItem } from "."
export type NoteCardType = Card<CardTypes.NOTES>
export type NoteCardType = Card<"notes">
const notes = signal<Record<NoteCardType["id"], NoteCardType>>({})

View File

@ -1,8 +1,8 @@
import { signal } from "kaioken"
import { Card, CardTypes } from "../types"
import { Card } from "../types"
import { focusedItem } from "."
export type TextCardType = Card<CardTypes.TEXTS> & {
export type TextCardType = Card<"texts"> & {
fontSize: number
}

View File

@ -1,19 +1,14 @@
export type CardTypes = "notes" | "images" | "texts"
export type positionCoords = { x: number; y: number }
export type dimensionCoords = { w: number; h: number }
export enum CardTypes {
NOTES = "notes",
IMAGES = "images",
TEXTS = "texts",
}
type Base64 = string
export interface Card<TCard extends CardTypes> {
export interface Card<Ttype extends CardTypes> {
id: string
type: TCard
type: Ttype
title: string
contents: TCard extends "image" ? Base64 : string
contents: Ttype extends "image" ? Base64 : string
position: positionCoords
dimensions: dimensionCoords
}

View File

@ -1,17 +1,8 @@
import { Signal } from "kaioken"
import { Card, CardTypes } from "../types"
import { CardTypes } from "../types"
export function updateLocalStorage(
location: CardTypes,
collection: Signal<Record<string, Card<CardTypes>>>
collection: unknown[] | Record<string, unknown>
) {
try {
localStorage.setItem(location, JSON.stringify(collection.value))
} catch (e) {
throw new DOMException(
"Could not update local storage " + e,
"LocalStorageError"
)
}
return collection
localStorage.setItem(location, JSON.stringify(collection))
}

View File

@ -2,12 +2,12 @@ import { sideEffectsEnabled, useHook } from "kaioken"
import { noop } from "kaioken/utils"
type UseDebounceState = {
timer: Timer | undefined
debounce: (func: (...args: unknown[]) => void, timeout?: number) => void
timer: number
debounce: (this: any, func: Function, timeout?: number) => void
}
function createState(): UseDebounceState {
return { timer: undefined, debounce: noop }
return { timer: 0, debounce: noop }
}
export function useDebounce() {
@ -17,7 +17,8 @@ export function useDebounce() {
if (!isInit) return { timer: hook.timer, debounce: hook.debounce }
hook.debounce = function debounce(
func: (...args: unknown[]) => void,
this: any,
func: Function,
timeout = 300
) {
clearTimeout(hook.timer)

View File

@ -1,21 +1,9 @@
import { defineConfig } from "vite"
import kaioken from "vite-plugin-kaioken"
import eslint from "vite-plugin-eslint"
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
kaioken(),
eslint({
cache: true,
exclude: ["src-tauri/**/*"],
emitError: true,
emitWarning: true,
failOnError: false,
failOnWarning: false,
lintOnStart: true,
}),
],
plugins: [kaioken()],
// Vite options tailored for Tauri development and only applied in `tauri dev` or `tauri build`
//
// 1. prevent vite from obscuring rust errors