Working streams

This commit is contained in:
Peter Millauer
2026-05-10 14:56:01 +02:00
parent d2de2452a8
commit b22971ef35
6 changed files with 53 additions and 34 deletions
+1
View File
@@ -11,6 +11,7 @@ node_modules
dist dist
dist-ssr dist-ssr
*.local *.local
circles-pictures
# Editor directories and files # Editor directories and files
.vscode/* .vscode/*
+3 -1
View File
@@ -1,3 +1,5 @@
import { Stroke } from "./canvas-state"; import { Stroke } from "./canvas-state";
export type ListenCallbackParam = { strokeId: number, stroke: Stroke } export type StrokeStreamElement = { strokeId: number, stroke: Stroke }
export type OnStrokeCallback = (state: StrokeStreamElement) => void
@@ -23,29 +23,25 @@ export class ClientDrawService {
private readonly densityValue = document.getElementById('densityValue')!, private readonly densityValue = document.getElementById('densityValue')!,
private readonly widthSlider = document.getElementById('widthSlider')!, private readonly widthSlider = document.getElementById('widthSlider')!,
private readonly widthValue = document.getElementById('widthValue')!, private readonly widthValue = document.getElementById('widthValue')!,
private readonly menuButton = document.getElementById('menuButton')!, private readonly menuButton = document.getElementById('menuButton')!,
private readonly drawer = document.getElementById('drawer')!, private readonly drawer = document.getElementById('drawer')!,
private readonly closeButton = document.getElementById('closeButton')!, private readonly closeButton = document.getElementById('closeButton')!,
) { ) {
this.resizeCanvas()
this.setupCanvasEvents() this.setupCanvasEvents()
window.addEventListener("resize", () => this.resizeCanvas());
this.cctx = canvas.getContext("2d")!
this.currentColor = colorPicker!.getAttribute('value')! this.currentColor = colorPicker!.getAttribute('value')!
this.currentDensity = Number(densitySlider!.getAttribute('value'))! this.currentDensity = Number(densitySlider!.getAttribute('value'))!
this.currentWidth = Number(widthSlider!.getAttribute('value'))! this.currentWidth = Number(widthSlider!.getAttribute('value'))!
this.cctx = canvas.getContext("2d")!
} }
draw() { draw() {
console.log("drawing")
const strokes: Stroke[] = this.stateService.getStrokes() const strokes: Stroke[] = this.stateService.getStrokes()
this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.cctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.cctx.globalAlpha = 1;
strokes.forEach(stroke => { strokes.forEach(stroke => {
this.cctx.fillStyle = stroke.color; this.cctx.fillStyle = stroke.color;
this.drawCurve(stroke.points, stroke.density, stroke.width) this.drawCurve(stroke.points, stroke.density, stroke.width)
@@ -122,7 +118,7 @@ export class ClientDrawService {
}); });
document.addEventListener('click', (e) => { document.addEventListener('click', (e) => {
if(!e.target){ if (!e.target) {
return return
} }
if (!this.drawer.contains(e.target as Node) && !this.menuButton.contains(e.target as Node)) { if (!this.drawer.contains(e.target as Node) && !this.menuButton.contains(e.target as Node)) {
@@ -132,12 +128,6 @@ export class ClientDrawService {
} }
private resizeCanvas() {
const size = Math.min(window.innerWidth, window.innerHeight) * 0.9;
this.canvas.width = size;
this.canvas.height = size;
}
private drawCurve(points: Point[], density: number, width: number) { private drawCurve(points: Point[], density: number, width: number) {
if (points.length < 2) return; if (points.length < 2) return;
@@ -1,6 +1,6 @@
import { RPCSocket } from "../../../../node_modules/rpclibrary/js/Index"; import { RPCSocket } from "../../../../node_modules/rpclibrary/js/Index";
import { CanvasState, Point, Stroke } from "../../model/canvas-state"; import { CanvasState, Point, Stroke } from "../../model/canvas-state";
import { ListenCallbackParam } from "../../model/rpc-callbacks"; import { StrokeStreamElement } from "../../model/rpc-callbacks";
import { ClientDrawService } from "../draw/draw.client-service"; import { ClientDrawService } from "../draw/draw.client-service";
@@ -14,11 +14,11 @@ export class ClientStateService {
const sock = await new RPCSocket(8080, 'localhost').connect(); const sock = await new RPCSocket(8080, 'localhost').connect();
this.remoteService = sock['StateService'] this.remoteService = sock['StateService']
this.canvasState = await this.getState() this.canvasState = await this.getState()
await this.remoteService.listen((listenDto: ListenCallbackParam) => { await this.remoteService.onStroke((s: StrokeStreamElement) => {
if(!this.canvasState.strokes[listenDto.strokeId]){ if(!this.canvasState.strokes[s.strokeId]){
this.canvasState.strokes[listenDto.strokeId] = listenDto.stroke this.canvasState.strokes[s.strokeId] = s.stroke
}else{ }else{
this.canvasState.strokes[listenDto.strokeId].points = [...this.canvasState.strokes[listenDto.strokeId].points, ...listenDto.stroke.points] this.canvasState.strokes[s.strokeId].points = [...this.canvasState.strokes[s.strokeId].points, ...s.stroke.points]
} }
drawService.draw() drawService.draw()
}) })
+11 -1
View File
@@ -13,6 +13,8 @@ export class ExpressService implements Initializable {
@Inject(StateService) @Inject(StateService)
private stateService: StateService; private stateService: StateService;
private connectionCount = 0;
initialize() { initialize() {
const app = express(); const app = express();
const PORT = 8080; const PORT = 8080;
@@ -28,7 +30,15 @@ export class ExpressService implements Initializable {
const rpcServer = new RPCServer([ const rpcServer = new RPCServer([
this.stateService, this.stateService,
]) ], {
closeHandler: (socket) => {
this.connectionCount -= 1
if(this.connectionCount === 0){
this.stateService.finalizePicture()
}
},
connectionHandler: (socket) => { this.connectionCount += 1 },
})
rpcServer.attach(httpServer) rpcServer.attach(httpServer)
rpcServer.listen(PORT) rpcServer.listen(PORT)
+28 -12
View File
@@ -1,43 +1,59 @@
import { Singleton, Initializable } from "depents"; import { Singleton, Initializable } from "depents";
import { RPCExporter } from "rpclibrary"; import { RPCExporter } from "rpclibrary";
import { CanvasState, Point, Stroke } from "../../../client/model/canvas-state"; import { CanvasState, Point, Stroke } from "../../../client/model/canvas-state";
import { ListenCallbackParam } from "../../../client/model/rpc-callbacks"; import { OnStrokeCallback } from "../../../client/model/rpc-callbacks";
import * as fs from "fs";
import * as path from "path";
@Singleton() @Singleton()
export class StateService implements Initializable, RPCExporter { export class StateService implements Initializable, RPCExporter {
name = 'StateService' as const name = 'StateService' as const
private canvasState: CanvasState = { strokes: [] } private canvasState: CanvasState = { strokes: [] }
private clients: Array<(param: ListenCallbackParam) => void> = [] private onStrokeCallbacks: Array<OnStrokeCallback> = []
initialize() { initialize() {
this.canvasState = { strokes: [] } this.canvasState = { strokes: [] }
this.clients = [] this.onStrokeCallbacks = []
}; };
beginStroke = async (stroke: Stroke) => { beginStroke = async (stroke: Stroke) => {
const strokeId = this.canvasState.strokes.length const strokeId = this.canvasState.strokes.length
this.canvasState.strokes.push(stroke) this.canvasState.strokes.push(stroke)
this.updateclients(strokeId, stroke) this.updateStrokeListeners(strokeId, stroke)
return strokeId return strokeId
} }
addPoint = async (strokeId: number, point: Point) => { addPoint = async (strokeId: number, point: Point) => {
this.canvasState.strokes[strokeId].points.push(point) this.canvasState.strokes[strokeId].points.push(point)
this.updateclients(strokeId, { ...this.canvasState.strokes[strokeId], points: [point] }) this.updateStrokeListeners(strokeId, { ...this.canvasState.strokes[strokeId], points: [point] })
} }
listen = async (callback: (state: any) => Promise<void>) => { onStroke = async (callback: OnStrokeCallback) => {
this.clients = [...this.clients, callback] this.onStrokeCallbacks = [...this.onStrokeCallbacks, callback]
return this.canvasState return
} }
getState = async (): Promise<CanvasState> => { getState = async (): Promise<CanvasState> => {
return this.canvasState return this.canvasState
} }
private updateclients = (strokeId: number, stroke: Stroke) => { finalizePicture = () => {
this.clients.forEach((client) => { const finalState = { ...this.canvasState }
this.canvasState = { strokes: [] }
const picturedir = path.join(__dirname, `../../../../circles-pictures/`);
if (!fs.existsSync(picturedir)) {
fs.mkdirSync(picturedir);
}
fs.writeFileSync(path.join(picturedir, `${Date.now()}.json`), JSON.stringify(finalState))
console.log(finalState)
}
private updateStrokeListeners = (strokeId: number, stroke: Stroke) => {
this.onStrokeCallbacks.forEach((client) => {
client({ strokeId, stroke }) client({ strokeId, stroke })
}) })
} }
@@ -47,8 +63,8 @@ export class StateService implements Initializable, RPCExporter {
this.addPoint, this.addPoint,
this.getState, this.getState,
{ {
name: 'listen' as const, name: 'onStroke' as const,
hook: (cb: any) => { this.listen(cb) } hook: this.onStroke,
} }
] ]