mirror of
https://github.com/linsa-io/linsa.git
synced 2026-01-12 12:20:23 +01:00
force graph, palette
This commit is contained in:
@@ -1,24 +1,106 @@
|
||||
"use client"
|
||||
|
||||
import { createJazzReactContext, DemoAuth } from "jazz-react"
|
||||
import { AuthUI } from "@/components/custom/auth-ui"
|
||||
import { createJazzReactApp } from "jazz-react"
|
||||
import { LaAccount } from "@/lib/schema"
|
||||
import { useClerk } from "@clerk/nextjs"
|
||||
import { createContext, useMemo, useState } from "react"
|
||||
import { AuthMethodCtx } from "jazz-react"
|
||||
|
||||
const appName = process.env.NEXT_PUBLIC_APP_NAME!
|
||||
|
||||
const auth = DemoAuth<LaAccount>({
|
||||
appName,
|
||||
Component: AuthUI,
|
||||
accountSchema: LaAccount
|
||||
})
|
||||
|
||||
const Jazz = createJazzReactContext({
|
||||
auth,
|
||||
peer: "wss://mesh.jazz.tools/?key=example@gmail.com"
|
||||
const Jazz = createJazzReactApp({
|
||||
AccountSchema: LaAccount
|
||||
})
|
||||
|
||||
export const { useAccount, useCoState, useAcceptInvite } = Jazz
|
||||
|
||||
export function JazzProvider({ children }: { children: React.ReactNode }) {
|
||||
return <Jazz.Provider>{children}</Jazz.Provider>
|
||||
return <Jazz.Provider peer="wss://mesh.jazz.tools/?key=example@gmail.com">{children}</Jazz.Provider>
|
||||
}
|
||||
|
||||
export const JazzClerkAuthCtx = createContext<{
|
||||
errors: string[]
|
||||
}>({
|
||||
errors: []
|
||||
})
|
||||
|
||||
export function JazzClerkAuth({ children }: { children: React.ReactNode }) {
|
||||
const clerk = useClerk()
|
||||
const [errors, setErrors] = useState<string[]>([])
|
||||
|
||||
const authMethod = useMemo(() => {
|
||||
return new BrowserClerkAuth(
|
||||
{
|
||||
onError: error => {
|
||||
void clerk.signOut()
|
||||
setErrors(errors => [...errors, error.toString()])
|
||||
}
|
||||
},
|
||||
clerk
|
||||
)
|
||||
}, [clerk])
|
||||
|
||||
return (
|
||||
<JazzClerkAuthCtx.Provider value={{ errors }}>
|
||||
<AuthMethodCtx.Provider value={authMethod}>{children}</AuthMethodCtx.Provider>
|
||||
</JazzClerkAuthCtx.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
import { Account, AuthMethod, AuthResult, ID } from "jazz-tools"
|
||||
import type { LoadedClerk } from "@clerk/types"
|
||||
import { AgentSecret } from "cojson"
|
||||
|
||||
export class BrowserClerkAuth implements AuthMethod {
|
||||
constructor(
|
||||
public driver: BrowserClerkAuth.Driver,
|
||||
private readonly clerkClient: LoadedClerk
|
||||
) {}
|
||||
|
||||
async start(): Promise<AuthResult> {
|
||||
if (this.clerkClient.user) {
|
||||
const storedCredentials = this.clerkClient.user.unsafeMetadata
|
||||
if (storedCredentials.jazzAccountID) {
|
||||
if (!storedCredentials.jazzAccountSecret) {
|
||||
throw new Error("No secret for existing user")
|
||||
}
|
||||
return {
|
||||
type: "existing",
|
||||
credentials: {
|
||||
accountID: storedCredentials.jazzAccountID as ID<Account>,
|
||||
secret: storedCredentials.jazzAccountSecret as AgentSecret
|
||||
},
|
||||
onSuccess: () => {},
|
||||
onError: (error: string | Error) => {
|
||||
this.driver.onError(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
type: "new",
|
||||
creationProps: {
|
||||
name: this.clerkClient.user.fullName || this.clerkClient.user.username || this.clerkClient.user.id
|
||||
},
|
||||
saveCredentials: async (credentials: { accountID: ID<Account>; secret: AgentSecret }) => {
|
||||
await this.clerkClient.user?.update({
|
||||
unsafeMetadata: {
|
||||
jazzAccountID: credentials.accountID,
|
||||
jazzAccountSecret: credentials.secret
|
||||
}
|
||||
})
|
||||
},
|
||||
onSuccess: () => {},
|
||||
onError: (error: string | Error) => {
|
||||
this.driver.onError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
throw new Error("Not signed in")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export namespace BrowserClerkAuth {
|
||||
export interface Driver {
|
||||
onError: (error: string | Error) => void
|
||||
}
|
||||
}
|
||||
|
||||
52
web/lib/utils/canvas.ts
Normal file
52
web/lib/utils/canvas.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/**
|
||||
* Resizes the canvas to match the size it is being displayed.
|
||||
*
|
||||
* @param canvas the canvas to resize
|
||||
* @returns `true` if the canvas was resized
|
||||
*/
|
||||
export function resizeCanvasToDisplaySize(canvas: HTMLCanvasElement): boolean {
|
||||
// Get the size the browser is displaying the canvas in device pixels.
|
||||
let dpr = window.devicePixelRatio
|
||||
let {width, height} = canvas.getBoundingClientRect()
|
||||
let display_width = Math.round(width * dpr)
|
||||
let display_height = Math.round(height * dpr)
|
||||
|
||||
let need_resize = canvas.width != display_width ||
|
||||
canvas.height != display_height
|
||||
|
||||
if (need_resize) {
|
||||
canvas.width = display_width
|
||||
canvas.height = display_height
|
||||
}
|
||||
|
||||
return need_resize
|
||||
}
|
||||
|
||||
export interface CanvasResizeObserver {
|
||||
/** Canvas was resized since last check. Set it to `false` to reset. */
|
||||
resized: boolean
|
||||
canvas: HTMLCanvasElement
|
||||
observer: ResizeObserver
|
||||
}
|
||||
|
||||
export function resize(observer: CanvasResizeObserver): boolean {
|
||||
let resized = resizeCanvasToDisplaySize(observer.canvas)
|
||||
observer.resized ||= resized
|
||||
return resized
|
||||
}
|
||||
|
||||
export function resizeObserver(canvas: HTMLCanvasElement): CanvasResizeObserver {
|
||||
let cro: CanvasResizeObserver = {
|
||||
resized: false,
|
||||
canvas: canvas,
|
||||
observer: null!,
|
||||
}
|
||||
cro.observer = new ResizeObserver(resize.bind(null, cro))
|
||||
resize(cro)
|
||||
cro.observer.observe(canvas)
|
||||
return cro
|
||||
}
|
||||
|
||||
export function clear(observer: CanvasResizeObserver): void {
|
||||
observer.observer.disconnect()
|
||||
}
|
||||
148
web/lib/utils/schedule.ts
Normal file
148
web/lib/utils/schedule.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
export interface Scheduler<Args extends unknown[]> {
|
||||
trigger: (...args: Args) => void,
|
||||
clear: () => void,
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback that is debounced and cancellable. The debounced callback is called on **trailing** edge.
|
||||
*
|
||||
* @param callback The callback to debounce
|
||||
* @param wait The duration to debounce in milliseconds
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const debounce = schedule.debounce((message: string) => console.log(message), 250)
|
||||
* debounce.trigger('Hello!')
|
||||
* debounce.clear() // clears a timeout in progress
|
||||
* ```
|
||||
*/
|
||||
export function debounce<Args extends unknown[]>(
|
||||
callback: (...args: Args) => void,
|
||||
wait?: number,
|
||||
): Debounce<Args> {
|
||||
return new Debounce(callback, wait)
|
||||
}
|
||||
|
||||
export class Debounce<Args extends unknown[]> implements Scheduler<Args> {
|
||||
timeout_id: ReturnType<typeof setTimeout> | undefined
|
||||
|
||||
constructor(
|
||||
public callback: (...args: Args) => void,
|
||||
public wait?: number
|
||||
) {}
|
||||
|
||||
trigger(...args: Args): void {
|
||||
if (this.timeout_id !== undefined) {
|
||||
this.clear()
|
||||
}
|
||||
this.timeout_id = setTimeout(() => {
|
||||
this.callback(...args)
|
||||
}, this.wait)
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
clearTimeout(this.timeout_id)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback that is throttled and cancellable. The throttled callback is called on **trailing** edge.
|
||||
*
|
||||
* @param callback The callback to throttle
|
||||
* @param wait The duration to throttle
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const throttle = schedule.throttle((val: string) => console.log(val), 250)
|
||||
* throttle.trigger('my-new-value')
|
||||
* throttle.clear() // clears a timeout in progress
|
||||
* ```
|
||||
*/
|
||||
export function throttle<Args extends unknown[]>(
|
||||
callback: (...args: Args) => void,
|
||||
wait?: number,
|
||||
): Throttle<Args> {
|
||||
return new Throttle(callback, wait)
|
||||
}
|
||||
|
||||
export class Throttle<Args extends unknown[]> implements Scheduler<Args> {
|
||||
is_throttled = false
|
||||
timeout_id: ReturnType<typeof setTimeout> | undefined
|
||||
last_args: Args | undefined
|
||||
|
||||
constructor(
|
||||
public callback: (...args: Args) => void,
|
||||
public wait?: number
|
||||
) {}
|
||||
|
||||
trigger(...args: Args): void {
|
||||
this.last_args = args
|
||||
if (this.is_throttled) {
|
||||
return
|
||||
}
|
||||
this.is_throttled = true
|
||||
this.timeout_id = setTimeout(() => {
|
||||
this.callback(...this.last_args as Args)
|
||||
this.is_throttled = false
|
||||
}, this.wait)
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
clearTimeout(this.timeout_id)
|
||||
this.is_throttled = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a callback throttled using `window.requestIdleCallback()`. ([MDN reference](https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback))
|
||||
*
|
||||
* The throttled callback is called on **trailing** edge.
|
||||
*
|
||||
* @param callback The callback to throttle
|
||||
* @param max_wait maximum wait time in milliseconds until the callback is called
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const idle = schedule.scheduleIdle((val: string) => console.log(val), 250)
|
||||
* idle.trigger('my-new-value')
|
||||
* idle.clear() // clears a timeout in progress
|
||||
* ```
|
||||
*/
|
||||
export function scheduleIdle<Args extends unknown[]>(
|
||||
callback: (...args: Args) => void,
|
||||
max_wait?: number,
|
||||
): ScheduleIdle<Args> | Throttle<Args> {
|
||||
return typeof requestIdleCallback == "function"
|
||||
? new ScheduleIdle(callback, max_wait)
|
||||
: new Throttle(callback)
|
||||
}
|
||||
|
||||
export class ScheduleIdle<Args extends unknown[]> implements Scheduler<Args> {
|
||||
is_deferred = false
|
||||
request_id: ReturnType<typeof requestIdleCallback> | undefined
|
||||
last_args: Args | undefined
|
||||
|
||||
constructor(
|
||||
public callback: (...args: Args) => void,
|
||||
public max_wait?: number,
|
||||
) {}
|
||||
|
||||
trigger(...args: Args): void {
|
||||
this.last_args = args
|
||||
if (this.is_deferred) {
|
||||
return
|
||||
}
|
||||
this.is_deferred = true
|
||||
this.request_id = requestIdleCallback(() => {
|
||||
this.callback(...this.last_args as Args)
|
||||
this.is_deferred = false
|
||||
}, {timeout: this.max_wait})
|
||||
}
|
||||
|
||||
clear(): void {
|
||||
if (this.request_id != undefined) {
|
||||
cancelIdleCallback(this.request_id)
|
||||
}
|
||||
this.is_deferred = false
|
||||
}
|
||||
}
|
||||
28
web/lib/utils/window-size.ts
Normal file
28
web/lib/utils/window-size.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as react from "react"
|
||||
|
||||
export type WindowSize = {
|
||||
width: number,
|
||||
height: number,
|
||||
}
|
||||
|
||||
export function getWindowSize(): WindowSize {
|
||||
return {
|
||||
width: window.innerWidth,
|
||||
height: window.innerHeight,
|
||||
}
|
||||
}
|
||||
|
||||
export function useWindowSize(): WindowSize {
|
||||
|
||||
let [window_size, setWindowSize] = react.useState(getWindowSize())
|
||||
|
||||
react.useEffect(() => {
|
||||
function handleResize() {
|
||||
setWindowSize(getWindowSize())
|
||||
}
|
||||
window.addEventListener("resize", handleResize)
|
||||
return () => window.removeEventListener("resize", handleResize)
|
||||
}, [])
|
||||
|
||||
return window_size
|
||||
}
|
||||
Reference in New Issue
Block a user