Initial skeleton: React/TS frontend + Go backend structure
This commit is contained in:
37
client/src/utils/crdt.ts
Normal file
37
client/src/utils/crdt.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import * as Y from 'yjs'
|
||||
|
||||
let doc: Y.Doc | null = null
|
||||
let provider: any | null = null
|
||||
|
||||
export const initCRDT = (roomName: string, signalingUrl: string) => {
|
||||
doc = new Y.Doc()
|
||||
|
||||
provider = new (require('y-webrtc').WebrtcProvider)(
|
||||
roomName,
|
||||
doc,
|
||||
{
|
||||
signaling: [signalingUrl],
|
||||
}
|
||||
)
|
||||
|
||||
provider.on('status', (status: string) => {
|
||||
console.log('[CRDT] Status:', status)
|
||||
})
|
||||
|
||||
return doc
|
||||
}
|
||||
|
||||
export const getCRDTDoc = (): Y.Doc | null => {
|
||||
return doc
|
||||
}
|
||||
|
||||
export const getCRDTProvider = () => {
|
||||
return provider
|
||||
}
|
||||
|
||||
export const destroyCRDT = () => {
|
||||
provider?.destroy()
|
||||
doc?.destroy()
|
||||
doc = null
|
||||
provider = null
|
||||
}
|
||||
50
client/src/utils/fuzzy.ts
Normal file
50
client/src/utils/fuzzy.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
const MAX_DISTANCE = 1000
|
||||
|
||||
export const fuzzySearch = (query: string, text: string): number => {
|
||||
if (!query) return 0
|
||||
|
||||
const queryLower = query.toLowerCase()
|
||||
const textLower = text.toLowerCase()
|
||||
|
||||
if (textLower.startsWith(queryLower)) {
|
||||
return 1 - query.length / text.length
|
||||
}
|
||||
|
||||
let score = 0
|
||||
let queryIndex = 0
|
||||
|
||||
for (let i = 0; i < textLower.length && queryIndex < queryLower.length; i++) {
|
||||
if (textLower[i] === queryLower[queryIndex]) {
|
||||
score++
|
||||
queryIndex++
|
||||
}
|
||||
}
|
||||
|
||||
if (score === 0) return 0
|
||||
|
||||
const matchRatio = score / query.length
|
||||
const positionBonus = queryIndex === query.length ? 0.2 : 0
|
||||
const lengthPenalty = Math.min(1, text.length / MAX_DISTANCE)
|
||||
|
||||
return (matchRatio * 0.7 + positionBonus) * lengthPenalty
|
||||
}
|
||||
|
||||
export const fuzzyFilter = <T extends { name: string; type?: string }>(
|
||||
items: T[],
|
||||
query: string,
|
||||
typeFilter?: string
|
||||
): T[] => {
|
||||
if (!query && !typeFilter) return items
|
||||
|
||||
return items
|
||||
.map((item) => ({
|
||||
item,
|
||||
score: fuzzySearch(query, item.name),
|
||||
}))
|
||||
.filter(
|
||||
({ item, score }) =>
|
||||
score > 0.3 && (!typeFilter || typeFilter === 'all' || item.type === typeFilter)
|
||||
)
|
||||
.sort((a, b) => b.score - a.score)
|
||||
.map(({ item }) => item)
|
||||
}
|
||||
27
client/src/utils/keyboard.ts
Normal file
27
client/src/utils/keyboard.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
export const isKey = (e: KeyboardEvent | React.KeyboardEvent, key: string): boolean => {
|
||||
return e.key.toLowerCase() === key.toLowerCase()
|
||||
}
|
||||
|
||||
export const isModifierKey = (e: KeyboardEvent | React.KeyboardEvent): boolean => {
|
||||
return e.ctrlKey || e.metaKey || e.altKey
|
||||
}
|
||||
|
||||
export const getMouseClickPosition = (e: React.MouseEvent | MouseEvent) => {
|
||||
return {
|
||||
x: e.clientX,
|
||||
y: e.clientY,
|
||||
}
|
||||
}
|
||||
|
||||
export const getKeyboardSelectionDelta = (e: KeyboardEvent | React.KeyboardEvent): { dx: number; dy: number } => {
|
||||
const step = e.shiftKey ? 10 : 1
|
||||
let dx = 0
|
||||
let dy = 0
|
||||
|
||||
if (e.key === 'ArrowUp') dy = -step
|
||||
else if (e.key === 'ArrowDown') dy = step
|
||||
else if (e.key === 'ArrowLeft') dx = -step
|
||||
else if (e.key === 'ArrowRight') dx = step
|
||||
|
||||
return { dx, dy }
|
||||
}
|
||||
41
client/src/utils/math.ts
Normal file
41
client/src/utils/math.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
export const degreesToRadians = (degrees: number): number => {
|
||||
return (degrees * Math.PI) / 180
|
||||
}
|
||||
|
||||
export const radiansToDegrees = (radians: number): number => {
|
||||
return (radians * 180) / Math.PI
|
||||
}
|
||||
|
||||
export const rotatePoint = (
|
||||
x: number,
|
||||
y: number,
|
||||
centerX: number,
|
||||
centerY: number,
|
||||
angleDegrees: number
|
||||
): { x: number; y: number } => {
|
||||
const angle = degreesToRadians(angleDegrees)
|
||||
const cos = Math.cos(angle)
|
||||
const sin = Math.sin(angle)
|
||||
|
||||
const dx = x - centerX
|
||||
const dy = y - centerY
|
||||
|
||||
return {
|
||||
x: centerX + dx * cos - dy * sin,
|
||||
y: centerY + dx * sin + dy * cos,
|
||||
}
|
||||
}
|
||||
|
||||
export const distance = (x1: number, y1: number, x2: number, y2: number): number => {
|
||||
const dx = x2 - x1
|
||||
const dy = y2 - y1
|
||||
return Math.sqrt(dx * dx + dy * dy)
|
||||
}
|
||||
|
||||
export const clamp = (value: number, min: number, max: number): number => {
|
||||
return Math.min(Math.max(value, min), max)
|
||||
}
|
||||
|
||||
export const lerp = (start: number, end: number, t: number): number => {
|
||||
return start * (1 - t) + end * t
|
||||
}
|
||||
Reference in New Issue
Block a user