Plan Checker, Wochenbericht-Fokus-Modus und Wandtafel-Dashboard-Feinschliff
Plan Checker (neues Studenten-Werkzeug, Seitenleiste + Admin → Plan-Checker- Merkmale): lädt einen Bauplan als PDF hoch und lässt ChatGPT oder Claude ihn gegen eine gepflegte Merkmalsliste prüfen. PDF-Rendering (pdf.js + @napi-rs/canvas) berücksichtigt jetzt Ebenen (Optional Content Groups) und WASM-Decoder korrekt, sendet eine Übersicht plus mehrere hochaufgelöste Kachel-Ausschnitte statt eines einzelnen, zu groben Bilds, und beide Anbieter bekommen dasselbe Bildmaterial. Jedes Prüfergebnis kann auf eines der gesendeten Bilder als Beleg verweisen (bildIndex) — als Screenshot im Ergebnis anklickbar, öffnet vergrößert in einem Modal statt eines von Chrome blockierten target=_blank-Links auf eine data:-URL. KI-Modellname ist jetzt pro Konto überschreibbar (Einstellungen → Mein Konto) für Drittanbieter mit abweichendem Modell-Katalog. Wochenbericht: dichte Kartenansicht bekommt einen Fokus-Modus — nur der Kommentar liegt offen, Leistungsphasen und Beteiligte stecken in aufklappbaren Abschnitten statt dauerhaft nebeneinander zu stehen. Projektbereich: Terminplan-Auslastung rechnet jetzt exakt wie der Auslastungs-Tab/die Wandtafel (statt einer unabhängigen, tageweisen Schätzung); die LPH-Fortschrittsliste im Dashboard zeigt nur noch die beauftragten Leistungsphasen. Wandtafel: Dashboard-Kopf mit Kennzahlen-Reihe und Panel-Reihe neu strukturiert, in den Blautönen des Firmenlogos; dieselbe Palette wurde anschließend auf den gesamten App-weiten Dark Mode übernommen. Dateibereinigung: nicht mehr benötigte Test-Uploads/Backups entfernt. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db63a29365
commit
6ba93b2a92
308 files changed
+64441
-743
No files matched your search
+293
@@ -0,0 +1,293 @@
|
||||
'use strict'
|
||||
|
||||
const { PoolStats } = require('../util/stats.js')
|
||||
const DispatcherBase = require('./dispatcher-base')
|
||||
const FixedQueue = require('./fixed-queue')
|
||||
const { kConnected, kSize, kRunning, kPending, kQueued, kBusy, kFree, kUrl, kClose, kDestroy, kDispatch } = require('../core/symbols')
|
||||
|
||||
const kClients = Symbol('clients')
|
||||
const kNeedDrain = Symbol('needDrain')
|
||||
const kQueue = Symbol('queue')
|
||||
const kClosedResolve = Symbol('closed resolve')
|
||||
const kOnDrain = Symbol('onDrain')
|
||||
const kOnConnect = Symbol('onConnect')
|
||||
const kOnDisconnect = Symbol('onDisconnect')
|
||||
const kOnConnectionError = Symbol('onConnectionError')
|
||||
const kOnClientBusy = Symbol('on client busy')
|
||||
const kOnClientDrain = Symbol('on client drain')
|
||||
const kDrainQueue = Symbol('drain queue')
|
||||
const kGetDispatcher = Symbol('get dispatcher')
|
||||
const kHasDispatcher = Symbol('has dispatcher')
|
||||
const kAddClient = Symbol('add client')
|
||||
const kRemoveClient = Symbol('remove client')
|
||||
|
||||
class PoolBase extends DispatcherBase {
|
||||
[kQueue] = new FixedQueue();
|
||||
|
||||
[kQueued] = 0;
|
||||
|
||||
[kClients] = [];
|
||||
|
||||
[kNeedDrain] = false;
|
||||
|
||||
[kOnDrain] (client, origin, targets) {
|
||||
if (client.closed || client.destroyed) {
|
||||
return
|
||||
}
|
||||
|
||||
const queue = this[kQueue]
|
||||
let needDrain = false
|
||||
|
||||
this[kOnClientDrain](client)
|
||||
|
||||
while (!needDrain) {
|
||||
const item = queue.shift()
|
||||
if (!item) {
|
||||
break
|
||||
}
|
||||
this[kQueued]--
|
||||
needDrain = !client.dispatch(item.opts, item.handler)
|
||||
}
|
||||
|
||||
client[kNeedDrain] = needDrain
|
||||
if (needDrain) {
|
||||
this[kOnClientBusy](client)
|
||||
}
|
||||
|
||||
if (!needDrain && this[kNeedDrain]) {
|
||||
this[kNeedDrain] = false
|
||||
this.emit('drain', origin, [this, ...targets])
|
||||
}
|
||||
|
||||
if (this[kClosedResolve] && queue.isEmpty()) {
|
||||
const closeAll = []
|
||||
for (let i = 0; i < this[kClients].length; i++) {
|
||||
const client = this[kClients][i]
|
||||
if (!client.destroyed) {
|
||||
closeAll.push(client.close())
|
||||
}
|
||||
}
|
||||
return Promise.all(closeAll)
|
||||
.then(this[kClosedResolve])
|
||||
}
|
||||
}
|
||||
|
||||
[kOnClientBusy] () {}
|
||||
|
||||
[kOnClientDrain] () {}
|
||||
|
||||
[kDrainQueue] (origin, targets) {
|
||||
const queue = this[kQueue]
|
||||
let hasDispatcher = true
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
const dispatcher = this[kGetDispatcher]()
|
||||
if (!dispatcher) {
|
||||
hasDispatcher = false
|
||||
break
|
||||
}
|
||||
|
||||
const item = queue.shift()
|
||||
this[kQueued]--
|
||||
|
||||
if (!dispatcher.dispatch(item.opts, item.handler)) {
|
||||
dispatcher[kNeedDrain] = true
|
||||
this[kOnClientBusy](dispatcher)
|
||||
hasDispatcher = this[kHasDispatcher]()
|
||||
if (!hasDispatcher) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hasDispatcher && this[kNeedDrain]) {
|
||||
this[kNeedDrain] = false
|
||||
this.emit('drain', origin, [this, ...targets])
|
||||
}
|
||||
|
||||
if (this[kClosedResolve] && queue.isEmpty()) {
|
||||
const closeAll = []
|
||||
for (let i = 0; i < this[kClients].length; i++) {
|
||||
const client = this[kClients][i]
|
||||
if (!client.destroyed) {
|
||||
closeAll.push(client.close())
|
||||
}
|
||||
}
|
||||
return Promise.all(closeAll)
|
||||
.then(this[kClosedResolve])
|
||||
}
|
||||
}
|
||||
|
||||
[kOnConnect] = (origin, targets) => {
|
||||
this.emit('connect', origin, [this, ...targets])
|
||||
};
|
||||
|
||||
[kOnDisconnect] = (origin, targets, err) => {
|
||||
this.emit('disconnect', origin, [this, ...targets], err)
|
||||
};
|
||||
|
||||
[kOnConnectionError] = (origin, targets, err) => {
|
||||
this.emit('connectionError', origin, [this, ...targets], err)
|
||||
}
|
||||
|
||||
get [kBusy] () {
|
||||
return this[kNeedDrain]
|
||||
}
|
||||
|
||||
get [kConnected] () {
|
||||
let ret = 0
|
||||
for (const { [kConnected]: connected } of this[kClients]) {
|
||||
ret += connected
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get [kFree] () {
|
||||
let ret = 0
|
||||
for (const { [kConnected]: connected, [kNeedDrain]: needDrain } of this[kClients]) {
|
||||
ret += connected && !needDrain
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get [kPending] () {
|
||||
let ret = this[kQueued]
|
||||
for (const { [kPending]: pending } of this[kClients]) {
|
||||
ret += pending
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get [kRunning] () {
|
||||
let ret = 0
|
||||
for (const { [kRunning]: running } of this[kClients]) {
|
||||
ret += running
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get [kSize] () {
|
||||
let ret = this[kQueued]
|
||||
for (const { [kSize]: size } of this[kClients]) {
|
||||
ret += size
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
get stats () {
|
||||
return new PoolStats(this)
|
||||
}
|
||||
|
||||
[kClose] () {
|
||||
if (this[kQueue].isEmpty()) {
|
||||
const closeAll = []
|
||||
for (let i = 0; i < this[kClients].length; i++) {
|
||||
const client = this[kClients][i]
|
||||
if (!client.destroyed) {
|
||||
closeAll.push(client.close())
|
||||
}
|
||||
}
|
||||
return Promise.all(closeAll)
|
||||
} else {
|
||||
return new Promise((resolve) => {
|
||||
this[kClosedResolve] = resolve
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
[kDestroy] (err) {
|
||||
while (true) {
|
||||
const item = this[kQueue].shift()
|
||||
if (!item) {
|
||||
break
|
||||
}
|
||||
item.handler.onResponseError(null, err)
|
||||
}
|
||||
|
||||
const destroyAll = new Array(this[kClients].length)
|
||||
for (let i = 0; i < this[kClients].length; i++) {
|
||||
destroyAll[i] = this[kClients][i].destroy(err)
|
||||
}
|
||||
return Promise.all(destroyAll)
|
||||
}
|
||||
|
||||
[kDispatch] (opts, handler) {
|
||||
const dispatcher = this[kGetDispatcher]()
|
||||
|
||||
if (!dispatcher) {
|
||||
this[kNeedDrain] = true
|
||||
this[kQueue].push({ opts, handler })
|
||||
this[kQueued]++
|
||||
} else if (!dispatcher.dispatch(opts, handler)) {
|
||||
dispatcher[kNeedDrain] = true
|
||||
this[kOnClientBusy](dispatcher)
|
||||
this[kNeedDrain] = !this[kHasDispatcher]()
|
||||
}
|
||||
|
||||
return !this[kNeedDrain]
|
||||
}
|
||||
|
||||
[kHasDispatcher] () {
|
||||
for (let i = 0; i < this[kClients].length; i++) {
|
||||
const dispatcher = this[kClients][i]
|
||||
|
||||
if (
|
||||
!dispatcher[kNeedDrain] &&
|
||||
dispatcher.closed !== true &&
|
||||
dispatcher.destroyed !== true
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
[kAddClient] (client) {
|
||||
client
|
||||
.on('drain', this[kOnDrain].bind(this, client))
|
||||
.on('connect', this[kOnConnect])
|
||||
.on('disconnect', this[kOnDisconnect])
|
||||
.on('connectionError', this[kOnConnectionError])
|
||||
|
||||
this[kClients].push(client)
|
||||
|
||||
if (this[kNeedDrain]) {
|
||||
queueMicrotask(() => {
|
||||
if (this[kNeedDrain] && !client[kNeedDrain]) {
|
||||
this[kOnDrain](client, client[kUrl], [client, this])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
[kRemoveClient] (client) {
|
||||
const idx = this[kClients].indexOf(client)
|
||||
if (idx !== -1) {
|
||||
this[kClients].splice(idx, 1)
|
||||
}
|
||||
|
||||
client.close(() => {})
|
||||
|
||||
this[kNeedDrain] = !this[kClients].some(dispatcher => (
|
||||
!dispatcher[kNeedDrain] &&
|
||||
dispatcher.closed !== true &&
|
||||
dispatcher.destroyed !== true
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
PoolBase,
|
||||
kClients,
|
||||
kNeedDrain,
|
||||
kAddClient,
|
||||
kRemoveClient,
|
||||
kDrainQueue,
|
||||
kOnClientBusy,
|
||||
kOnClientDrain,
|
||||
kGetDispatcher,
|
||||
kHasDispatcher
|
||||
}
|
||||
Reference in new issue
Block a user