dsh-remote-plugin 0.6.14 → 0.6.16
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/apk/dsh-remote.apk +0 -0
- package/gateway.cjs +314 -30
- package/package.json +1 -1
- package/public/admin.html +46 -1
- package/public/admin.js +79 -9
- package/public/announcements.json +33 -0
- package/public/app.js +1103 -62
- package/public/desktop/desktop.css +22 -7
- package/public/desktop/desktop.html +24 -4
- package/public/desktop/desktop.js +275 -26
- package/public/index.html +120 -15
- package/public/morphicons-init.js +41 -0
- package/public/motion.js +469 -0
- package/public/plugin.html +4 -1
- package/public/plugin.js +1 -0
- package/public/styles.css +96 -6
- package/public/update.json +12 -12
- package/public/vendor/gsap/NOTICE.md +10 -0
- package/public/vendor/gsap/gsap.min.js +11 -0
- package/public/vendor/morphicons/LICENSE +21 -0
- package/public/vendor/morphicons/README.md +10 -0
- package/public/vendor/morphicons/controller-CXZuwJ_M.js +152 -0
- package/public/vendor/morphicons/dom.js +206 -0
- package/public/vendor/morphicons/element.js +261 -0
- package/public/vendor/morphicons/normalize-CYnN3Npw.js +540 -0
- package/public/vendor/morphicons/spring-CFHloqPP.js +623 -0
- package/public/version.json +1 -1
package/public/motion.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/* dsh-Remote motion layer: GSAP core + timeline, with reduced-motion and low-cost DOM updates. */
|
|
2
|
+
(function () {
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
5
|
+
const gsap = window.gsap
|
|
6
|
+
if (!gsap) return
|
|
7
|
+
|
|
8
|
+
const reduceQuery = window.matchMedia?.('(prefers-reduced-motion: reduce)')
|
|
9
|
+
let reduced = !!reduceQuery?.matches
|
|
10
|
+
reduceQuery?.addEventListener?.('change', event => { reduced = !!event.matches })
|
|
11
|
+
const activePulses = new WeakSet()
|
|
12
|
+
|
|
13
|
+
function clear(targets) {
|
|
14
|
+
gsap.set(targets, { clearProps: 'opacity,visibility,transform,willChange' })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function motionKey(node) {
|
|
18
|
+
return node?.dataset?.motionKey || node?.dataset?.id || node?.dataset?.sessionSwipe || node?.dataset?.wbSession || node?.textContent?.slice(0, 80) || ''
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function motionSignature(items) {
|
|
22
|
+
return items.map(motionKey).join('|')
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function view(view) {
|
|
26
|
+
if (!view) return
|
|
27
|
+
const children = [...view.children].filter(child => !child.classList.contains('hidden')).slice(0, 6)
|
|
28
|
+
gsap.killTweensOf([view, ...children])
|
|
29
|
+
if (reduced) {
|
|
30
|
+
clear([view, ...children])
|
|
31
|
+
return
|
|
32
|
+
}
|
|
33
|
+
const tl = gsap.timeline({ defaults: { ease: 'power2.out' } })
|
|
34
|
+
tl.fromTo(view, { autoAlpha: 0, y: 8 }, { autoAlpha: 1, y: 0, duration: 0.22, clearProps: 'transform' })
|
|
35
|
+
.fromTo(children, { autoAlpha: 0, y: 6 }, { autoAlpha: 1, y: 0, duration: 0.16, stagger: 0.025, clearProps: 'transform' }, '<0.04')
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function list(container, selector) {
|
|
39
|
+
if (!container) return
|
|
40
|
+
const items = [...container.querySelectorAll(selector)]
|
|
41
|
+
if (!items.length) {
|
|
42
|
+
delete container.dataset.motionListSignature
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
const signature = motionSignature(items)
|
|
46
|
+
if (container.dataset.motionListSignature === signature) return
|
|
47
|
+
container.dataset.motionListSignature = signature
|
|
48
|
+
gsap.killTweensOf(items)
|
|
49
|
+
if (reduced) {
|
|
50
|
+
clear(items)
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
gsap.fromTo(items, { autoAlpha: 0, y: 8 }, {
|
|
54
|
+
autoAlpha: 1,
|
|
55
|
+
y: 0,
|
|
56
|
+
duration: 0.2,
|
|
57
|
+
ease: 'power2.out',
|
|
58
|
+
stagger: { each: 0.025, from: 'start' },
|
|
59
|
+
clearProps: 'transform'
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// FLIP-style reflow without the optional Flip plugin: read all positions,
|
|
64
|
+
// let the caller render once, then animate only transform/opacity.
|
|
65
|
+
function relayout(container, selector, render) {
|
|
66
|
+
if (!container || typeof render !== 'function') return false
|
|
67
|
+
const existing = [...container.querySelectorAll(selector)]
|
|
68
|
+
const before = new Map(existing.map(node => [motionKey(node), node.getBoundingClientRect()]))
|
|
69
|
+
gsap.killTweensOf(existing)
|
|
70
|
+
render()
|
|
71
|
+
const next = [...container.querySelectorAll(selector)]
|
|
72
|
+
if (reduced) {
|
|
73
|
+
clear(next)
|
|
74
|
+
return true
|
|
75
|
+
}
|
|
76
|
+
const entering = []
|
|
77
|
+
const moving = []
|
|
78
|
+
for (const node of next) {
|
|
79
|
+
const previous = before.get(motionKey(node))
|
|
80
|
+
if (!previous) {
|
|
81
|
+
entering.push(node)
|
|
82
|
+
continue
|
|
83
|
+
}
|
|
84
|
+
const current = node.getBoundingClientRect()
|
|
85
|
+
const x = previous.left - current.left
|
|
86
|
+
const y = previous.top - current.top
|
|
87
|
+
if (Math.abs(x) > 0.5 || Math.abs(y) > 0.5) {
|
|
88
|
+
gsap.set(node, { x, y })
|
|
89
|
+
moving.push(node)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (moving.length) {
|
|
93
|
+
gsap.to(moving, {
|
|
94
|
+
x: 0,
|
|
95
|
+
y: 0,
|
|
96
|
+
duration: 0.28,
|
|
97
|
+
ease: 'power2.out',
|
|
98
|
+
stagger: { each: 0.018, from: 'start' },
|
|
99
|
+
clearProps: 'transform'
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
if (entering.length) {
|
|
103
|
+
gsap.fromTo(entering, { autoAlpha: 0, y: 8 }, {
|
|
104
|
+
autoAlpha: 1,
|
|
105
|
+
y: 0,
|
|
106
|
+
duration: 0.2,
|
|
107
|
+
ease: 'power2.out',
|
|
108
|
+
stagger: { each: 0.025, from: 'start' },
|
|
109
|
+
clearProps: 'transform'
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
return true
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function bindLongPressReorder(container, selector, options = {}) {
|
|
116
|
+
if (!container || !selector) return
|
|
117
|
+
const registry = container.__dshReorderRegistry || (container.__dshReorderRegistry = new Map())
|
|
118
|
+
const existing = registry.get(selector)
|
|
119
|
+
if (existing) {
|
|
120
|
+
existing.options = options
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
const LONG_PRESS_MS = 300
|
|
124
|
+
const MOVE_TOLERANCE = 28
|
|
125
|
+
const state = { options, press: null, drag: null, suppressClickUntil: 0 }
|
|
126
|
+
const raf = callback => window.requestAnimationFrame ? window.requestAnimationFrame(callback) : setTimeout(callback, 16)
|
|
127
|
+
const caf = id => window.cancelAnimationFrame ? window.cancelAnimationFrame(id) : clearTimeout(id)
|
|
128
|
+
const itemFrom = target => target?.closest?.(selector)
|
|
129
|
+
const listFrom = item => item?.parentElement || container
|
|
130
|
+
const groupFrom = item => state.options.groupSelector ? (item.closest(state.options.groupSelector) || container) : container
|
|
131
|
+
const keyFrom = item => state.options.key ? state.options.key(item) : motionKey(item)
|
|
132
|
+
const itemsFrom = list => [...list.querySelectorAll(selector)].filter(item => item.parentElement === list)
|
|
133
|
+
const scrollTargetsFrom = item => {
|
|
134
|
+
const targets = []
|
|
135
|
+
let node = item?.parentElement
|
|
136
|
+
while (node && node !== document.body) {
|
|
137
|
+
const style = window.getComputedStyle(node)
|
|
138
|
+
if ((style.overflowY === 'auto' || style.overflowY === 'scroll') && node.scrollHeight > node.clientHeight + 1) targets.push(node)
|
|
139
|
+
node = node.parentElement
|
|
140
|
+
}
|
|
141
|
+
if (document.scrollingElement) targets.push(document.scrollingElement)
|
|
142
|
+
return targets
|
|
143
|
+
}
|
|
144
|
+
const reorderDraggedItems = (drag, clientY) => {
|
|
145
|
+
const items = itemsFrom(drag.list).filter(item => item !== drag.item)
|
|
146
|
+
if (!items.length) return
|
|
147
|
+
const firstRects = new Map(items.map(item => [item, item.getBoundingClientRect()]))
|
|
148
|
+
let target = null
|
|
149
|
+
let insertBeforeTarget = false
|
|
150
|
+
for (const item of items) {
|
|
151
|
+
const rect = item.getBoundingClientRect()
|
|
152
|
+
if (clientY < rect.top + rect.height / 2) {
|
|
153
|
+
target = item
|
|
154
|
+
insertBeforeTarget = true
|
|
155
|
+
break
|
|
156
|
+
}
|
|
157
|
+
target = item
|
|
158
|
+
}
|
|
159
|
+
if (!target) return
|
|
160
|
+
const reference = insertBeforeTarget ? target : target.nextElementSibling
|
|
161
|
+
if (reference !== drag.placeholder) drag.list.insertBefore(drag.placeholder, reference || null)
|
|
162
|
+
if (!reduced) {
|
|
163
|
+
for (const item of items) {
|
|
164
|
+
const first = firstRects.get(item)
|
|
165
|
+
const last = item.getBoundingClientRect()
|
|
166
|
+
const x = first.left - last.left
|
|
167
|
+
const y = first.top - last.top
|
|
168
|
+
if (Math.abs(x) > 0.5 || Math.abs(y) > 0.5) {
|
|
169
|
+
gsap.fromTo(item, { x, y }, { x: 0, y: 0, duration: 0.16, ease: 'power2.out', clearProps: 'transform' })
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const autoScroll = drag => {
|
|
175
|
+
if (!drag || state.drag !== drag) return
|
|
176
|
+
const pointerY = drag.pointerY
|
|
177
|
+
const targets = drag.scrollTargets || []
|
|
178
|
+
for (const target of targets) {
|
|
179
|
+
const root = target === document.scrollingElement
|
|
180
|
+
? { top: 0, bottom: window.innerHeight }
|
|
181
|
+
: target.getBoundingClientRect()
|
|
182
|
+
const threshold = Math.min(78, Math.max(42, (root.bottom - root.top) * 0.14))
|
|
183
|
+
let delta = 0
|
|
184
|
+
if (pointerY < root.top + threshold) {
|
|
185
|
+
const strength = 1 - Math.max(0, pointerY - root.top) / threshold
|
|
186
|
+
if (target.scrollTop > 0) delta = -Math.ceil(4 + strength * 14)
|
|
187
|
+
} else if (pointerY > root.bottom - threshold) {
|
|
188
|
+
const max = target.scrollHeight - target.clientHeight
|
|
189
|
+
const strength = 1 - Math.max(0, root.bottom - pointerY) / threshold
|
|
190
|
+
if (target.scrollTop < max) delta = Math.ceil(4 + strength * 14)
|
|
191
|
+
}
|
|
192
|
+
if (delta) {
|
|
193
|
+
target.scrollTop = Math.max(0, Math.min(target.scrollHeight - target.clientHeight, target.scrollTop + delta))
|
|
194
|
+
reorderDraggedItems(drag, pointerY)
|
|
195
|
+
break
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
drag.scrollRaf = raf(() => autoScroll(drag))
|
|
199
|
+
}
|
|
200
|
+
const restoreStyle = (item, style) => {
|
|
201
|
+
if (style == null) item.removeAttribute('style')
|
|
202
|
+
else item.setAttribute('style', style)
|
|
203
|
+
}
|
|
204
|
+
const orderFrom = drag => {
|
|
205
|
+
const order = []
|
|
206
|
+
for (const child of drag.list.children) {
|
|
207
|
+
if (child === drag.placeholder) order.push(keyFrom(drag.item))
|
|
208
|
+
else if (child !== drag.item && child.matches?.(selector)) order.push(keyFrom(child))
|
|
209
|
+
}
|
|
210
|
+
return order
|
|
211
|
+
}
|
|
212
|
+
const animateDrop = (drag, commit) => {
|
|
213
|
+
const floating = drag.item.getBoundingClientRect()
|
|
214
|
+
const order = orderFrom(drag)
|
|
215
|
+
drag.list.insertBefore(drag.item, drag.placeholder)
|
|
216
|
+
drag.placeholder.remove()
|
|
217
|
+
restoreStyle(drag.item, drag.originalStyle)
|
|
218
|
+
const finalRect = drag.item.getBoundingClientRect()
|
|
219
|
+
const dx = floating.left - finalRect.left
|
|
220
|
+
const dy = floating.top - finalRect.top
|
|
221
|
+
if (!reduced && (Math.abs(dx) > 0.5 || Math.abs(dy) > 0.5)) {
|
|
222
|
+
gsap.fromTo(drag.item, { x: dx, y: dy, scale: 1.02 }, {
|
|
223
|
+
x: 0,
|
|
224
|
+
y: 0,
|
|
225
|
+
scale: 1,
|
|
226
|
+
duration: 0.24,
|
|
227
|
+
ease: 'power2.out',
|
|
228
|
+
clearProps: 'transform'
|
|
229
|
+
})
|
|
230
|
+
} else if (reduced) clear(drag.item)
|
|
231
|
+
if (commit) {
|
|
232
|
+
const callback = drag.options.onCommit
|
|
233
|
+
const payload = { item: drag.item, list: drag.list, group: drag.group, order }
|
|
234
|
+
if (callback) setTimeout(() => callback(payload), reduced ? 0 : 240)
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const restoreDrag = (drag, commit) => {
|
|
238
|
+
if (!commit) {
|
|
239
|
+
const originalNext = drag.originalNextSibling?.parentElement === drag.list ? drag.originalNextSibling : null
|
|
240
|
+
drag.list.insertBefore(drag.placeholder, originalNext)
|
|
241
|
+
}
|
|
242
|
+
animateDrop(drag, commit)
|
|
243
|
+
}
|
|
244
|
+
const setPageScrollLock = locked => {
|
|
245
|
+
document.documentElement.classList.toggle('reorder-scroll-lock', locked)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const cancelPress = () => {
|
|
249
|
+
if (!state.press) return
|
|
250
|
+
clearTimeout(state.press.timer)
|
|
251
|
+
state.press.item.classList.remove('reorder-pressing')
|
|
252
|
+
state.press = null
|
|
253
|
+
}
|
|
254
|
+
const finishDrag = (commit) => {
|
|
255
|
+
const drag = state.drag
|
|
256
|
+
if (!drag) return
|
|
257
|
+
state.drag = null
|
|
258
|
+
drag.item.classList.remove('reorder-dragging')
|
|
259
|
+
drag.item.removeAttribute('aria-grabbed')
|
|
260
|
+
drag.item.releasePointerCapture?.(drag.pointerId)
|
|
261
|
+
if (drag.scrollRaf != null) caf(drag.scrollRaf)
|
|
262
|
+
container.classList.remove('reorder-active')
|
|
263
|
+
setPageScrollLock(false)
|
|
264
|
+
if (commit) state.suppressClickUntil = Date.now() + 680
|
|
265
|
+
restoreDrag(drag, commit)
|
|
266
|
+
}
|
|
267
|
+
const activate = () => {
|
|
268
|
+
const press = state.press
|
|
269
|
+
if (!press) return
|
|
270
|
+
state.press = null
|
|
271
|
+
const rect = press.item.getBoundingClientRect()
|
|
272
|
+
const originalNextSibling = press.item.nextElementSibling
|
|
273
|
+
const placeholder = document.createElement('div')
|
|
274
|
+
placeholder.className = 'reorder-placeholder'
|
|
275
|
+
placeholder.setAttribute('aria-hidden', 'true')
|
|
276
|
+
placeholder.style.height = `${rect.height}px`
|
|
277
|
+
placeholder.style.width = `${rect.width}px`
|
|
278
|
+
press.item.before(placeholder)
|
|
279
|
+
state.drag = {
|
|
280
|
+
...press,
|
|
281
|
+
options: state.options,
|
|
282
|
+
placeholder,
|
|
283
|
+
originalStyle: press.item.getAttribute('style'),
|
|
284
|
+
originalNextSibling,
|
|
285
|
+
startY: press.y,
|
|
286
|
+
pointerY: press.y,
|
|
287
|
+
scrollTargets: scrollTargetsFrom(press.item)
|
|
288
|
+
}
|
|
289
|
+
press.item.setPointerCapture?.(press.pointerId)
|
|
290
|
+
press.item.classList.remove('reorder-pressing')
|
|
291
|
+
press.item.classList.add('reorder-dragging')
|
|
292
|
+
press.item.setAttribute('aria-grabbed', 'true')
|
|
293
|
+
press.item.style.position = 'fixed'
|
|
294
|
+
press.item.style.left = `${rect.left}px`
|
|
295
|
+
press.item.style.top = `${rect.top}px`
|
|
296
|
+
press.item.style.width = `${rect.width}px`
|
|
297
|
+
press.item.style.zIndex = '20'
|
|
298
|
+
press.item.style.pointerEvents = 'none'
|
|
299
|
+
if (!reduced) {
|
|
300
|
+
gsap.set(press.item, { scale: 1.02 })
|
|
301
|
+
state.drag.yTo = gsap.quickTo(press.item, 'y', { duration: 0.12, ease: 'power2.out' })
|
|
302
|
+
}
|
|
303
|
+
container.classList.add('reorder-active')
|
|
304
|
+
setPageScrollLock(true)
|
|
305
|
+
const drag = state.drag
|
|
306
|
+
drag.scrollRaf = raf(() => autoScroll(drag))
|
|
307
|
+
}
|
|
308
|
+
const onPointerDown = event => {
|
|
309
|
+
if (state.press || state.drag) return
|
|
310
|
+
if (event.button != null && event.button !== 0) return
|
|
311
|
+
const item = itemFrom(event.target)
|
|
312
|
+
if (!item || !container.contains(item)) return
|
|
313
|
+
const currentOptions = state.options || {}
|
|
314
|
+
if (currentOptions.handleSelector && !event.target.closest(currentOptions.handleSelector)) return
|
|
315
|
+
if (currentOptions.excludeSelector && event.target.closest(currentOptions.excludeSelector)) return
|
|
316
|
+
const list = listFrom(item)
|
|
317
|
+
const group = groupFrom(item)
|
|
318
|
+
if (!itemsFrom(list).includes(item)) return
|
|
319
|
+
const press = { item, list, group, pointerId: event.pointerId, x: event.clientX, y: event.clientY, timer: 0 }
|
|
320
|
+
press.timer = setTimeout(activate, LONG_PRESS_MS)
|
|
321
|
+
state.press = press
|
|
322
|
+
item.classList.add('reorder-pressing')
|
|
323
|
+
}
|
|
324
|
+
const onPointerMove = event => {
|
|
325
|
+
const press = state.press
|
|
326
|
+
if (press && !state.drag) {
|
|
327
|
+
if (Math.hypot(event.clientX - press.x, event.clientY - press.y) > MOVE_TOLERANCE) cancelPress()
|
|
328
|
+
return
|
|
329
|
+
}
|
|
330
|
+
const drag = state.drag
|
|
331
|
+
if (!drag || drag.pointerId !== event.pointerId) return
|
|
332
|
+
event.preventDefault()
|
|
333
|
+
drag.pointerY = event.clientY
|
|
334
|
+
if (!reduced) drag.yTo?.(event.clientY - drag.startY)
|
|
335
|
+
reorderDraggedItems(drag, event.clientY)
|
|
336
|
+
}
|
|
337
|
+
const onPointerUp = event => {
|
|
338
|
+
if (state.press?.pointerId === event.pointerId) cancelPress()
|
|
339
|
+
if (state.drag?.pointerId === event.pointerId) finishDrag(true)
|
|
340
|
+
}
|
|
341
|
+
const onPointerCancel = event => {
|
|
342
|
+
if (state.press?.pointerId === event.pointerId) cancelPress()
|
|
343
|
+
if (state.drag?.pointerId === event.pointerId) finishDrag(false)
|
|
344
|
+
}
|
|
345
|
+
const touchPoint = event => event.changedTouches?.[0] || event.touches?.[0]
|
|
346
|
+
const touchPointerEvent = event => {
|
|
347
|
+
const point = touchPoint(event)
|
|
348
|
+
if (!point) return null
|
|
349
|
+
return {
|
|
350
|
+
target: event.target,
|
|
351
|
+
button: 0,
|
|
352
|
+
pointerId: 10000 + point.identifier,
|
|
353
|
+
clientX: point.clientX,
|
|
354
|
+
clientY: point.clientY,
|
|
355
|
+
preventDefault: () => event.preventDefault()
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const onTouchStart = event => {
|
|
359
|
+
const normalized = touchPointerEvent(event)
|
|
360
|
+
if (normalized) onPointerDown(normalized)
|
|
361
|
+
}
|
|
362
|
+
const onTouchMove = event => {
|
|
363
|
+
const normalized = touchPointerEvent(event)
|
|
364
|
+
if (normalized) onPointerMove(normalized)
|
|
365
|
+
}
|
|
366
|
+
const onTouchEnd = event => {
|
|
367
|
+
const normalized = touchPointerEvent(event)
|
|
368
|
+
if (normalized) onPointerUp(normalized)
|
|
369
|
+
}
|
|
370
|
+
const onTouchCancel = event => {
|
|
371
|
+
const normalized = touchPointerEvent(event)
|
|
372
|
+
if (normalized) onPointerCancel(normalized)
|
|
373
|
+
}
|
|
374
|
+
const onDocumentMove = event => {
|
|
375
|
+
if (state.drag) event.preventDefault()
|
|
376
|
+
}
|
|
377
|
+
const pointerIdFromEvent = event => {
|
|
378
|
+
if (event.pointerId != null) return event.pointerId
|
|
379
|
+
const point = touchPoint(event)
|
|
380
|
+
return point ? 10000 + point.identifier : null
|
|
381
|
+
}
|
|
382
|
+
const onDocumentEnd = (event, commit) => {
|
|
383
|
+
const pointerId = pointerIdFromEvent(event)
|
|
384
|
+
if (state.press && (pointerId == null || state.press.pointerId === pointerId)) cancelPress()
|
|
385
|
+
if (state.drag && (pointerId == null || state.drag.pointerId === pointerId)) finishDrag(commit)
|
|
386
|
+
}
|
|
387
|
+
const onWindowBlur = () => onDocumentEnd({}, false)
|
|
388
|
+
const onVisibilityChange = () => {
|
|
389
|
+
if (document.hidden) onDocumentEnd({}, false)
|
|
390
|
+
}
|
|
391
|
+
const onContextMenu = event => {
|
|
392
|
+
if (state.press || state.drag) event.preventDefault()
|
|
393
|
+
}
|
|
394
|
+
const onClick = event => {
|
|
395
|
+
if (Date.now() >= state.suppressClickUntil) return
|
|
396
|
+
if (itemFrom(event.target)) {
|
|
397
|
+
event.preventDefault()
|
|
398
|
+
event.stopPropagation()
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
container.addEventListener('pointerdown', onPointerDown)
|
|
402
|
+
container.addEventListener('pointermove', onPointerMove)
|
|
403
|
+
container.addEventListener('pointerup', onPointerUp)
|
|
404
|
+
container.addEventListener('pointercancel', onPointerCancel)
|
|
405
|
+
container.addEventListener('lostpointercapture', onPointerCancel)
|
|
406
|
+
container.addEventListener('touchstart', onTouchStart, { passive: false })
|
|
407
|
+
container.addEventListener('touchmove', onTouchMove, { passive: false })
|
|
408
|
+
container.addEventListener('touchend', onTouchEnd, { passive: false })
|
|
409
|
+
container.addEventListener('touchcancel', onTouchCancel, { passive: false })
|
|
410
|
+
container.addEventListener('contextmenu', onContextMenu)
|
|
411
|
+
container.addEventListener('click', onClick, true)
|
|
412
|
+
document.addEventListener('touchmove', onDocumentMove, { passive: false, capture: true })
|
|
413
|
+
document.addEventListener('pointermove', onDocumentMove, { passive: false, capture: true })
|
|
414
|
+
document.addEventListener('pointerup', event => onDocumentEnd(event, true), { passive: false, capture: true })
|
|
415
|
+
document.addEventListener('pointercancel', event => onDocumentEnd(event, false), { passive: false, capture: true })
|
|
416
|
+
document.addEventListener('touchend', event => onDocumentEnd(event, true), { passive: false, capture: true })
|
|
417
|
+
document.addEventListener('touchcancel', event => onDocumentEnd(event, false), { passive: false, capture: true })
|
|
418
|
+
window.addEventListener('blur', onWindowBlur)
|
|
419
|
+
document.addEventListener('visibilitychange', onVisibilityChange)
|
|
420
|
+
registry.set(selector, state)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function overlay(node) {
|
|
424
|
+
if (!node) return
|
|
425
|
+
const card = node.querySelector('.modal-card, .ds-modal-card, .sheet, .ds-drawer') || node
|
|
426
|
+
gsap.killTweensOf([node, card])
|
|
427
|
+
if (reduced) {
|
|
428
|
+
clear([node, card])
|
|
429
|
+
return
|
|
430
|
+
}
|
|
431
|
+
const isSideDrawer = card.classList.contains('ds-drawer')
|
|
432
|
+
const isSheet = card.classList.contains('sheet')
|
|
433
|
+
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } })
|
|
434
|
+
tl.fromTo(node, { autoAlpha: 0 }, { autoAlpha: 1, duration: 0.16 })
|
|
435
|
+
.fromTo(card,
|
|
436
|
+
{ autoAlpha: 0, x: isSideDrawer ? 28 : 0, y: isSheet ? 24 : 8, scale: isSideDrawer || isSheet ? 1 : 0.985 },
|
|
437
|
+
{ autoAlpha: 1, x: 0, y: 0, scale: 1, duration: isSheet ? 0.24 : 0.2, clearProps: 'transform' },
|
|
438
|
+
'<'
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function pulse(targets) {
|
|
443
|
+
if (reduced) return clear(targets)
|
|
444
|
+
const nodes = typeof targets === 'string' ? document.querySelectorAll(targets) : targets
|
|
445
|
+
for (const node of nodes || []) {
|
|
446
|
+
if (activePulses.has(node)) continue
|
|
447
|
+
activePulses.add(node)
|
|
448
|
+
gsap.to(node, { scale: 1.12, autoAlpha: 0.62, duration: 0.85, ease: 'sine.inOut', repeat: -1, yoyo: true, transformOrigin: '50% 50%' })
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
window.DshMotion = { gsap, reduced: () => reduced, view, list, relayout, bindLongPressReorder, overlay, pulse }
|
|
453
|
+
|
|
454
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
455
|
+
document.querySelectorAll('.view:not(.hidden), .ds-view:not(.hidden)').forEach(node => view(node))
|
|
456
|
+
}, { once: true })
|
|
457
|
+
|
|
458
|
+
const observer = new MutationObserver(records => {
|
|
459
|
+
for (const record of records) {
|
|
460
|
+
const node = record.target
|
|
461
|
+
if (!(node instanceof HTMLElement)) continue
|
|
462
|
+
const becameVisible = record.oldValue?.split(/\s+/).includes('hidden') && !node.classList.contains('hidden')
|
|
463
|
+
if (!becameVisible) continue
|
|
464
|
+
if (node.matches('.view, .ds-view')) view(node)
|
|
465
|
+
else if (node.matches('.modal, .ds-modal, .sheet, .ds-drawer')) overlay(node)
|
|
466
|
+
}
|
|
467
|
+
})
|
|
468
|
+
observer.observe(document.documentElement, { subtree: true, attributes: true, attributeFilter: ['class'], attributeOldValue: true })
|
|
469
|
+
})()
|
package/public/plugin.html
CHANGED
|
@@ -116,7 +116,7 @@
|
|
|
116
116
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="8" y="8" width="11" height="11" rx="2"/><path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"/></svg>复制令牌
|
|
117
117
|
</button>
|
|
118
118
|
<button id="plugin-toggle" class="plugin-action" type="button">
|
|
119
|
-
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v9M7.8 5.4a7 7 0 1 0 8.4 0"/></svg><span id="plugin-toggle-label">启动网关</span>
|
|
119
|
+
<morph-icon id="plugin-toggle-icon" data-morph-state="closed" data-morph-closed="M12 3v9M7.8 5.4a7 7 0 1 0 8.4 0" data-morph-open="M7 7h10v10H7z" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3v9M7.8 5.4a7 7 0 1 0 8.4 0"/></svg></morph-icon><span id="plugin-toggle-label">启动网关</span>
|
|
120
120
|
</button>
|
|
121
121
|
</section>
|
|
122
122
|
|
|
@@ -146,6 +146,9 @@
|
|
|
146
146
|
|
|
147
147
|
<footer class="plugin-foot"><span>状态面板 · 需要深入管理时进入控制台</span><a id="plugin-about" href="/remote/admin/" target="_blank" rel="noopener">关于与支持</a></footer>
|
|
148
148
|
</main>
|
|
149
|
+
<script src="vendor/gsap/gsap.min.js"></script>
|
|
150
|
+
<script src="motion.js"></script>
|
|
151
|
+
<script type="module" src="morphicons-init.js"></script>
|
|
149
152
|
<script src="plugin.js"></script>
|
|
150
153
|
</body>
|
|
151
154
|
</html>
|
package/public/plugin.js
CHANGED
|
@@ -99,6 +99,7 @@ function render(st) {
|
|
|
99
99
|
primary.textContent = healthy ? '打开控制台' : installed ? '启动网关' : '查看控制台'
|
|
100
100
|
primary.dataset.action = healthy ? 'console' : installed ? 'start' : 'console'
|
|
101
101
|
text('plugin-toggle-label', gateway ? '停止网关' : '启动网关')
|
|
102
|
+
$('plugin-toggle-icon')?.setAttribute('data-morph-state', gateway ? 'open' : 'closed')
|
|
102
103
|
$('plugin-toggle').classList.toggle('hidden', !installed)
|
|
103
104
|
|
|
104
105
|
text('plugin-version', st.version ? 'v' + st.version : '—')
|
package/public/styles.css
CHANGED
|
@@ -112,8 +112,6 @@ a.mini-btn { text-decoration: none; display: inline-flex; align-items: center; }
|
|
|
112
112
|
|
|
113
113
|
/* ---------- 主体 ---------- */
|
|
114
114
|
.main { padding: 12px 12px 20px; max-width: 720px; margin: 0 auto; }
|
|
115
|
-
.view { animation: fadein .18s ease; }
|
|
116
|
-
@keyframes fadein { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; } }
|
|
117
115
|
|
|
118
116
|
.stat-strip {
|
|
119
117
|
display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 14px;
|
|
@@ -215,6 +213,12 @@ a.mini-btn { text-decoration: none; display: inline-flex; align-items: center; }
|
|
|
215
213
|
box-sizing: border-box; margin: 8px 2px 0; padding: 6px 8px 4px; border-bottom: 1px solid var(--dsr-line);
|
|
216
214
|
color: var(--dsr-accent-strong); font-size: 11px; font-weight: 700;
|
|
217
215
|
}
|
|
216
|
+
.session-workspace-group { flex: 0 0 auto; display: flex; flex-direction: column; gap: 0; }
|
|
217
|
+
.session-group-label { touch-action: pan-y; user-select: none; }
|
|
218
|
+
.session-group-drag-handle { flex: 0 0 auto; color: var(--dsr-muted); opacity: .52; font-size: 14px; line-height: 1; letter-spacing: -3px; }
|
|
219
|
+
.session-workspace-group.reorder-pressing { outline: 1px solid var(--dsr-accent-line); border-radius: var(--dsr-radius); }
|
|
220
|
+
.session-workspace-group.reorder-dragging { position: relative; z-index: 5; opacity: .76; outline: 1px solid var(--dsr-accent-strong); box-shadow: 0 10px 24px var(--dsr-shadow); touch-action: none; }
|
|
221
|
+
.session-workspace-group .session-card { touch-action: pan-y; }
|
|
218
222
|
.session-group-icon { flex: 0 0 auto; color: var(--dsr-accent-2); font-size: 12px; }
|
|
219
223
|
.session-group-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
220
224
|
.session-list.workspace-sorted .sc-workspace { display: none; }
|
|
@@ -255,11 +259,18 @@ a.mini-btn { text-decoration: none; display: inline-flex; align-items: center; }
|
|
|
255
259
|
.workbench-bar.bound .wb-toggle[aria-expanded="true"] .wb-chevron { transform: rotate(180deg); }
|
|
256
260
|
.wb-panel { background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-top: none; border-radius: 0 0 var(--dsr-radius) var(--dsr-radius); padding: 8px; display: flex; flex-direction: column; gap: 6px; }
|
|
257
261
|
.wb-project { background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: 10px; overflow: hidden; }
|
|
258
|
-
.wb-project-head { display: flex; align-items: center; gap: 7px; padding: 9px 10px; cursor: pointer; }
|
|
262
|
+
.wb-project-head { display: flex; align-items: center; gap: 7px; padding: 9px 10px; cursor: pointer; touch-action: pan-y; user-select: none; }
|
|
263
|
+
.wb-drag-handle, .wb-session-drag-handle { flex: none; color: var(--dsr-muted); opacity: .52; font-size: 14px; line-height: 1; letter-spacing: -3px; }
|
|
264
|
+
.wb-project.reorder-pressing, .wb-session.reorder-pressing { outline: 1px solid var(--dsr-accent-line); }
|
|
265
|
+
.wb-project.reorder-dragging, .wb-session.reorder-dragging { position: relative; z-index: 5; opacity: .76; outline: 1px solid var(--dsr-accent-strong); box-shadow: 0 10px 24px var(--dsr-shadow); touch-action: none; }
|
|
266
|
+
.wb-panel.reorder-active { user-select: none; }
|
|
267
|
+
.reorder-placeholder { flex: 0 0 auto; box-sizing: border-box; border: 1px dashed var(--dsr-accent-line); border-radius: 8px; background: var(--dsr-accent-soft); opacity: .72; }
|
|
268
|
+
html.reorder-scroll-lock { overscroll-behavior: none; }
|
|
269
|
+
html.reorder-scroll-lock body { overscroll-behavior: none; }
|
|
259
270
|
.wb-project-title { flex: 1; min-width: 0; font-size: 13.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
260
271
|
.wb-new { padding: 3px 9px; font-size: 12px; }
|
|
261
272
|
.wb-sessions { display: flex; flex-direction: column; gap: 4px; padding: 0 6px 6px; }
|
|
262
|
-
.wb-session { width: 100%; display: flex; align-items: center; gap: 8px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 8px; padding: 7px 9px; text-align: left; color: var(--dsr-text); font: inherit; }
|
|
273
|
+
.wb-session { width: 100%; display: flex; align-items: center; gap: 8px; background: var(--dsr-bg-2); border: 1px solid var(--dsr-line); border-radius: 8px; padding: 7px 9px; text-align: left; color: var(--dsr-text); font: inherit; touch-action: pan-y; user-select: none; }
|
|
263
274
|
.wb-session-title { flex: 1; min-width: 0; font-size: 13px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
264
275
|
.wb-session-meta { flex-shrink: 0; font-size: 11px; color: var(--dsr-muted); }
|
|
265
276
|
.wb-empty { font-size: 12px; color: var(--dsr-muted); text-align: center; padding: 10px 0; }
|
|
@@ -317,6 +328,9 @@ body.in-session .view {
|
|
|
317
328
|
font-size: 16px; font-weight: 700; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
318
329
|
}
|
|
319
330
|
.session-sub { font-size: 12px; color: var(--dsr-muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
331
|
+
.session-head .mini-btn { flex: none; padding: 7px 9px; }
|
|
332
|
+
.text-input { width: 100%; box-sizing: border-box; min-height: 42px; padding: 10px 12px; border: 1px solid var(--dsr-line); border-radius: 10px; background: var(--dsr-bg-2); color: var(--dsr-text); font: inherit; outline: none; }
|
|
333
|
+
.text-input:focus { border-color: var(--dsr-accent-line); }
|
|
320
334
|
.cards {
|
|
321
335
|
display: flex; flex-direction: column; gap: 8px; margin-top: 8px;
|
|
322
336
|
flex-shrink: 0; max-height: 42vh; overflow-y: auto;
|
|
@@ -333,6 +347,8 @@ body.in-session .view {
|
|
|
333
347
|
.subagent-toggle { width: 100%; display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 10px 12px; border: 0; background: transparent; color: inherit; text-align: left; cursor: pointer; }
|
|
334
348
|
.subagent-toggle .card-title { margin: 0; }
|
|
335
349
|
.subagent-toggle-icon { color: var(--dsr-muted); font-size: 16px; line-height: 1; }
|
|
350
|
+
.subagent-toggle-icon morph-icon,
|
|
351
|
+
.subagent-toggle-icon morph-icon svg { width: 16px; height: 16px; display: block; }
|
|
336
352
|
.subagent-list { padding: 0 12px 10px; border-top: 1px solid var(--dsr-line); }
|
|
337
353
|
.queue-dock { margin: 0 0 10px; border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-surface); overflow: hidden; }
|
|
338
354
|
.queue-dock-head { display: flex; align-items: center; gap: 8px; padding: 9px 12px; color: var(--dsr-muted); font-size: 12px; font-weight: 700; border-bottom: 1px solid var(--dsr-line); }
|
|
@@ -704,6 +720,81 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
|
|
|
704
720
|
background: var(--dsr-panel); border: 1px solid var(--dsr-line); border-radius: var(--dsr-radius);
|
|
705
721
|
margin-bottom: 10px; overflow: hidden;
|
|
706
722
|
}
|
|
723
|
+
.model-settings-shell { padding: 14px; }
|
|
724
|
+
.model-settings-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--dsr-line); }
|
|
725
|
+
.model-settings-head .setting-actions { flex-wrap: nowrap; }
|
|
726
|
+
.model-settings-status { padding: 12px 0 2px; font-size: 12px; }
|
|
727
|
+
.model-settings-status.error { color: var(--dsr-error); white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
728
|
+
.model-settings-list { display: flex; flex-direction: column; gap: 8px; padding-top: 10px; }
|
|
729
|
+
.model-provider-card { border: 1px solid var(--dsr-line); border-radius: 12px; background: var(--dsr-bg-2); overflow: hidden; }
|
|
730
|
+
.model-provider-head { display: flex; align-items: center; gap: 8px; padding: 11px 12px; }
|
|
731
|
+
.model-provider-identity { display: flex; align-items: center; gap: 7px; min-width: 0; flex: 1; }
|
|
732
|
+
.model-provider-name { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; font-weight: 600; }
|
|
733
|
+
.model-provider-route { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--dsr-muted); font-size: 11px; }
|
|
734
|
+
.model-provider-dot { width: 8px; height: 8px; flex: 0 0 auto; border-radius: 50%; background: var(--dsr-muted); }
|
|
735
|
+
.model-provider-dot.configured { background: var(--dsr-success); box-shadow: 0 0 8px var(--dsr-success); }
|
|
736
|
+
.model-provider-dot.unknown { background: var(--dsr-info); }
|
|
737
|
+
.model-editor { display: flex; flex-direction: column; gap: 12px; padding: 13px 14px 14px; background: var(--dsr-panel); border-top: 1px solid var(--dsr-line); }
|
|
738
|
+
.model-editor-title { display: flex; align-items: baseline; gap: 8px; }
|
|
739
|
+
.model-editor-title strong { font-size: 14px; }
|
|
740
|
+
.model-editor-title code { color: var(--dsr-muted); font-size: 11px; overflow-wrap: anywhere; }
|
|
741
|
+
.model-field { display: flex; flex-direction: column; gap: 5px; }
|
|
742
|
+
.model-field > label, .model-field-label { color: var(--dsr-muted); font-size: 12px; }
|
|
743
|
+
.model-input { box-sizing: border-box; width: 100%; min-height: 36px; padding: 8px 10px; border: 1px solid var(--dsr-line); border-radius: 9px; background: var(--dsr-bg-2); color: var(--dsr-text); font: inherit; font-size: 13px; outline: none; }
|
|
744
|
+
.model-input:focus { border-color: var(--dsr-accent-line); box-shadow: 0 0 0 2px var(--dsr-accent-soft); }
|
|
745
|
+
.model-input::placeholder { color: var(--dsr-muted); opacity: .85; }
|
|
746
|
+
.model-input[type=password] { letter-spacing: .04em; }
|
|
747
|
+
.model-inline { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: 9px; }
|
|
748
|
+
.model-catalog { display: flex; flex-direction: column; gap: 8px; padding-top: 3px; border-top: 1px solid var(--dsr-line); }
|
|
749
|
+
.model-catalog-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
750
|
+
.model-catalog-title { font-size: 12px; color: var(--dsr-muted); }
|
|
751
|
+
.model-catalog-hint { margin-top: 2px; font-size: 11px; color: var(--dsr-muted); line-height: 1.45; }
|
|
752
|
+
.model-catalog-actions { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
|
|
753
|
+
.model-list { display: flex; flex-direction: column; gap: 6px; }
|
|
754
|
+
.model-entry-card { display: flex; flex-direction: column; gap: 8px; padding: 9px; border: 1px solid var(--dsr-line); border-radius: 9px; background: var(--dsr-bg-2); }
|
|
755
|
+
.model-entry { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr) auto; gap: 6px; align-items: center; }
|
|
756
|
+
.model-entry .model-input { min-width: 0; min-height: 32px; padding: 6px 8px; font-size: 12px; }
|
|
757
|
+
.model-entry-remove { width: 30px; height: 30px; padding: 0; border: 1px solid var(--dsr-line); border-radius: 8px; background: transparent; color: var(--dsr-muted); cursor: pointer; }
|
|
758
|
+
.model-entry-remove:active { color: var(--dsr-error); border-color: var(--dsr-error); }
|
|
759
|
+
.model-empty { padding: 10px; border: 1px dashed var(--dsr-line); border-radius: 9px; color: var(--dsr-muted); font-size: 12px; text-align: center; }
|
|
760
|
+
.model-reasoning { display: flex; flex-direction: column; gap: 7px; padding-top: 8px; border-top: 1px solid var(--dsr-line); }
|
|
761
|
+
.model-reasoning-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 8px; }
|
|
762
|
+
.model-reasoning-list { display: flex; flex-direction: column; gap: 6px; }
|
|
763
|
+
.model-reasoning-entry { display: grid; grid-template-columns: minmax(0, .9fr) minmax(0, 1fr) minmax(0, 1.4fr) auto; gap: 6px; align-items: center; }
|
|
764
|
+
.model-reasoning-entry .model-input { min-width: 0; min-height: 32px; padding: 6px 8px; font-size: 12px; }
|
|
765
|
+
.model-reasoning-default { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8px; color: var(--dsr-muted); font-size: 12px; }
|
|
766
|
+
.model-reasoning-default .model-input { min-height: 32px; padding: 6px 8px; font-size: 12px; }
|
|
767
|
+
.model-discovery { display: flex; flex-direction: column; gap: 7px; padding: 9px; border: 1px solid var(--dsr-info-line); border-radius: 9px; background: var(--dsr-info-soft); }
|
|
768
|
+
.model-discovery-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; color: var(--dsr-info); font-size: 12px; }
|
|
769
|
+
.model-discovery-list { display: flex; flex-direction: column; gap: 4px; max-height: 180px; overflow-y: auto; }
|
|
770
|
+
.model-discovery-row { display: flex; align-items: center; gap: 7px; min-height: 30px; font-size: 12px; }
|
|
771
|
+
.model-discovery-row input { flex: 0 0 auto; }
|
|
772
|
+
.model-discovery-row code { min-width: 0; overflow-wrap: anywhere; color: var(--dsr-text); }
|
|
773
|
+
.model-editor-actions { display: flex; justify-content: flex-end; gap: 8px; padding-top: 2px; }
|
|
774
|
+
.model-editor-error { margin: 0; color: var(--dsr-error); font-size: 12px; line-height: 1.45; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
775
|
+
.model-readonly { color: var(--dsr-warning); font-size: 12px; }
|
|
776
|
+
.feature-test-shell { padding: 14px; }
|
|
777
|
+
.feature-test-head { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--dsr-line); }
|
|
778
|
+
.feature-test-note { margin-top: 12px; padding: 10px 11px; border: 1px solid var(--dsr-info-line); border-radius: 9px; background: var(--dsr-info-soft); color: var(--dsr-info); font-size: 12px; line-height: 1.55; }
|
|
779
|
+
.feature-test-actions { display: flex; flex-wrap: wrap; gap: 7px; padding: 12px 0 8px; }
|
|
780
|
+
.feature-test-status { min-height: 20px; padding: 4px 0; font-size: 12px; }
|
|
781
|
+
.feature-test-status.error { color: var(--dsr-error); }
|
|
782
|
+
.feature-test-status.ok { color: var(--dsr-success); }
|
|
783
|
+
.feature-test-summary { margin-top: 6px; color: var(--dsr-text); font-size: 12px; line-height: 1.55; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
784
|
+
.feature-test-log { max-height: 420px; min-height: 170px; margin: 10px 0 0; padding: 11px; overflow: auto; border: 1px solid var(--dsr-line); border-radius: 9px; background: var(--dsr-bg-2); color: var(--dsr-text); font: 11px/1.55 ui-monospace, SFMono-Regular, Consolas, monospace; white-space: pre-wrap; overflow-wrap: anywhere; }
|
|
785
|
+
@media (max-width: 520px) {
|
|
786
|
+
.model-settings-head { flex-direction: column; }
|
|
787
|
+
.model-settings-head .setting-actions { width: 100%; justify-content: flex-start; }
|
|
788
|
+
.model-inline { grid-template-columns: 1fr; }
|
|
789
|
+
.model-entry { grid-template-columns: minmax(0, 1fr) auto; }
|
|
790
|
+
.model-entry .model-name-input { grid-column: 1 / -1; grid-row: 2; }
|
|
791
|
+
.model-reasoning-entry { grid-template-columns: minmax(0, 1fr) auto; }
|
|
792
|
+
.model-reasoning-entry .model-input:nth-child(2), .model-reasoning-entry .model-input:nth-child(3) { grid-column: 1 / -1; }
|
|
793
|
+
.model-reasoning-entry .model-entry-remove { grid-column: 2; grid-row: 1; }
|
|
794
|
+
.model-reasoning-default { grid-template-columns: 1fr; gap: 4px; }
|
|
795
|
+
.feature-test-actions { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
796
|
+
.feature-test-actions .mini-btn { width: 100%; }
|
|
797
|
+
}
|
|
707
798
|
.setting-row {
|
|
708
799
|
display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
|
709
800
|
padding: 13px 14px;
|
|
@@ -711,6 +802,7 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
|
|
|
711
802
|
.setting-row > div:first-child { flex: 1; min-width: 0; }
|
|
712
803
|
.setting-row + .setting-row { border-top: 1px solid var(--dsr-line); }
|
|
713
804
|
.setting-name { font-size: 14.5px; font-weight: 600; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
805
|
+
.dsh-experimental-badge { display: inline-block; margin-left: 6px; padding: 2px 6px; border: 1px solid var(--dsr-warning-line); border-radius: 999px; color: var(--dsr-warning); background: var(--dsr-warning-soft); font-size: 10px; font-weight: 700; vertical-align: 2px; }
|
|
714
806
|
.setting-desc { font-size: 12px; color: var(--dsr-muted); margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
715
807
|
.setting-desc.expanded { white-space: normal; overflow: visible; text-overflow: clip; word-break: break-word; }
|
|
716
808
|
.setting-select { min-width: 112px; max-width: 145px; padding: 7px 9px; border-radius: 10px; border: 1px solid var(--dsr-line); background: var(--dsr-bg-2); color: var(--dsr-text); font: inherit; font-size: 12px; }
|
|
@@ -845,7 +937,6 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
|
|
|
845
937
|
background: var(--dsr-bg-2); border-top: 1px solid var(--dsr-line); border-radius: 20px 20px 0 0;
|
|
846
938
|
padding: 8px 12px calc(12px + env(safe-area-inset-bottom, 0px));
|
|
847
939
|
box-shadow: 0 -10px 34px var(--dsr-shadow);
|
|
848
|
-
animation: sheet-up .22s cubic-bezier(.2,.8,.3,1);
|
|
849
940
|
}
|
|
850
941
|
.sheet-handle { width: 38px; height: 4px; border-radius: 999px; background: var(--dsr-line); margin: 2px auto 10px; }
|
|
851
942
|
.sheet-title { font-size: 14px; font-weight: 700; padding: 0 6px 8px; }
|
|
@@ -867,7 +958,6 @@ button.overview-attention-item, button.overview-session-item { cursor:pointer; }
|
|
|
867
958
|
.sheet-item-name { font-size: 14px; font-weight: 600; }
|
|
868
959
|
.sheet-item-desc { font-size: 12px; color: var(--dsr-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
|
869
960
|
@keyframes sheet-fade { from { opacity: 0 } to { opacity: 1 } }
|
|
870
|
-
@keyframes sheet-up { from { transform: translateY(24px); opacity: .6 } to { transform: translateY(0); opacity: 1 } }
|
|
871
961
|
|
|
872
962
|
/* ---------- 应用内选择抽屉 ---------- */
|
|
873
963
|
.custom-select-native {
|