@ash-qw/dsh-theme-prts 0.1.98 → 0.1.99
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/lib/client.js +282 -3
- package/package.json +1 -1
- package/src/client/facility-vector.js +29 -0
- package/src/client/preferences.js +2 -0
- package/src/client/session-lifeline.js +255 -0
- package/src/client/sidebar-control-adapter.js +12 -0
- package/src/client/theme-controller-v2.js +2 -0
- package/src/client/theme-controller.js +2 -0
- package/src/client/theme-settings-workbench.js +8 -1
- package/src/styles/facility-vector.css +65 -0
- package/src/styles/prts.css +11 -5
- package/src/styles/refinement.css +5 -2
- package/src/styles/stabilization.css +1 -1
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createFacilityPath } from './facility-geometry.js'
|
|
2
|
+
import { configureSessionLifeline } from './session-lifeline.js'
|
|
2
3
|
|
|
3
4
|
const SVG_NS = 'http://www.w3.org/2000/svg'
|
|
4
5
|
const SILHOUETTE_TRACK_WIDTH = 512
|
|
@@ -117,6 +118,16 @@ function appendSessionPickup(document, graphic) {
|
|
|
117
118
|
clip.append(svgNode(document, 'path', { 'data-prts-facility-clip': '' }))
|
|
118
119
|
defs.append(clip)
|
|
119
120
|
|
|
121
|
+
const lifeline = svgNode(document, 'g', {
|
|
122
|
+
'data-prts-session-lifeline': '',
|
|
123
|
+
'clip-path': `url(#${clipId})`,
|
|
124
|
+
})
|
|
125
|
+
for (const strand of ['back', 'core', 'front']) {
|
|
126
|
+
lifeline.append(svgNode(document, 'path', {
|
|
127
|
+
'data-prts-session-lifeline-filament': strand,
|
|
128
|
+
}))
|
|
129
|
+
}
|
|
130
|
+
|
|
120
131
|
const pickup = svgNode(document, 'g', {
|
|
121
132
|
'data-prts-session-pickup': '',
|
|
122
133
|
'clip-path': `url(#${clipId})`,
|
|
@@ -146,9 +157,22 @@ function appendSessionPickup(document, graphic) {
|
|
|
146
157
|
})
|
|
147
158
|
|
|
148
159
|
graphic.insertBefore(defs, graphic.firstChild)
|
|
160
|
+
graphic.insertBefore(lifeline, graphic.querySelector('[data-prts-facility-layer="outline"]'))
|
|
149
161
|
graphic.insertBefore(pickup, graphic.querySelector('[data-prts-facility-layer="outline"]'))
|
|
150
162
|
}
|
|
151
163
|
|
|
164
|
+
function clamp(value, min, max) {
|
|
165
|
+
return Math.min(max, Math.max(min, value))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sessionLifelineGeometry(width, height) {
|
|
169
|
+
const start = clamp(width * .04, 8, 12)
|
|
170
|
+
const end = Math.max(start + 1, width - 8)
|
|
171
|
+
const baseline = height * .56
|
|
172
|
+
const lift = clamp(height * .35, 10, 14)
|
|
173
|
+
return { start, end, baseline, lift }
|
|
174
|
+
}
|
|
175
|
+
|
|
152
176
|
export function ensureFacilityGraphic(document, owner, { kind, topNotch = false, texture = 'none' } = {}) {
|
|
153
177
|
if (!owner) return null
|
|
154
178
|
let graphic = owner.querySelector?.(':scope > svg[data-prts-facility-svg]')
|
|
@@ -200,6 +224,8 @@ export function renderFacilityGraphic(owner, { width, height } = {}) {
|
|
|
200
224
|
}
|
|
201
225
|
|
|
202
226
|
const size = `${Number(width.toFixed(3))}x${Number(height.toFixed(3))}`
|
|
227
|
+
const lifeline = graphic.querySelector('[data-prts-session-lifeline]')
|
|
228
|
+
const lifelineGeometry = lifeline ? sessionLifelineGeometry(width, height) : undefined
|
|
203
229
|
if (owner.getAttribute('data-prts-facility-size') !== size) {
|
|
204
230
|
graphic.setAttribute('viewBox', `0 0 ${Number(width.toFixed(3))} ${Number(height.toFixed(3))}`)
|
|
205
231
|
for (const layer of graphic.querySelectorAll('[data-prts-facility-layer], [data-prts-facility-clip]')) layer.setAttribute('d', path)
|
|
@@ -223,6 +249,9 @@ export function renderFacilityGraphic(owner, { width, height } = {}) {
|
|
|
223
249
|
bar.setAttribute('y', String(Number((center - barHeight / 2).toFixed(3))))
|
|
224
250
|
}
|
|
225
251
|
}
|
|
252
|
+
if (lifeline) {
|
|
253
|
+
configureSessionLifeline(lifeline, lifelineGeometry)
|
|
254
|
+
}
|
|
226
255
|
owner.setAttribute('data-prts-facility-size', size)
|
|
227
256
|
}
|
|
228
257
|
setReady(owner, true)
|
|
@@ -18,6 +18,7 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
18
18
|
particleTraversalSpeed: PARTICLE_TRAVERSAL_SPEED_DEFAULT,
|
|
19
19
|
particlePattern: 'orthogonal',
|
|
20
20
|
railDefaultHidden: false,
|
|
21
|
+
sessionFlow: true,
|
|
21
22
|
conversationStyle: 'native',
|
|
22
23
|
})
|
|
23
24
|
|
|
@@ -147,6 +148,7 @@ export function normalizePreferences(value) {
|
|
|
147
148
|
),
|
|
148
149
|
particlePattern: 'orthogonal',
|
|
149
150
|
railDefaultHidden: booleanOr(input.railDefaultHidden, DEFAULT_PREFERENCES.railDefaultHidden),
|
|
151
|
+
sessionFlow: booleanOr(input.sessionFlow, DEFAULT_PREFERENCES.sessionFlow),
|
|
150
152
|
conversationStyle: enumOr(input.conversationStyle, CONVERSATION_STYLES, DEFAULT_PREFERENCES.conversationStyle),
|
|
151
153
|
}
|
|
152
154
|
normalized.preset = matchedVisualPreset(normalized)
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
const HOVER_SETTLE = 180
|
|
2
|
+
const STATIC_TIME = 2.4
|
|
3
|
+
const POINT_COUNT = 9
|
|
4
|
+
|
|
5
|
+
function clamp(value, min, max) {
|
|
6
|
+
return Math.min(max, Math.max(min, value))
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function number(value) {
|
|
10
|
+
return Number(value.toFixed(3))
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function readGeometry(lifeline) {
|
|
14
|
+
const start = Number(lifeline?.getAttribute?.('data-prts-lifeline-start'))
|
|
15
|
+
const end = Number(lifeline?.getAttribute?.('data-prts-lifeline-end'))
|
|
16
|
+
const baseline = Number(lifeline?.getAttribute?.('data-prts-lifeline-baseline'))
|
|
17
|
+
const lift = Number(lifeline?.getAttribute?.('data-prts-lifeline-lift'))
|
|
18
|
+
if (![start, end, baseline, lift].every(Number.isFinite) || end <= start || lift <= 0) return null
|
|
19
|
+
return { start, end, baseline, lift }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function filamentNodes(lifeline) {
|
|
23
|
+
if (!lifeline) return []
|
|
24
|
+
if (lifeline.matches?.('[data-prts-session-lifeline-filament]')) return [lifeline]
|
|
25
|
+
return Array.from(lifeline.querySelectorAll?.('[data-prts-session-lifeline-filament]') ?? [])
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function createSmoothPath(points) {
|
|
29
|
+
if (points.length < 2) return ''
|
|
30
|
+
const commands = [`M ${number(points[0].x)} ${number(points[0].y)}`]
|
|
31
|
+
for (let index = 0; index < points.length - 1; index += 1) {
|
|
32
|
+
const previous = points[Math.max(0, index - 1)]
|
|
33
|
+
const current = points[index]
|
|
34
|
+
const next = points[index + 1]
|
|
35
|
+
const following = points[Math.min(points.length - 1, index + 2)]
|
|
36
|
+
const firstControl = {
|
|
37
|
+
x: current.x + (next.x - previous.x) / 6,
|
|
38
|
+
y: current.y + (next.y - previous.y) / 6,
|
|
39
|
+
}
|
|
40
|
+
const secondControl = {
|
|
41
|
+
x: next.x - (following.x - current.x) / 6,
|
|
42
|
+
y: next.y - (following.y - current.y) / 6,
|
|
43
|
+
}
|
|
44
|
+
commands.push([
|
|
45
|
+
'C',
|
|
46
|
+
number(firstControl.x),
|
|
47
|
+
number(firstControl.y),
|
|
48
|
+
number(secondControl.x),
|
|
49
|
+
number(secondControl.y),
|
|
50
|
+
number(next.x),
|
|
51
|
+
number(next.y),
|
|
52
|
+
].join(' '))
|
|
53
|
+
}
|
|
54
|
+
return commands.join(' ')
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function sampleFlow(u, time, strand) {
|
|
58
|
+
const edgeEnvelope = Math.sin(Math.PI * u) ** .88
|
|
59
|
+
const slowTurn = Math.sin(time * .43 + u * Math.PI * 1.16 + Math.sin(time * .13) * .48)
|
|
60
|
+
const crossCurrent = Math.sin(time * -.29 + u * Math.PI * 2.42 + .7)
|
|
61
|
+
const fineCurrent = Math.sin(time * .19 + u * Math.PI * 3.76 - .9)
|
|
62
|
+
const sharedFlow = slowTurn * .52 + crossCurrent * .24 + fineCurrent * .08
|
|
63
|
+
|
|
64
|
+
const fan = Math.sin(Math.PI * u) ** 1.35
|
|
65
|
+
const separation = strand * (.15 + Math.sin(time * .31 + u * Math.PI * 1.48 + strand * .8) * .1)
|
|
66
|
+
const independentDrift = Math.sin(time * (.36 + strand * .021) + u * Math.PI * 2.18 + strand * 1.35) * .055
|
|
67
|
+
return edgeEnvelope * clamp(sharedFlow + fan * separation + independentDrift, -.9, .9)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function createSessionLifelinePath(geometry, time = STATIC_TIME, strength = 1, strand = 0) {
|
|
71
|
+
const { start, end, baseline, lift } = geometry
|
|
72
|
+
const span = end - start
|
|
73
|
+
const tension = clamp(strength, 0, 1)
|
|
74
|
+
const points = []
|
|
75
|
+
for (let index = 0; index < POINT_COUNT; index += 1) {
|
|
76
|
+
const u = index / (POINT_COUNT - 1)
|
|
77
|
+
points.push({
|
|
78
|
+
x: start + span * u,
|
|
79
|
+
y: baseline - lift * sampleFlow(u, time, strand) * tension,
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
return createSmoothPath(points)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function configureSessionLifeline(lifeline, { start, end, baseline, lift }) {
|
|
86
|
+
if (!lifeline) return false
|
|
87
|
+
const geometry = { start, end, baseline, lift }
|
|
88
|
+
lifeline.setAttribute('data-prts-lifeline-start', String(number(start)))
|
|
89
|
+
lifeline.setAttribute('data-prts-lifeline-end', String(number(end)))
|
|
90
|
+
lifeline.setAttribute('data-prts-lifeline-baseline', String(number(baseline)))
|
|
91
|
+
lifeline.setAttribute('data-prts-lifeline-lift', String(number(lift)))
|
|
92
|
+
filamentNodes(lifeline).forEach((filament, index, filaments) => {
|
|
93
|
+
const strand = filaments.length === 1 ? 0 : index - (filaments.length - 1) / 2
|
|
94
|
+
filament.setAttribute('d', createSessionLifelinePath(geometry, STATIC_TIME, 1, strand))
|
|
95
|
+
})
|
|
96
|
+
return true
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function createSessionLifelineAnimator({ document, window }) {
|
|
100
|
+
const requestFrame = window?.requestAnimationFrame?.bind(window)
|
|
101
|
+
?? (callback => window?.setTimeout?.(() => callback(Date.now()), 1000 / 60))
|
|
102
|
+
const cancelFrame = window?.cancelAnimationFrame?.bind(window)
|
|
103
|
+
?? window?.clearTimeout?.bind(window)
|
|
104
|
+
const reducedMotion = window?.matchMedia?.('(prefers-reduced-motion: reduce)')
|
|
105
|
+
const MotionObserver = window?.MutationObserver
|
|
106
|
+
|
|
107
|
+
let frame
|
|
108
|
+
let lastTime
|
|
109
|
+
let flowTime = STATIC_TIME
|
|
110
|
+
let strength = 1
|
|
111
|
+
let row
|
|
112
|
+
let lifeline
|
|
113
|
+
let filaments = []
|
|
114
|
+
let hovered = false
|
|
115
|
+
let motionObserver
|
|
116
|
+
|
|
117
|
+
function render() {
|
|
118
|
+
const geometry = readGeometry(lifeline)
|
|
119
|
+
if (!geometry || filaments.length === 0) return false
|
|
120
|
+
filaments.forEach((filament, index) => {
|
|
121
|
+
const strand = filaments.length === 1 ? 0 : index - (filaments.length - 1) / 2
|
|
122
|
+
filament.setAttribute('d', createSessionLifelinePath(geometry, flowTime, strength, strand))
|
|
123
|
+
})
|
|
124
|
+
return true
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function canAnimate() {
|
|
128
|
+
return Boolean(
|
|
129
|
+
row?.isConnected
|
|
130
|
+
&& lifeline?.isConnected
|
|
131
|
+
&& row.getAttribute('aria-selected') === 'true'
|
|
132
|
+
&& document?.visibilityState !== 'hidden'
|
|
133
|
+
&& document?.documentElement?.dataset?.prtsSessionFlow !== 'off'
|
|
134
|
+
&& document?.documentElement?.dataset?.prtsMotion !== 'reduced'
|
|
135
|
+
&& !reducedMotion?.matches
|
|
136
|
+
)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function cancel() {
|
|
140
|
+
if (frame !== undefined) cancelFrame?.(frame)
|
|
141
|
+
frame = undefined
|
|
142
|
+
lastTime = undefined
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function needsFrame() {
|
|
146
|
+
return canAnimate() && (!hovered || strength > 0)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function schedule() {
|
|
150
|
+
if (frame !== undefined || !needsFrame()) return
|
|
151
|
+
frame = requestFrame?.(tick)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function tick(time) {
|
|
155
|
+
frame = undefined
|
|
156
|
+
if (!canAnimate()) {
|
|
157
|
+
lastTime = undefined
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
const now = Number.isFinite(time) ? time : Date.now()
|
|
161
|
+
if (lastTime === undefined) lastTime = now
|
|
162
|
+
const elapsed = Math.min(100, Math.max(0, now - lastTime))
|
|
163
|
+
lastTime = now
|
|
164
|
+
flowTime += elapsed / 1000
|
|
165
|
+
const target = hovered ? 0 : 1
|
|
166
|
+
if (strength !== target) {
|
|
167
|
+
const step = elapsed / HOVER_SETTLE
|
|
168
|
+
strength = target > strength
|
|
169
|
+
? Math.min(target, strength + step)
|
|
170
|
+
: Math.max(target, strength - step)
|
|
171
|
+
}
|
|
172
|
+
render()
|
|
173
|
+
schedule()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function motionReduced() {
|
|
177
|
+
return document?.documentElement?.dataset?.prtsMotion === 'reduced' || reducedMotion?.matches
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function syncMotion() {
|
|
181
|
+
if (canAnimate()) {
|
|
182
|
+
schedule()
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
cancel()
|
|
186
|
+
if (lifeline && motionReduced()) {
|
|
187
|
+
flowTime = STATIC_TIME
|
|
188
|
+
strength = 1
|
|
189
|
+
render()
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function onPointerEnter() {
|
|
194
|
+
hovered = true
|
|
195
|
+
syncMotion()
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function onPointerLeave() {
|
|
199
|
+
hovered = false
|
|
200
|
+
syncMotion()
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function detachRow() {
|
|
204
|
+
row?.removeEventListener?.('pointerenter', onPointerEnter)
|
|
205
|
+
row?.removeEventListener?.('pointerleave', onPointerLeave)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function attachObservers() {
|
|
209
|
+
document?.addEventListener?.('visibilitychange', syncMotion)
|
|
210
|
+
reducedMotion?.addEventListener?.('change', syncMotion)
|
|
211
|
+
if (!motionObserver && typeof MotionObserver === 'function' && document?.documentElement) {
|
|
212
|
+
motionObserver = new MotionObserver(syncMotion)
|
|
213
|
+
motionObserver.observe(document.documentElement, {
|
|
214
|
+
attributes: true,
|
|
215
|
+
attributeFilter: ['data-prts-motion', 'data-prts-session-flow'],
|
|
216
|
+
})
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
start() {
|
|
222
|
+
attachObservers()
|
|
223
|
+
syncMotion()
|
|
224
|
+
},
|
|
225
|
+
setTarget(nextRow, nextLifeline) {
|
|
226
|
+
if (row === nextRow && lifeline === nextLifeline) {
|
|
227
|
+
syncMotion()
|
|
228
|
+
return
|
|
229
|
+
}
|
|
230
|
+
cancel()
|
|
231
|
+
detachRow()
|
|
232
|
+
row = nextRow
|
|
233
|
+
lifeline = nextLifeline
|
|
234
|
+
filaments = filamentNodes(lifeline)
|
|
235
|
+
hovered = Boolean(row?.matches?.(':hover'))
|
|
236
|
+
flowTime = STATIC_TIME
|
|
237
|
+
strength = hovered ? 0 : 1
|
|
238
|
+
row?.addEventListener?.('pointerenter', onPointerEnter)
|
|
239
|
+
row?.addEventListener?.('pointerleave', onPointerLeave)
|
|
240
|
+
render()
|
|
241
|
+
syncMotion()
|
|
242
|
+
},
|
|
243
|
+
dispose() {
|
|
244
|
+
cancel()
|
|
245
|
+
detachRow()
|
|
246
|
+
row = undefined
|
|
247
|
+
lifeline = undefined
|
|
248
|
+
filaments = []
|
|
249
|
+
motionObserver?.disconnect()
|
|
250
|
+
motionObserver = undefined
|
|
251
|
+
document?.removeEventListener?.('visibilitychange', syncMotion)
|
|
252
|
+
reducedMotion?.removeEventListener?.('change', syncMotion)
|
|
253
|
+
},
|
|
254
|
+
}
|
|
255
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ensureFacilityGraphic, ensureFacilityTextureDefs, renderFacilityGraphic } from './facility-vector.js'
|
|
2
|
+
import { createSessionLifelineAnimator } from './session-lifeline.js'
|
|
2
3
|
|
|
3
4
|
const OWNED_ATTRIBUTES = [
|
|
4
5
|
'data-prts-row-title',
|
|
@@ -128,6 +129,7 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
128
129
|
let vectorOwners = new Set()
|
|
129
130
|
const dirtyVectors = new Map()
|
|
130
131
|
const vectorSizes = new WeakMap()
|
|
132
|
+
const lifelineAnimator = createSessionLifelineAnimator({ document, window })
|
|
131
133
|
|
|
132
134
|
function scheduleVectors() {
|
|
133
135
|
if (resizeFrame !== undefined) return
|
|
@@ -191,6 +193,8 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
191
193
|
const root = document?.querySelector?.('[data-prts-region="sessions"] [data-slot="sidebar.workspaces"]')
|
|
192
194
|
const next = new Set()
|
|
193
195
|
const nextVectorOwners = new Set()
|
|
196
|
+
let activeLifelineRow
|
|
197
|
+
let activeLifeline
|
|
194
198
|
if (root) {
|
|
195
199
|
for (const projection of root.querySelectorAll('[data-prts-owned-projection]')) projection.remove()
|
|
196
200
|
for (const row of root.querySelectorAll('div[role="treeitem"][aria-expanded]')) {
|
|
@@ -222,6 +226,10 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
222
226
|
const spine = ensureFacilitySpine(document, row, 'session', next)
|
|
223
227
|
registerVector(face, { kind: 'face', topNotch: true, texture: 'pickup' }, next, nextVectorOwners)
|
|
224
228
|
registerVector(spine, { kind: 'spine' }, next, nextVectorOwners)
|
|
229
|
+
if (!activeLifelineRow && row.getAttribute('aria-selected') === 'true') {
|
|
230
|
+
activeLifelineRow = row
|
|
231
|
+
activeLifeline = face.querySelector('[data-prts-session-lifeline]')
|
|
232
|
+
}
|
|
225
233
|
markSidebarNode(row, 'data-prts-session-row', next)
|
|
226
234
|
row.dataset.prtsSessionIndex = sessionIndex
|
|
227
235
|
const title = resolveRowTitle(row, 'session', controls.actions)
|
|
@@ -236,6 +244,7 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
236
244
|
registerVector(controls.menu, { kind: 'button' }, next, nextVectorOwners)
|
|
237
245
|
}
|
|
238
246
|
}
|
|
247
|
+
lifelineAnimator.setTarget(activeLifelineRow, activeLifeline)
|
|
239
248
|
for (const node of owned) {
|
|
240
249
|
if (next.has(node)) continue
|
|
241
250
|
if (node.hasAttribute?.('data-prts-owned-projection')
|
|
@@ -252,6 +261,7 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
252
261
|
}
|
|
253
262
|
|
|
254
263
|
function clear() {
|
|
264
|
+
lifelineAnimator.setTarget(undefined, undefined)
|
|
255
265
|
for (const node of document?.querySelectorAll?.('[data-prts-owned-projection], [data-prts-owned-facility], [data-prts-owned-facility-spine], [data-prts-owned-facility-vector], [data-prts-owned-facility-defs]') ?? []) node.remove()
|
|
256
266
|
for (const node of owned) {
|
|
257
267
|
for (const attribute of OWNED_ATTRIBUTES) node.removeAttribute?.(attribute)
|
|
@@ -267,6 +277,7 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
267
277
|
return {
|
|
268
278
|
start() {
|
|
269
279
|
if (!document) return
|
|
280
|
+
lifelineAnimator.start()
|
|
270
281
|
ensureFacilityTextureDefs(document)
|
|
271
282
|
const ResizeObserver = window?.ResizeObserver
|
|
272
283
|
if (!resizeObserver && typeof ResizeObserver === 'function') {
|
|
@@ -311,6 +322,7 @@ export function createSidebarControlAdapter({ document, window }) {
|
|
|
311
322
|
resizeFrame = undefined
|
|
312
323
|
if (resizeTimer !== undefined) window?.clearTimeout?.(resizeTimer)
|
|
313
324
|
resizeTimer = undefined
|
|
325
|
+
lifelineAnimator.dispose()
|
|
314
326
|
clear()
|
|
315
327
|
},
|
|
316
328
|
}
|
|
@@ -7,6 +7,7 @@ const ROOT_ATTRIBUTES = [
|
|
|
7
7
|
'data-prts-glass',
|
|
8
8
|
'data-prts-motion',
|
|
9
9
|
'data-prts-particle-pattern',
|
|
10
|
+
'data-prts-session-flow',
|
|
10
11
|
'data-prts-conversation-style',
|
|
11
12
|
]
|
|
12
13
|
|
|
@@ -716,6 +717,7 @@ export function createThemeController({ document, window, cssText, service, onTr
|
|
|
716
717
|
root.dataset.prtsGlass = preferences.glass
|
|
717
718
|
root.dataset.prtsMotion = preferences.motion
|
|
718
719
|
root.dataset.prtsParticlePattern = 'orthogonal'
|
|
720
|
+
root.dataset.prtsSessionFlow = preferences.sessionFlow ? 'on' : 'off'
|
|
719
721
|
root.dataset.prtsConversationStyle = preferences.conversationStyle
|
|
720
722
|
refresh()
|
|
721
723
|
},
|
|
@@ -7,6 +7,7 @@ const ROOT_ATTRIBUTES = [
|
|
|
7
7
|
'data-prts-glass',
|
|
8
8
|
'data-prts-motion',
|
|
9
9
|
'data-prts-particle-pattern',
|
|
10
|
+
'data-prts-session-flow',
|
|
10
11
|
'data-prts-conversation-style',
|
|
11
12
|
]
|
|
12
13
|
|
|
@@ -109,6 +110,7 @@ export function createThemeController({ document, window, cssText, service }) {
|
|
|
109
110
|
root.dataset.prtsGlass = preferences.glass
|
|
110
111
|
root.dataset.prtsMotion = preferences.motion
|
|
111
112
|
root.dataset.prtsParticlePattern = 'orthogonal'
|
|
113
|
+
root.dataset.prtsSessionFlow = preferences.sessionFlow ? 'on' : 'off'
|
|
112
114
|
root.dataset.prtsConversationStyle = preferences.conversationStyle
|
|
113
115
|
refresh()
|
|
114
116
|
},
|
|
@@ -19,6 +19,7 @@ const COMMON_CONTROLS = [
|
|
|
19
19
|
['texture', '环境底纹', [['off', '关闭'], ['restrained', '克制'], ['full', '完整']]],
|
|
20
20
|
['glass', '玻璃材质', [['off', '关闭'], ['soft', '柔和'], ['standard', '标准'], ['clear', '清晰']]],
|
|
21
21
|
['motion', '动态效果', [['system', '跟随系统'], ['reduced', '减少']]],
|
|
22
|
+
['sessionFlow', '当前会话流线', [['false', '关闭'], ['true', '开启']]],
|
|
22
23
|
['conversationStyle', '会话主题', [['native', '原有'], ['deck-chat', '通讯链路']]],
|
|
23
24
|
]
|
|
24
25
|
|
|
@@ -37,6 +38,8 @@ function settingButtons(key, label, options) {
|
|
|
37
38
|
? '<small data-prts-setting-note>在任意窗口宽度下默认收起 P.R.T.S. 导航,可从左侧按钮临时展开</small>'
|
|
38
39
|
: key === 'conversationStyle'
|
|
39
40
|
? '<small data-prts-setting-note>立即切换会话背景、头像与消息气泡</small>'
|
|
41
|
+
: key === 'sessionFlow'
|
|
42
|
+
? '<small data-prts-setting-note>仅控制当前会话卡片的三色流线</small>'
|
|
40
43
|
: ''
|
|
41
44
|
const sample = value => key === 'texture' || key === 'glass'
|
|
42
45
|
? `<i data-prts-option-sample="${key}" data-prts-option-sample-value="${value}" aria-hidden="true"></i>`
|
|
@@ -281,7 +284,11 @@ export function createThemeSettingsOverlay({
|
|
|
281
284
|
const particleDetail = resolveParticleDetail(currentPreferences)
|
|
282
285
|
for (const button of panel.querySelectorAll('button[data-prts-setting-key]')) {
|
|
283
286
|
const key = button.dataset.prtsSettingKey
|
|
284
|
-
const actual = key === 'particleDetail'
|
|
287
|
+
const actual = key === 'particleDetail'
|
|
288
|
+
? particleDetail
|
|
289
|
+
: key === 'sessionFlow'
|
|
290
|
+
? currentPreferences.sessionFlow !== false
|
|
291
|
+
: currentPreferences[key]
|
|
285
292
|
const selected = String(actual) === button.dataset.prtsSettingValue
|
|
286
293
|
button.classList.toggle('is-selected', selected)
|
|
287
294
|
button.setAttribute('aria-pressed', String(selected))
|
|
@@ -141,6 +141,71 @@ html[data-dsh-prts] [data-prts-session-pickup] {
|
|
|
141
141
|
pointer-events: none;
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
html[data-dsh-prts] [data-prts-session-lifeline] {
|
|
145
|
+
--prts-lifeline-pink: #f7ddeb;
|
|
146
|
+
--prts-lifeline-green: #65ddb0;
|
|
147
|
+
--prts-lifeline-blue: #6eb7f3;
|
|
148
|
+
opacity: 0;
|
|
149
|
+
transition: opacity 160ms ease;
|
|
150
|
+
pointer-events: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
html[data-dsh-prts][data-prts-scheme="light"] [data-prts-session-lifeline] {
|
|
154
|
+
--prts-lifeline-pink: #c36f99;
|
|
155
|
+
--prts-lifeline-green: #218d6b;
|
|
156
|
+
--prts-lifeline-blue: #357ebc;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
html[data-dsh-prts] [data-prts-session-lifeline-filament] {
|
|
160
|
+
fill: none;
|
|
161
|
+
stroke: currentColor;
|
|
162
|
+
stroke-width: .8;
|
|
163
|
+
stroke-linecap: round;
|
|
164
|
+
stroke-linejoin: round;
|
|
165
|
+
vector-effect: non-scaling-stroke;
|
|
166
|
+
shape-rendering: geometricPrecision;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
html[data-dsh-prts] [data-prts-session-lifeline-filament="back"] {
|
|
170
|
+
stroke: var(--prts-lifeline-pink);
|
|
171
|
+
opacity: .74;
|
|
172
|
+
stroke-width: .85;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
html[data-dsh-prts] [data-prts-session-lifeline-filament="core"] {
|
|
176
|
+
stroke: var(--prts-lifeline-green);
|
|
177
|
+
opacity: .68;
|
|
178
|
+
stroke-width: .9;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
html[data-dsh-prts] [data-prts-session-lifeline-filament="front"] {
|
|
182
|
+
stroke: var(--prts-lifeline-blue);
|
|
183
|
+
opacity: .74;
|
|
184
|
+
stroke-width: .85;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
html[data-dsh-prts] [data-prts-session-row][aria-selected="true"] [data-prts-session-lifeline] {
|
|
188
|
+
opacity: .62;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
html[data-dsh-prts] [data-prts-session-row][aria-selected="true"]:hover [data-prts-session-lifeline] {
|
|
192
|
+
opacity: 0;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
html[data-dsh-prts][data-prts-session-flow="off"] [data-prts-session-row] [data-prts-session-lifeline] {
|
|
196
|
+
opacity: 0;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@media (prefers-reduced-motion: reduce) {
|
|
200
|
+
html[data-dsh-prts] [data-prts-session-lifeline] {
|
|
201
|
+
transition-duration: 0s;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
html[data-dsh-prts][data-prts-motion="reduced"] [data-prts-session-lifeline] {
|
|
206
|
+
transition-duration: 0s;
|
|
207
|
+
}
|
|
208
|
+
|
|
144
209
|
html[data-dsh-prts] [data-prts-session-row]:hover [data-prts-facility-texture="pickup"] [data-prts-session-pickup] {
|
|
145
210
|
opacity: .58;
|
|
146
211
|
transform: translate3d(0, 0, 0);
|
package/src/styles/prts.css
CHANGED
|
@@ -809,7 +809,6 @@ html[data-dsh-prts] [data-prts-region="operation"] {
|
|
|
809
809
|
html[data-dsh-prts] [data-prts-region="sessions"] {
|
|
810
810
|
box-sizing: border-box;
|
|
811
811
|
position: relative;
|
|
812
|
-
z-index: 4;
|
|
813
812
|
overflow: hidden;
|
|
814
813
|
border-right: 1px solid var(--prts-line);
|
|
815
814
|
background: var(--prts-panel);
|
|
@@ -818,7 +817,6 @@ html[data-dsh-prts] [data-prts-region="sessions"] {
|
|
|
818
817
|
|
|
819
818
|
html[data-dsh-prts] [data-prts-region="operation"] {
|
|
820
819
|
position: relative;
|
|
821
|
-
z-index: 1;
|
|
822
820
|
overflow: hidden;
|
|
823
821
|
background: var(--prts-canvas);
|
|
824
822
|
color: var(--prts-ink);
|
|
@@ -987,6 +985,14 @@ html[data-dsh-prts] [data-prts-session-row] [data-prts-row-title]::before {
|
|
|
987
985
|
letter-spacing: .08em;
|
|
988
986
|
}
|
|
989
987
|
|
|
988
|
+
html[data-dsh-prts] [data-prts-session-row][aria-selected="true"] [data-prts-row-title] {
|
|
989
|
+
font-weight: 700;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
html[data-dsh-prts] [data-prts-session-row][aria-selected="true"] [data-prts-row-title]::before {
|
|
993
|
+
color: var(--prts-yellow);
|
|
994
|
+
}
|
|
995
|
+
|
|
990
996
|
html[data-dsh-prts] [data-prts-workspace-actions],
|
|
991
997
|
html[data-dsh-prts] [data-prts-session-actions] {
|
|
992
998
|
position: absolute;
|
|
@@ -1516,7 +1522,7 @@ html[data-dsh-prts] [data-prts-composer-fallback] :is(textarea, [data-composer-i
|
|
|
1516
1522
|
caret-color: var(--prts-yellow);
|
|
1517
1523
|
}
|
|
1518
1524
|
|
|
1519
|
-
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *),
|
|
1525
|
+
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):not([data-prts-glass-menu] *):not([data-prts-floating-glass] *):not([role="menu"] *):not([role="listbox"] *),
|
|
1520
1526
|
html[data-dsh-prts] [data-prts-conversation-control] {
|
|
1521
1527
|
border: 1px solid var(--prts-line) !important;
|
|
1522
1528
|
border-radius: var(--prts-radius-control) !important;
|
|
@@ -1526,7 +1532,7 @@ html[data-dsh-prts] [data-prts-conversation-control] {
|
|
|
1526
1532
|
}
|
|
1527
1533
|
|
|
1528
1534
|
|
|
1529
|
-
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):is(:hover, :focus-visible),
|
|
1535
|
+
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):not([data-prts-glass-menu] *):not([data-prts-floating-glass] *):not([role="menu"] *):not([role="listbox"] *):is(:hover, :focus-visible),
|
|
1530
1536
|
html[data-dsh-prts] [data-prts-conversation-control]:is(:hover, :focus-visible) {
|
|
1531
1537
|
border-color: var(--prts-yellow) !important;
|
|
1532
1538
|
}
|
|
@@ -1537,7 +1543,7 @@ html[data-dsh-prts] [data-composer-card] button[type="submit"] {
|
|
|
1537
1543
|
color: var(--prts-yellow-ink) !important;
|
|
1538
1544
|
}
|
|
1539
1545
|
|
|
1540
|
-
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):active,
|
|
1546
|
+
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):not([data-prts-glass-menu] *):not([data-prts-floating-glass] *):not([role="menu"] *):not([role="listbox"] *):active,
|
|
1541
1547
|
html[data-dsh-prts] [data-prts-conversation-control]:active {
|
|
1542
1548
|
transform: translateY(1px) scale(.98);
|
|
1543
1549
|
}
|
|
@@ -11,7 +11,6 @@ html[data-dsh-prts] [data-prts-region="frame"] {
|
|
|
11
11
|
|
|
12
12
|
html[data-dsh-prts] [data-prts-region="frame"] > [data-shell-overlay] {
|
|
13
13
|
position: absolute !important;
|
|
14
|
-
z-index: 120;
|
|
15
14
|
inset: 0;
|
|
16
15
|
min-width: 0;
|
|
17
16
|
min-height: 0;
|
|
@@ -155,6 +154,10 @@ html[data-dsh-prts] [data-prts-session-row]:hover [data-prts-session-time] {
|
|
|
155
154
|
opacity: 0;
|
|
156
155
|
}
|
|
157
156
|
|
|
157
|
+
html[data-dsh-prts] [data-prts-session-row][aria-selected="true"] [data-prts-session-time] {
|
|
158
|
+
opacity: 0;
|
|
159
|
+
}
|
|
160
|
+
|
|
158
161
|
/* Restore the notched P.R.T.S. composer with one complete layered contour. */
|
|
159
162
|
html[data-dsh-prts] [data-composer-card],
|
|
160
163
|
html[data-dsh-prts] [data-prts-composer-fallback] {
|
|
@@ -417,7 +420,7 @@ html[data-dsh-prts] [data-prts-hero-active] [data-prts-composer-fallback] textar
|
|
|
417
420
|
resize: vertical;
|
|
418
421
|
}
|
|
419
422
|
|
|
420
|
-
html[data-dsh-prts] [data-prts-hero-active] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *),
|
|
423
|
+
html[data-dsh-prts] [data-prts-hero-active] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):not([data-prts-glass-menu] *):not([data-prts-floating-glass] *):not([role="menu"] *):not([role="listbox"] *),
|
|
421
424
|
html[data-dsh-prts] [data-prts-hero-active] [data-prts-glass-control] {
|
|
422
425
|
min-height: 36px;
|
|
423
426
|
}
|
|
@@ -137,7 +137,7 @@ html[data-dsh-prts] [data-prts-nav-bottom] b {
|
|
|
137
137
|
/* Complete structural outlines; decorative accents are separate edges. */
|
|
138
138
|
html[data-dsh-prts] [data-composer-card],
|
|
139
139
|
html[data-dsh-prts] [data-prts-composer-fallback],
|
|
140
|
-
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *),
|
|
140
|
+
html[data-dsh-prts] [data-composer-card] button:not([data-slot="conversation.input.attachments"] *):not([data-prts-glass-menu] *):not([data-prts-floating-glass] *):not([role="menu"] *):not([role="listbox"] *),
|
|
141
141
|
html[data-dsh-prts] [data-prts-conversation-control],
|
|
142
142
|
html[data-dsh-prts] [data-produced-files-row] > button[type="button"],
|
|
143
143
|
html[data-dsh-prts] [data-prts-to-bottom] {
|