@ash-qw/dsh-theme-prts 2.3.0 → 2.4.0
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 +20 -0
- package/README.en.md +5 -3
- package/README.md +5 -3
- package/lib/client.js +430 -51
- package/package.json +1 -1
- package/src/client/background-layout.js +65 -0
- package/src/client/background-region-editor.js +103 -0
- package/src/client/cursor-adapter.js +1 -1
- package/src/client/custom-background.js +23 -7
- package/src/client/host-geometry-adapter.js +6 -1
- package/src/client/index.js +2 -0
- package/src/client/native-preferences.js +7 -0
- package/src/client/opacity-dial.js +13 -8
- package/src/client/preferences.js +5 -1
- package/src/client/session-card-reveal.js +125 -0
- package/src/client/sidebar-control-adapter.js +40 -35
- package/src/client/theme-controller-v2.js +2 -0
- package/src/client/theme-transfer-panel.js +9 -1
- package/src/client/theme-transfer.js +33 -3
- package/src/server/backgrounds.js +10 -6
- package/src/shared/background-region.js +21 -0
- package/src/styles/cursor.css +8 -0
- package/src/styles/custom-background.css +95 -19
- package/src/styles/prts.css +2 -2
- package/src/styles/refinement.css +6 -0
- package/src/styles/theme-settings-workbench.css +1 -0
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createSessionCardReveal } from './session-card-reveal.js'
|
|
1
2
|
import { createSessionSummaryPreview, scrollSummaryTitle } from './session-summary-preview.js'
|
|
2
3
|
import { ensureFacilityGraphic, ensureFacilityTextureDefs, renderFacilityGraphic } from './facility-vector.js'
|
|
3
4
|
import { createSessionLifelineAnimator } from './session-lifeline.js'
|
|
@@ -353,7 +354,11 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
353
354
|
queueScan()
|
|
354
355
|
}
|
|
355
356
|
function interruptSelectionTarget(event) {
|
|
356
|
-
if (event.target.closest?.('[data-prts-session-row]'))
|
|
357
|
+
if (event.target.closest?.('[data-prts-session-row]')) {
|
|
358
|
+
cancelPendingNavigation()
|
|
359
|
+
cancelReveal()
|
|
360
|
+
clearSelectionTarget()
|
|
361
|
+
}
|
|
357
362
|
}
|
|
358
363
|
function setSelectionTarget(id) {
|
|
359
364
|
clearSelectionTarget()
|
|
@@ -362,27 +367,41 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
362
367
|
document.addEventListener('pointerdown', interruptSelectionTarget, true)
|
|
363
368
|
}
|
|
364
369
|
|
|
365
|
-
let pendingReveal, revealTimer
|
|
370
|
+
let pendingReveal, revealTimer
|
|
366
371
|
let navigationFrame, navigationGeneration = 0
|
|
367
372
|
function cancelPendingNavigation() {
|
|
368
373
|
navigationGeneration++
|
|
369
374
|
window.cancelAnimationFrame?.(navigationFrame)
|
|
370
375
|
}
|
|
371
376
|
function cancelReveal() {
|
|
377
|
+
pendingReveal?.tracker?.cancel()
|
|
372
378
|
pendingReveal = undefined
|
|
373
379
|
window.clearTimeout(revealTimer)
|
|
374
|
-
|
|
375
|
-
window.cancelAnimationFrame?.(revealFrame)
|
|
380
|
+
for (const type of ['wheel','touchmove','pointerdown','keydown']) document.removeEventListener(type, interruptReveal, true)
|
|
376
381
|
}
|
|
377
|
-
function
|
|
382
|
+
function interruptReveal(event) {
|
|
383
|
+
if (!event.target?.closest?.('[data-prts-region="sessions"]')) return
|
|
384
|
+
if (event.type === 'keydown' && !['ArrowUp','ArrowDown','PageUp','PageDown','Home','End',' '].includes(event.key)) return
|
|
385
|
+
cancelPendingNavigation()
|
|
378
386
|
cancelReveal()
|
|
379
|
-
|
|
380
|
-
|
|
387
|
+
clearSelectionTarget()
|
|
388
|
+
}
|
|
389
|
+
function revealSessionCard(sessionId, onDone) {
|
|
390
|
+
pendingReveal?.tracker?.interrupt()
|
|
391
|
+
cancelReveal()
|
|
392
|
+
pendingReveal = {sessionId, onDone}
|
|
393
|
+
revealTimer = window.setTimeout(finishReveal, 4000)
|
|
394
|
+
for (const type of ['wheel','touchmove','pointerdown','keydown']) document.addEventListener(type, interruptReveal, {capture:true, passive:true})
|
|
381
395
|
const root = document?.querySelector?.('[data-prts-region="sessions"] [data-slot="sidebar.workspaces"]')
|
|
382
396
|
syncSessionReveal(root)
|
|
383
397
|
queueScan()
|
|
384
398
|
return Boolean(pendingReveal?.expanding)
|
|
385
399
|
}
|
|
400
|
+
function finishReveal() {
|
|
401
|
+
const onDone = pendingReveal?.onDone
|
|
402
|
+
cancelReveal()
|
|
403
|
+
onDone?.()
|
|
404
|
+
}
|
|
386
405
|
function syncSessionReveal(root) {
|
|
387
406
|
if (!pendingReveal || !root) return
|
|
388
407
|
const {sessionId} = pendingReveal
|
|
@@ -390,30 +409,14 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
390
409
|
const row = rows.find(el => el.getAttribute('data-row-key') === `session:${sessionId}`
|
|
391
410
|
|| el.getAttribute('data-session-id') === sessionId)
|
|
392
411
|
if (row) {
|
|
393
|
-
if (pendingReveal.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
const animations = []
|
|
400
|
-
if (!reduced) for (let node = row; node && node !== root; node = node.parentElement) {
|
|
401
|
-
for (const animation of node.getAnimations?.() ?? []) {
|
|
402
|
-
const timing = animation.effect?.getComputedTiming?.()
|
|
403
|
-
if (animation.playState === 'running' && Number.isFinite(timing?.endTime)) animations.push(animation.finished)
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
if (animations.length) await Promise.race([
|
|
407
|
-
Promise.allSettled(animations),
|
|
408
|
-
new Promise(resolve => { revealSettleTimer = window.setTimeout(resolve, 300) }),
|
|
409
|
-
])
|
|
410
|
-
if (pendingReveal !== request || !row.isConnected) return
|
|
411
|
-
row.scrollIntoView?.({block:'nearest', inline:'nearest', behavior:reduced ? 'instant' : 'smooth'})
|
|
412
|
-
cancelReveal()
|
|
413
|
-
})
|
|
412
|
+
if (!pendingReveal.tracker) {
|
|
413
|
+
const request = pendingReveal
|
|
414
|
+
request.tracker = createSessionCardReveal({window, root, sessionId, onStart: () => window.clearTimeout(revealTimer), onDone: () => {
|
|
415
|
+
if (pendingReveal === request) finishReveal()
|
|
416
|
+
}})
|
|
417
|
+
}
|
|
414
418
|
return
|
|
415
419
|
}
|
|
416
|
-
if (++pendingReveal.attempts > 40) { cancelReveal(); return }
|
|
417
420
|
const items = snapshotOf(workspaces?.list)?.items ?? []
|
|
418
421
|
const owner = items.find(item => item.sessionIds?.includes(sessionId))
|
|
419
422
|
const key = owner?.workspaceId ?? ''
|
|
@@ -439,7 +442,6 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
439
442
|
closeNavigationPreview()
|
|
440
443
|
cancelPendingNavigation()
|
|
441
444
|
setSelectionTarget(sessionId)
|
|
442
|
-
revealSessionCard(sessionId)
|
|
443
445
|
const generation = navigationGeneration
|
|
444
446
|
const navigate = () => {
|
|
445
447
|
if (generation !== navigationGeneration || !adapterActive) return
|
|
@@ -462,11 +464,14 @@ export function createSidebarControlAdapter({ document, window, sessions, worksp
|
|
|
462
464
|
if (row) row.click?.()
|
|
463
465
|
else clearSelectionTarget()
|
|
464
466
|
}
|
|
465
|
-
//
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
navigationFrame = window.requestAnimationFrame(
|
|
467
|
+
// Finish sidebar motion first, then paint its final position before the host can block.
|
|
468
|
+
revealSessionCard(sessionId, () => {
|
|
469
|
+
if (generation !== navigationGeneration || !adapterActive) return
|
|
470
|
+
setSelectionTarget(sessionId)
|
|
471
|
+
navigationFrame = window.requestAnimationFrame(() => {
|
|
472
|
+
if (generation !== navigationGeneration) return
|
|
473
|
+
navigationFrame = window.requestAnimationFrame(navigate)
|
|
474
|
+
})
|
|
470
475
|
})
|
|
471
476
|
}
|
|
472
477
|
|
|
@@ -771,8 +771,10 @@ export function createThemeController({ document, window, cssText, service, curs
|
|
|
771
771
|
root.dataset.prtsBackgroundMode = preferences.backgroundMode
|
|
772
772
|
root.style.setProperty('--prts-background-shade', `${preferences.backgroundDim * 100}%`)
|
|
773
773
|
for (const name of ['sidebar', 'rail', 'baker']) root.style.setProperty(`--prts-${name}-opacity`, `${preferences[`${name}Opacity`] * 100}%`)
|
|
774
|
+
root.style.setProperty('--prts-right-sidebar-opacity', `${preferences.rightSidebarOpacity * 100}%`)
|
|
774
775
|
root.style.setProperty('--prts-classic-user-opacity', `${preferences.classicUserOpacity * 100}%`)
|
|
775
776
|
for (const name of ['sidebar', 'rail', 'baker']) root.style.setProperty(`--prts-${name}-blur`, `${preferences[`${name}Blur`]}px`)
|
|
777
|
+
root.style.setProperty('--prts-right-sidebar-blur', `${preferences.rightSidebarBlur}px`)
|
|
776
778
|
root.style.setProperty('--prts-classic-user-blur', `${preferences.classicUserBlur}px`)
|
|
777
779
|
refresh()
|
|
778
780
|
},
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { THEME_PACKAGE_LIMIT, validateThemePackage, describeThemePackage } from './theme-transfer.js'
|
|
1
|
+
import { THEME_PACKAGE_LIMIT, validateThemePackage, describeThemePackage, ignoredThemePackageFields } from './theme-transfer.js'
|
|
2
2
|
|
|
3
3
|
export function themeTransferMarkup() {
|
|
4
4
|
return `<dialog data-prts-transfer-dialog aria-labelledby="prts-transfer-title">
|
|
@@ -20,6 +20,7 @@ export function themeTransferMarkup() {
|
|
|
20
20
|
</div>
|
|
21
21
|
<div data-prts-transfer-preview hidden>
|
|
22
22
|
<strong>确认导入</strong><p data-prts-transfer-summary></p>
|
|
23
|
+
<p data-prts-transfer-compatibility hidden role="alert"></p>
|
|
23
24
|
<small>应用前会下载当前配置备份,请保留备份文件。</small>
|
|
24
25
|
<div data-prts-transfer-buttons><button type="button" data-prts-transfer-apply>确认导入</button><button type="button" data-prts-transfer-cancel>取消</button></div>
|
|
25
26
|
</div>
|
|
@@ -43,6 +44,7 @@ export function mountThemeTransfer({ panel, window, transfer }) {
|
|
|
43
44
|
const render = () => {
|
|
44
45
|
if (disposed) return
|
|
45
46
|
get('preview').hidden = !pending
|
|
47
|
+
if (!pending) get('compatibility').hidden = true
|
|
46
48
|
get('recovery').hidden = !transfer.hasBackup
|
|
47
49
|
for (const control of section.querySelectorAll('button,input')) control.disabled = busy
|
|
48
50
|
// Prevent local editors from racing a multi-service import; other clients use revision checks.
|
|
@@ -72,6 +74,12 @@ export function mountThemeTransfer({ panel, window, transfer }) {
|
|
|
72
74
|
if (disposed || selected !== generation) return ''
|
|
73
75
|
pending = value
|
|
74
76
|
get('summary').textContent = describeThemePackage(value)
|
|
77
|
+
const ignored = ignoredThemePackageFields(value)
|
|
78
|
+
const compatibility = get('compatibility')
|
|
79
|
+
compatibility.hidden = ignored.length === 0
|
|
80
|
+
compatibility.textContent = ignored.length
|
|
81
|
+
? `当前版本不支持以下配置,导入后将丢失:${ignored.join('、')}。请保留原文件。`
|
|
82
|
+
: ''
|
|
75
83
|
return '已读取文件,请确认导入内容。'
|
|
76
84
|
})
|
|
77
85
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { validBackgroundRegion } from '../shared/background-region.js'
|
|
1
2
|
import { DEFAULT_PREFERENCES, normalizePreferences } from './preferences.js'
|
|
2
3
|
|
|
3
4
|
export const THEME_PACKAGE_LIMIT = 160 * 1024 * 1024
|
|
@@ -6,6 +7,33 @@ const transferRoles = ['user', 'assistant']
|
|
|
6
7
|
const transferBuiltins = ['rhodes-island', 'lungmen', 'penguin-logistics', 'rhine-lab', 'reunion']
|
|
7
8
|
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
8
9
|
const encodedImage = (value, limit) => typeof value === 'string' && value.length > 0 && value.length <= Math.ceil(limit / 3) * 4 && value.length % 4 === 0 && /^[A-Za-z0-9+/]+={0,2}$/.test(value)
|
|
10
|
+
const unknownFields = (value, known, prefix) => record(value)
|
|
11
|
+
? Object.keys(value).filter(key => !known.includes(key)).map(key => `${prefix}.${key}`)
|
|
12
|
+
: []
|
|
13
|
+
|
|
14
|
+
export function ignoredThemePackageFields(value) {
|
|
15
|
+
const ignored = unknownFields(value.preferences, Object.keys(DEFAULT_PREFERENCES), '设置')
|
|
16
|
+
const assets = value.assets
|
|
17
|
+
if (!assets) return ignored
|
|
18
|
+
ignored.push(...unknownFields(assets, ['backgrounds', 'backgroundRegions', 'avatars', 'emblems'], '素材'))
|
|
19
|
+
ignored.push(...unknownFields(assets.backgrounds, transferSlots, '背景'))
|
|
20
|
+
ignored.push(...unknownFields(assets.backgroundRegions, transferSlots, '背景保护区域'))
|
|
21
|
+
for (const slot of transferSlots) ignored.push(...unknownFields(assets.backgroundRegions?.[slot], ['x', 'y', 'width', 'height'], `背景保护区域.${slot}`))
|
|
22
|
+
ignored.push(...unknownFields(assets.avatars, transferRoles, '头像'))
|
|
23
|
+
for (const role of transferRoles) {
|
|
24
|
+
ignored.push(...unknownFields(assets.avatars?.[role], ['image', 'crop'], `头像.${role}`))
|
|
25
|
+
ignored.push(...unknownFields(assets.avatars?.[role]?.crop, ['zoom', 'x', 'y'], `头像.${role}.crop`))
|
|
26
|
+
}
|
|
27
|
+
const emblems = assets.emblems
|
|
28
|
+
ignored.push(...unknownFields(emblems, ['items', 'hero', 'conversation'], '徽记'))
|
|
29
|
+
for (const [index, item] of (emblems?.items ?? []).entries()) {
|
|
30
|
+
ignored.push(...unknownFields(item, ['format', 'version', 'label', 'params', 'image'], `徽记.items[${index}]`))
|
|
31
|
+
ignored.push(...unknownFields(item?.params, ['mode', 'lineSource', 'cleanup', 'threshold', 'zoom', 'x', 'y'], `徽记.items[${index}].params`))
|
|
32
|
+
}
|
|
33
|
+
ignored.push(...unknownFields(emblems?.hero, ['kind', 'key', 'label', 'asset'], '徽记.hero'))
|
|
34
|
+
for (const [index, entry] of (emblems?.conversation ?? []).entries()) ignored.push(...unknownFields(entry, ['kind', 'key', 'label', 'asset'], `徽记.conversation[${index}]`))
|
|
35
|
+
return ignored
|
|
36
|
+
}
|
|
9
37
|
|
|
10
38
|
export function validateThemePackage(value) {
|
|
11
39
|
if (!record(value) || value.format !== 'prts-theme' || value.version !== 1) throw Error('不支持的主题文件格式或版本')
|
|
@@ -22,6 +50,7 @@ export function validateThemePackage(value) {
|
|
|
22
50
|
const a = value.assets
|
|
23
51
|
if (!record(a) || !record(a.backgrounds) || !record(a.avatars) || !record(a.emblems)) throw Error('主题素材数据不完整')
|
|
24
52
|
for (const slot of transferSlots) if (a.backgrounds[slot] !== null && !encodedImage(a.backgrounds[slot], 10 * 1024 * 1024)) throw Error('背景图片数据无效')
|
|
53
|
+
if (a.backgroundRegions !== undefined && (!record(a.backgroundRegions) || transferSlots.some(slot => !validBackgroundRegion(a.backgroundRegions[slot] ?? null)))) throw Error('背景保护区域无效')
|
|
25
54
|
for (const role of transferRoles) {
|
|
26
55
|
const avatar = a.avatars[role]
|
|
27
56
|
if (avatar === null) continue
|
|
@@ -72,10 +101,11 @@ export function createThemeTransfer({ window, getPreferences, savePreferences, c
|
|
|
72
101
|
const value = { format: 'prts-theme', version: 1, themeVersion, exportedAt: new Date().toISOString(), preferences: { ...getPreferences() } }
|
|
73
102
|
const states = {}
|
|
74
103
|
if (includeAssets) {
|
|
75
|
-
const backgrounds = {}, avatars = {}, items = [], ids = new Map()
|
|
104
|
+
const backgrounds = {}, backgroundRegions = {}, avatars = {}, items = [], ids = new Map()
|
|
76
105
|
states.backgrounds = await request('backgrounds/configuration')
|
|
77
106
|
for (const slot of transferSlots) {
|
|
78
107
|
const entry = states.backgrounds[slot]
|
|
108
|
+
backgroundRegions[slot] = entry?.region ?? null
|
|
79
109
|
backgrounds[slot] = null
|
|
80
110
|
if (entry) {
|
|
81
111
|
if (!/^[a-f0-9-]{36}$/.test(entry.id)) throw Error('背景引用无效')
|
|
@@ -102,7 +132,7 @@ export function createThemeTransfer({ window, getPreferences, savePreferences, c
|
|
|
102
132
|
}
|
|
103
133
|
const hero = await reference(states.emblems.configuration.hero), conversation = []
|
|
104
134
|
for (const entry of states.emblems.configuration.conversation) conversation.push(await reference(entry))
|
|
105
|
-
value.assets = { backgrounds, avatars, emblems: { items, hero, conversation } }
|
|
135
|
+
value.assets = { backgrounds, backgroundRegions, avatars, emblems: { items, hero, conversation } }
|
|
106
136
|
for (const kind of ['backgrounds', 'avatars', 'emblems']) {
|
|
107
137
|
if ((await request(kind + '/configuration')).revision !== states[kind].revision) throw Error('素材在导出期间发生变化,请重试')
|
|
108
138
|
}
|
|
@@ -146,7 +176,7 @@ export function createThemeTransfer({ window, getPreferences, savePreferences, c
|
|
|
146
176
|
for (const item of a.emblems.items) drafts.push(await request('emblems/import', item))
|
|
147
177
|
const entry = (ref, index) => ref === null ? null : ref.kind === 'builtin' ? ref : { kind: 'custom', key: 'import-' + index + '-' + drafts[ref.asset].draftId, label: ref.label, draftId: drafts[ref.asset].draftId }
|
|
148
178
|
const configuration = { version: 2, hero: entry(a.emblems.hero, 'hero'), conversation: a.emblems.conversation.map(entry) }
|
|
149
|
-
for (const slot of transferSlots) await write('backgrounds', { slot, image: a.backgrounds[slot] }, { slot, image: old.backgrounds[slot] })
|
|
179
|
+
for (const slot of transferSlots) await write('backgrounds', { slot, image: a.backgrounds[slot], region: a.backgroundRegions?.[slot] ?? null }, { slot, image: old.backgrounds[slot], region: old.backgroundRegions?.[slot] ?? null })
|
|
150
180
|
for (const role of transferRoles) await write('avatars', { role, image: a.avatars[role]?.image ?? null, crop: a.avatars[role]?.crop }, { role, image: old.avatars[role]?.image ?? null, crop: old.avatars[role]?.crop })
|
|
151
181
|
await write('emblems', { configuration }, { configuration: before.states.emblems.configuration })
|
|
152
182
|
}
|
|
@@ -2,6 +2,7 @@ import { mkdir, readFile, writeFile, rename, rm } from 'node:fs/promises'
|
|
|
2
2
|
import { join } from 'node:path'
|
|
3
3
|
import { randomUUID } from 'node:crypto'
|
|
4
4
|
import sharp from 'sharp'
|
|
5
|
+
import { validBackgroundRegion } from '../shared/background-region.js'
|
|
5
6
|
import { body, json, isBase64 } from './api.js'
|
|
6
7
|
import { fault } from './emblems.js'
|
|
7
8
|
|
|
@@ -26,8 +27,11 @@ export async function createBackgroundService(directory) {
|
|
|
26
27
|
const task = queue.then(async () => {
|
|
27
28
|
if (!BACKGROUND_SLOTS.includes(data?.slot)) throw fault(400, '背景区域无效')
|
|
28
29
|
if (data.revision !== state.revision) throw fault(409, '背景已在其他页面更新,请重新操作')
|
|
29
|
-
|
|
30
|
-
if (data.
|
|
30
|
+
const regionOnly = Object.hasOwn(data, 'region') && !Object.hasOwn(data, 'image')
|
|
31
|
+
if (Object.hasOwn(data, 'region') && !validBackgroundRegion(data.region)) throw fault(400, '背景保护区域无效')
|
|
32
|
+
if (regionOnly && !state[data.slot]) throw fault(400, '请先上传背景图片')
|
|
33
|
+
let entry = regionOnly ? { ...state[data.slot], region: data.region } : null, output
|
|
34
|
+
if (!regionOnly && data.image !== null) {
|
|
31
35
|
if (!isBase64(data.image) || data.image.length > 14 * 1024 * 1024) throw fault(400, '请上传不超过 10 MB 的图片')
|
|
32
36
|
const bytes = Buffer.from(data.image, 'base64')
|
|
33
37
|
if (!bytes.length || bytes.length > 10 * 1024 * 1024) throw fault(400, '请上传不超过 10 MB 的图片')
|
|
@@ -37,22 +41,22 @@ export async function createBackgroundService(directory) {
|
|
|
37
41
|
if (!['png', 'jpeg', 'webp', 'avif'].includes(metadata.format)) throw Error('format')
|
|
38
42
|
const result = await input.rotate().resize({ width: 3840, height: 2160, fit: 'inside', withoutEnlargement: true }).webp({ quality: 86 }).toBuffer({ resolveWithObject: true })
|
|
39
43
|
output = result.data
|
|
40
|
-
entry = { id: randomUUID(), width: result.info.width, height: result.info.height }
|
|
44
|
+
entry = { id: randomUUID(), width: result.info.width, height: result.info.height, region: data.region ?? null }
|
|
41
45
|
} catch { throw fault(400, '无法读取图片,请使用 PNG、JPEG、WebP 或 AVIF(最多 4000 万像素)') }
|
|
42
46
|
}
|
|
43
47
|
const next = { ...state, revision: state.revision + 1, [data.slot]: entry }
|
|
44
48
|
const temporary = configurationPath + '.' + randomUUID() + '.tmp'
|
|
45
49
|
try {
|
|
46
|
-
if (
|
|
50
|
+
if (output) await writeFile(join(directory, entry.id + '.webp'), output, { mode: 0o600 })
|
|
47
51
|
await writeFile(temporary, JSON.stringify(next), { mode: 0o600 })
|
|
48
52
|
await rename(temporary, configurationPath)
|
|
49
53
|
} catch (error) {
|
|
50
|
-
if (
|
|
54
|
+
if (output) await rm(join(directory, entry.id + '.webp'), { force: true }).catch(() => {})
|
|
51
55
|
throw error
|
|
52
56
|
} finally { await rm(temporary, { force: true }).catch(() => {}) }
|
|
53
57
|
const previous = state[data.slot]
|
|
54
58
|
state = next
|
|
55
|
-
if (previous) await rm(join(directory, previous.id + '.webp'), { force: true }).catch(() => {})
|
|
59
|
+
if (previous && previous.id !== entry?.id) await rm(join(directory, previous.id + '.webp'), { force: true }).catch(() => {})
|
|
56
60
|
for (const listener of listeners) { try { listener(state.revision) } catch {} }
|
|
57
61
|
return snapshot()
|
|
58
62
|
})
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Coordinates are fractions of the oriented, stored image.
|
|
2
|
+
export function validBackgroundRegion(region) {
|
|
3
|
+
return region === null || (region && typeof region === 'object' && !Array.isArray(region)
|
|
4
|
+
&& ['x','y','width','height'].every(key => Number.isFinite(region[key]))
|
|
5
|
+
&& region.x >= 0 && region.y >= 0 && region.width > 0 && region.height > 0
|
|
6
|
+
&& region.x + region.width <= 1.000001 && region.y + region.height <= 1.000001)
|
|
7
|
+
}
|
|
8
|
+
export function backgroundPlacement(imageWidth, imageHeight, width, height, region = null) {
|
|
9
|
+
if (![imageWidth,imageHeight,width,height].every(v => Number.isFinite(v) && v > 0)) return {x:0,y:0,width:0,height:0,clipped:false}
|
|
10
|
+
const scale = Math.max(width / imageWidth, height / imageHeight)
|
|
11
|
+
const w = imageWidth * scale, h = imageHeight * scale
|
|
12
|
+
const clamp = (v,a,b) => Math.max(a,Math.min(b,v))
|
|
13
|
+
const axis = (size, viewport, start, length, vertical = false) => {
|
|
14
|
+
const centered = (viewport-size)/2
|
|
15
|
+
if (!region) return centered
|
|
16
|
+
if (length*size > viewport) return clamp(vertical ? -start*size : viewport/2-(start+length/2)*size,viewport-size,0)
|
|
17
|
+
return clamp(centered, Math.max(viewport-size,-start*size), Math.min(0,viewport-(start+length)*size))
|
|
18
|
+
}
|
|
19
|
+
return {x:axis(w,width,region?.x,region?.width),y:axis(h,height,region?.y,region?.height,true),width:w,height:h,
|
|
20
|
+
clipped:Boolean(region && (region.width*w > width+.01 || region.height*h > height+.01))}
|
|
21
|
+
}
|
package/src/styles/cursor.css
CHANGED
|
@@ -33,3 +33,11 @@
|
|
|
33
33
|
html[data-dsh-prts] :is([data-prts-emblem-sort-active],[data-prts-emblem-sort-active] *) {
|
|
34
34
|
cursor: var(--prts-sort-cursor, grabbing) !important;
|
|
35
35
|
}
|
|
36
|
+
|
|
37
|
+
/* The crop surface uses one native cursor family, including during pointer capture. */
|
|
38
|
+
html[data-dsh-prts] [data-prts-region-stage] { cursor:default; }
|
|
39
|
+
html[data-dsh-prts] [data-prts-region-stage] [data-prts-region-box] { cursor:move; }
|
|
40
|
+
html[data-dsh-prts] [data-prts-region-stage] [data-prts-region-handle] { cursor:ns-resize!important; }
|
|
41
|
+
html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="e"],[data-prts-region-handle="w"]) { cursor:ew-resize!important; }
|
|
42
|
+
html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="nw"],[data-prts-region-handle="se"]) { cursor:nwse-resize!important; }
|
|
43
|
+
html[data-dsh-prts] [data-prts-region-stage] :is([data-prts-region-handle="ne"],[data-prts-region-handle="sw"]) { cursor:nesw-resize!important; }
|
|
@@ -1,18 +1,37 @@
|
|
|
1
1
|
/* Viewport-aligned wallpaper. */
|
|
2
2
|
html[data-dsh-prts] { --prts-background-wash: color-mix(in srgb, var(--prts-canvas) var(--prts-background-shade, 45%), transparent); }
|
|
3
3
|
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] :is([data-prts-region="frame"], [data-prts-nav-rail]) {
|
|
4
|
-
background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-full)
|
|
4
|
+
background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-full) var(--prts-protected-full-x, 50%) var(--prts-protected-full-y, 50%) / cover fixed;
|
|
5
5
|
}
|
|
6
6
|
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] :is([data-prts-region="sessions"], [data-prts-region="operation"]) { background: transparent; }
|
|
7
7
|
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-sidebar] {
|
|
8
|
-
--prts-wall-width:max(calc(var(--prts-sidebar-width,280px) + var(--prts-nav-width)),calc((100dvh - var(--prts-host-top-inset)) * var(--prts-sidebar-image-ratio,1)));
|
|
9
|
-
--prts-wall-x:calc((var(--prts-sidebar-width,280px) + var(--prts-nav-width) - var(--prts-wall-width)) / 2);
|
|
10
|
-
& :is([data-prts-region="sessions"],[data-prts-nav-rail]){background:linear-gradient(var(--prts-background-wash),var(--prts-background-wash)),var(--prts-background-sidebar) var(--prts-wall-x)
|
|
11
|
-
& [data-prts-region="sessions"]{background-position:center,calc(var(--prts-wall-x) - var(--prts-nav-width))
|
|
8
|
+
--prts-wall-width:var(--prts-protected-sidebar-width,max(calc(var(--prts-sidebar-width,280px) + var(--prts-nav-width)),calc((100dvh - var(--prts-host-top-inset)) * var(--prts-sidebar-image-ratio,1))));
|
|
9
|
+
--prts-wall-x:var(--prts-protected-sidebar-x,calc((var(--prts-sidebar-width,280px) + var(--prts-nav-width) - var(--prts-wall-width)) / 2));
|
|
10
|
+
& :is([data-prts-region="sessions"],[data-prts-nav-rail]){background:linear-gradient(var(--prts-background-wash),var(--prts-background-wash)),var(--prts-background-sidebar) var(--prts-wall-x) var(--prts-protected-sidebar-y, 50%) / var(--prts-wall-width) auto no-repeat}
|
|
11
|
+
& [data-prts-region="sessions"]{background-position:center,calc(var(--prts-wall-x) - var(--prts-nav-width)) var(--prts-protected-sidebar-y, 50%)}
|
|
12
12
|
}
|
|
13
13
|
html[data-dsh-prts][data-prts-rail-mode="overlay"]:is([data-prts-background-mode="split"][data-prts-background-sidebar],[data-prts-background-mode="full"][data-prts-background-full]) [data-prts-nav-rail] { background: transparent; }
|
|
14
|
-
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conversation]
|
|
15
|
-
background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-conversation)
|
|
14
|
+
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conversation] {
|
|
15
|
+
& [data-prts-region="frame"] { background: linear-gradient(var(--prts-background-wash), var(--prts-background-wash)), var(--prts-background-conversation) var(--prts-protected-conversation-x, 50%) var(--prts-protected-conversation-y, 50%) / cover no-repeat; }
|
|
16
|
+
& [data-prts-region="operation"] { background: transparent; }
|
|
17
|
+
&:not([data-prts-background-sidebar]) [data-prts-region="sessions"] { background: transparent; }
|
|
18
|
+
}
|
|
19
|
+
/* The native right dock paints an opaque tab host over the frame wallpaper. */
|
|
20
|
+
html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"][data-prts-background-conversation]) [data-prts-region="auxiliary"] { background: transparent; }
|
|
21
|
+
html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"][data-prts-background-conversation]) [data-prts-region="auxiliary"] :is([data-dockkit-host="dock"] > :first-child,[data-dockkit-empty]) {
|
|
22
|
+
position: relative;
|
|
23
|
+
isolation: isolate;
|
|
24
|
+
background: transparent;
|
|
25
|
+
}
|
|
26
|
+
html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"][data-prts-background-conversation]) [data-prts-region="auxiliary"] :is([data-dockkit-host="dock"] > :first-child,[data-dockkit-empty])::before {
|
|
27
|
+
content: "";
|
|
28
|
+
position: absolute;
|
|
29
|
+
inset: 0;
|
|
30
|
+
z-index: -1;
|
|
31
|
+
pointer-events: none;
|
|
32
|
+
background: color-mix(in srgb, var(--prts-panel) var(--prts-right-sidebar-opacity, 6%), transparent);
|
|
33
|
+
-webkit-backdrop-filter: blur(var(--prts-right-sidebar-blur, 8px)) saturate(1.15);
|
|
34
|
+
backdrop-filter: blur(var(--prts-right-sidebar-blur, 8px)) saturate(1.15);
|
|
16
35
|
}
|
|
17
36
|
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] [data-prts-ambient-layer],
|
|
18
37
|
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conversation] [data-prts-ambient-layer] { display: none !important; }
|
|
@@ -34,13 +53,23 @@ html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-conv
|
|
|
34
53
|
[data-prts-background-images] { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 14px; }
|
|
35
54
|
[data-prts-background-card="full"] { grid-column: 1 / -1; }
|
|
36
55
|
[data-prts-background-card-heading] { display: flex; justify-content: space-between; align-items: center; min-height: 30px; margin-bottom: 6px; font-size: 13px; }
|
|
37
|
-
[data-prts-background-
|
|
38
|
-
[data-prts-background-card="full"] [data-prts-background-
|
|
39
|
-
[data-prts-background-
|
|
40
|
-
[data-prts-background-
|
|
41
|
-
[data-prts-background-
|
|
42
|
-
[data-prts-background-
|
|
43
|
-
|
|
56
|
+
[data-prts-background-image-controls] { position:relative;display:grid;place-items:center;min-height:140px;border:1px dashed var(--prts-line);border-radius:8px;background:var(--prts-panel);overflow:hidden; }
|
|
57
|
+
[data-prts-background-card="full"] [data-prts-background-image-controls] { min-height:180px; }
|
|
58
|
+
[data-prts-background-image-controls][data-has-image] { border-style:solid; }
|
|
59
|
+
[data-prts-background-thumbnail] { position:absolute;inset:0;background:center / cover no-repeat;pointer-events:none; }
|
|
60
|
+
[data-prts-background-empty] {font-size:12px;color:var(--prts-muted);padding-bottom:40px}
|
|
61
|
+
[data-has-image] [data-prts-background-empty] {visibility:hidden}
|
|
62
|
+
/* One compact toolbar stays legible over either bright or dark photography. */
|
|
63
|
+
[data-prts-background-image-actions] {position:absolute;bottom:10px;right:10px;display:flex;align-items:center;gap:2px;padding:2px;border:1px solid #ffffff26;border-radius:7px;background:rgb(20 26 30 / 82%);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:0 2px 8px #0002;color:#edf2f3}
|
|
64
|
+
[data-prts-background-image-actions] button {display:inline-flex;align-items:center;justify-content:center;gap:5px;min-height:26px;padding:4px 7px;border:0;border-radius:5px;background:transparent;color:inherit;box-shadow:none;font:500 11px/1.2 var(--prts-body);white-space:nowrap;cursor:pointer;transition:background-color 120ms ease,color 120ms ease}
|
|
65
|
+
[data-prts-background-image-actions] button + button {position:relative}
|
|
66
|
+
[data-prts-background-image-actions] button + button::before {content:"";position:absolute;left:-1px;top:7px;bottom:7px;width:1px;background:#ffffff24}
|
|
67
|
+
[data-prts-background-image-actions] button[hidden] + button::before {display:none}
|
|
68
|
+
[data-prts-background-image-actions] svg {width:13px;height:13px;flex:none;fill:none;stroke:currentColor;stroke-width:1.4;stroke-linecap:round;stroke-linejoin:round;opacity:.8}
|
|
69
|
+
[data-prts-background-image-actions] button:hover {background:#ffffff18;color:#fff}
|
|
70
|
+
[data-prts-background-image-actions] button:active {background:#ffffff28}
|
|
71
|
+
[data-prts-background-image-actions] button:disabled {opacity:.45;cursor:wait}
|
|
72
|
+
[data-prts-background-settings] [data-prts-background-image-actions] button:focus-visible {outline:2px solid #b9e6ee;outline-offset:-2px}
|
|
44
73
|
[data-prts-background-remove] { padding: 4px 8px; background: transparent; color: var(--prts-muted); border: 0; border-radius: 4px; font-size: 12px; cursor: pointer; }
|
|
45
74
|
[data-prts-background-remove]:hover { color: var(--prts-red); background: color-mix(in srgb, var(--prts-red) 8%, transparent); }
|
|
46
75
|
[data-prts-background-shade] { display: grid; gap: 8px; font-size: 13px; padding-top: 14px; border-top: 1px solid var(--prts-line); }
|
|
@@ -64,8 +93,7 @@ html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full]
|
|
|
64
93
|
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-sidebar] [data-prts-region="sessions"] [data-slot="sidebar.workspaces"] span[class$="_fade"] { display: none !important; }
|
|
65
94
|
|
|
66
95
|
/* Filter the plate, not the ancestor of fixed controls. */
|
|
67
|
-
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] [data-prts-region="sessions"]::before
|
|
68
|
-
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-sidebar] [data-prts-region="sessions"]::before {
|
|
96
|
+
html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"]:is([data-prts-background-sidebar],[data-prts-background-conversation])) [data-prts-region="sessions"]::before {
|
|
69
97
|
content: "";
|
|
70
98
|
position: absolute;
|
|
71
99
|
inset: 0;
|
|
@@ -77,8 +105,7 @@ backdrop-filter: blur(var(--prts-sidebar-blur, 8px)) saturate(1.15);
|
|
|
77
105
|
box-shadow: inset -1px 0 color-mix(in srgb, var(--prts-ink) 10%, transparent), inset 0 1px rgb(255 255 255 / 8%);
|
|
78
106
|
}
|
|
79
107
|
/* Raise sidebar controls above glass. */
|
|
80
|
-
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] [data-prts-region="sessions"] [data-slot="sidebar"] > :first-child
|
|
81
|
-
html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-sidebar] [data-prts-region="sessions"] [data-slot="sidebar"] > :first-child {
|
|
108
|
+
html[data-dsh-prts]:is([data-prts-background-mode="full"][data-prts-background-full],[data-prts-background-mode="split"]:is([data-prts-background-sidebar],[data-prts-background-conversation])) [data-prts-region="sessions"] [data-slot="sidebar"] > :first-child {
|
|
82
109
|
position: relative;
|
|
83
110
|
z-index: 1;
|
|
84
111
|
}
|
|
@@ -263,13 +290,16 @@ border-radius: 6px;
|
|
|
263
290
|
background: var(--prts-background-full, linear-gradient(135deg, #8bb6c2, #c7b69c)) center / cover;
|
|
264
291
|
--prts-preview-surface: var(--prts-panel);
|
|
265
292
|
}
|
|
266
|
-
html[data-prts-background-mode="split"]
|
|
293
|
+
html[data-prts-background-mode="split"] [data-prts-material-preview="sidebarOpacity"] { background-image: var(--prts-background-sidebar, var(--prts-background-conversation, linear-gradient(135deg, #8bb6c2, #c7b69c))); }
|
|
294
|
+
html[data-prts-background-mode="split"] [data-prts-material-preview="rightSidebarOpacity"] { background-image: var(--prts-background-conversation, linear-gradient(135deg, #8bb6c2, #c7b69c)); }
|
|
295
|
+
html[data-prts-background-mode="split"] [data-prts-material-preview="railOpacity"] { background-image: var(--prts-background-sidebar, linear-gradient(135deg, #8bb6c2, #c7b69c)); }
|
|
267
296
|
html[data-prts-background-mode="split"] [data-prts-material-preview="bakerOpacity"] { background-image: var(--prts-background-conversation, linear-gradient(135deg, #8bb6c2, #c7b69c)); }
|
|
268
297
|
html[data-prts-background-mode="split"] [data-prts-material-preview="classicUserOpacity"] { background-image: var(--prts-background-conversation, linear-gradient(135deg, #8bb6c2, #c7b69c)); }
|
|
269
298
|
[data-prts-material-preview]::before { content: ""; position: absolute; inset: 0; background: var(--prts-background-wash); }
|
|
270
299
|
[data-prts-material-preview-surface] { position: absolute; inset: 0; display: grid; align-content: center; gap: 6px; padding: 10px; font-size: 10px; text-align: left; color: var(--prts-ink); background: color-mix(in srgb, var(--prts-preview-surface) var(--prts-sidebar-opacity, 6%), transparent); backdrop-filter: blur(var(--prts-sidebar-blur, 8px)) saturate(1.15); }
|
|
271
300
|
[data-prts-material-preview-surface] i { display: block; height: 8px; border-radius: 2px; background: color-mix(in srgb, var(--prts-ink) 18%, transparent); }
|
|
272
301
|
[data-prts-material-preview-surface] i:last-child { width: 65%; }
|
|
302
|
+
[data-prts-material-preview="rightSidebarOpacity"] [data-prts-material-preview-surface] { background: color-mix(in srgb, var(--prts-preview-surface) var(--prts-right-sidebar-opacity, 6%), transparent); backdrop-filter: blur(var(--prts-right-sidebar-blur, 8px)) saturate(1.15); }
|
|
273
303
|
[data-prts-material-preview="railOpacity"] [data-prts-material-preview-surface] { inset: 0 auto 0 0; width: 40%; padding: 5px; justify-items: center; gap: 2px; background: color-mix(in srgb, var(--prts-preview-surface) var(--prts-rail-opacity, 30%), transparent); backdrop-filter: blur(var(--prts-rail-blur, 12px)) saturate(1.15); }
|
|
274
304
|
[data-prts-material-preview="bakerOpacity"] [data-prts-material-preview-surface] { inset: 16px 10px; border-radius: 0 10px 10px 10px; background: rgb(69 70 68 / var(--prts-baker-opacity, 50%)); backdrop-filter: blur(var(--prts-baker-blur, 8px)) saturate(1.08); }
|
|
275
305
|
html[data-prts-scheme="light"] [data-prts-material-preview="bakerOpacity"] [data-prts-material-preview-surface] { background: rgb(223 227 223 / var(--prts-baker-opacity, 50%)); }
|
|
@@ -343,3 +373,49 @@ html[data-dsh-prts] [data-prts-background-hero-setting][hidden] { display:none;
|
|
|
343
373
|
|
|
344
374
|
/* Windows chrome must not clip a different canvas color into the conversation. */
|
|
345
375
|
html[data-dsh-prts][data-windows-titlebar] [data-prts-region="operation"] { border-radius: 0; }
|
|
376
|
+
|
|
377
|
+
[data-prts-region-editor] { width:min(1000px,calc(100vw - 32px));max-width:none;max-height:calc(100dvh - 32px);box-sizing:border-box;margin:auto;padding:24px;border:1px solid var(--prts-line);border-radius:14px;background:var(--prts-panel);color:var(--prts-ink);overflow:auto; }
|
|
378
|
+
[data-prts-region-editor]:not([open]) {display:none}
|
|
379
|
+
[data-prts-region-editor]::backdrop {background:#0009;backdrop-filter:blur(5px)}
|
|
380
|
+
[data-prts-region-editor] > header {display:flex;justify-content:space-between;align-items:start;gap:20px;margin-bottom:24px}
|
|
381
|
+
[data-prts-region-editor] > header strong {font-size:18px}
|
|
382
|
+
[data-prts-region-workspace] {display:grid;grid-template-columns:minmax(0,1.5fr) minmax(290px,1fr);gap:28px}
|
|
383
|
+
[data-prts-region-original],[data-prts-region-samples] {display:grid;align-content:start;gap:12px;min-width:0}
|
|
384
|
+
[data-prts-region-original] {padding:0 8px 0 24px}
|
|
385
|
+
[data-prts-region-stage] {margin:24px 0 12px}
|
|
386
|
+
[data-prts-region-preview-grid] {display:grid;grid-template-columns:minmax(0,1.5fr) minmax(0,1fr);gap:14px;align-items:start;margin-top:12px}
|
|
387
|
+
[data-prts-region-preview-grid] figure {display:grid;justify-items:center;align-content:start;gap:12px;margin:0;min-width:0}
|
|
388
|
+
[data-prts-region-preview-grid] figcaption {font-size:11px;color:var(--prts-muted)}
|
|
389
|
+
[data-prts-region-editor] > footer {display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:24px;padding-top:16px;border-top:1px solid var(--prts-line)}
|
|
390
|
+
[data-prts-region-editor] > footer > span {flex:1}
|
|
391
|
+
[data-prts-region-ruler] {position:absolute;pointer-events:none;color:var(--prts-muted);font:10px/1 var(--prts-mono)}
|
|
392
|
+
[data-prts-region-ruler="x"] {left:0;right:0;top:-20px;height:10px;border-bottom:1px solid var(--prts-line)}
|
|
393
|
+
[data-prts-region-ruler="y"] {top:0;bottom:0;left:-24px;width:16px;border-right:1px solid var(--prts-line)}
|
|
394
|
+
[data-prts-region-ruler] span,[data-prts-region-ruler] b {position:absolute;font-weight:400}
|
|
395
|
+
[data-prts-region-ruler="x"] span,[data-prts-region-ruler="x"] b {transform:translateX(-50%)}
|
|
396
|
+
[data-prts-region-ruler="y"] span,[data-prts-region-ruler="y"] b {transform:translateY(-50%)}
|
|
397
|
+
[data-prts-region-ruler] b {background:var(--prts-panel);color:var(--prts-cyan);z-index:1}
|
|
398
|
+
@media(max-width:760px){[data-prts-region-editor]{padding:18px;width:calc(100vw - 16px);max-height:calc(100dvh - 16px)}[data-prts-region-workspace]{grid-template-columns:1fr}[data-prts-region-preview-grid]{grid-template-columns:1fr 1fr}[data-prts-region-editor] > footer > span{display:none}}
|
|
399
|
+
[data-prts-region-editor] p { margin:0;font-size:12px;color:var(--prts-muted); }
|
|
400
|
+
[data-prts-region-stage] { position:relative;max-width:100%;justify-self:center;touch-action:none;user-select:none;cursor:default; }
|
|
401
|
+
[data-prts-region-stage] img { display:block;width:100%;height:auto;pointer-events:none; }
|
|
402
|
+
[data-prts-region-box] { position:absolute;box-sizing:border-box;border:2px solid #fff;outline:1px solid #111;box-shadow:0 0 0 1px #111;background:#ffffff08;cursor:move;touch-action:none; }
|
|
403
|
+
[data-prts-region-box] > span { position:absolute;top:4px;left:4px;background:#111b;color:#fff;font-size:10px;white-space:nowrap;pointer-events:none; }
|
|
404
|
+
[data-prts-region-handle] { position:absolute;width:16px!important;height:16px!important;min-width:0!important;padding:0!important;border:1px solid #111!important;background:#fff!important;border-radius:3px!important;touch-action:none; }
|
|
405
|
+
[data-prts-region-handle="nw"] {left:-8px;top:-8px;cursor:nwse-resize}
|
|
406
|
+
[data-prts-region-handle="ne"] {right:-8px;top:-8px;cursor:nesw-resize}
|
|
407
|
+
[data-prts-region-handle="sw"] {left:-8px;bottom:-8px;cursor:nesw-resize}
|
|
408
|
+
[data-prts-region-handle="se"] {right:-8px;bottom:-8px;cursor:nwse-resize}
|
|
409
|
+
[data-prts-region-preview] { position:relative;justify-self:center;max-width:100%;overflow:hidden;background-repeat:no-repeat;border:1px solid var(--prts-line);box-sizing:content-box; }
|
|
410
|
+
[data-prts-region-preview] i { position:absolute;border:1px solid #fff;box-shadow:0 0 0 1px #111;box-sizing:border-box;pointer-events:none; }
|
|
411
|
+
[data-prts-region-actions] { display:flex;gap:8px;flex-wrap:wrap; }
|
|
412
|
+
[data-prts-region-editor] button { padding:6px 10px;border:1px solid var(--prts-line);border-radius:5px;background:var(--prts-panel);color:var(--prts-ink);cursor:pointer;font-size:12px; }
|
|
413
|
+
[data-prts-region-save] { border-color:var(--prts-cyan)!important; }
|
|
414
|
+
[data-prts-region-status]:empty {display:none}
|
|
415
|
+
|
|
416
|
+
[data-prts-region-editor="sidebar"] [data-prts-region-preview-grid] {grid-template-columns:minmax(0,1fr) minmax(0,1.45fr)}
|
|
417
|
+
|
|
418
|
+
[data-prts-region-handle="n"] {left:50%;top:-8px;transform:translateX(-50%);cursor:ns-resize}
|
|
419
|
+
[data-prts-region-handle="s"] {left:50%;bottom:-8px;transform:translateX(-50%);cursor:ns-resize}
|
|
420
|
+
[data-prts-region-handle="w"] {left:-8px;top:50%;transform:translateY(-50%);cursor:ew-resize}
|
|
421
|
+
[data-prts-region-handle="e"] {right:-8px;top:50%;transform:translateY(-50%);cursor:ew-resize}
|
package/src/styles/prts.css
CHANGED
|
@@ -1907,12 +1907,12 @@ html[data-dsh-prts][data-prts-conversation-style="deck-chat"] {
|
|
|
1907
1907
|
& [data-message-role="assistant"]::after,
|
|
1908
1908
|
& :is([data-chat-flow-kind="user"], [data-submission-echo]:not([data-pending-steering])) [class$="_bubble"]::before,
|
|
1909
1909
|
& [data-message-role="user"]::before {
|
|
1910
|
-
content: ""; position: absolute; top: 0; left: -
|
|
1910
|
+
content: ""; position: absolute; top: 0; left: -10px; width: 10px; height: 18px; pointer-events: none;
|
|
1911
1911
|
background: var(--prts-bubble-surface, var(--prts-chat-user-surface));
|
|
1912
1912
|
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 18'%3E%3Cpath d='M0 0H10V18Q10 6 0 0Z'/%3E%3C/svg%3E") center / 100% 100% no-repeat;
|
|
1913
1913
|
}
|
|
1914
1914
|
& :is([data-chat-flow-kind="user"], [data-submission-echo]:not([data-pending-steering])) [class$="_bubble"]::before,
|
|
1915
|
-
& [data-message-role="user"]::before { left: auto; right: -
|
|
1915
|
+
& [data-message-role="user"]::before { left: auto; right: -10px; transform: scaleX(-1); }
|
|
1916
1916
|
}
|
|
1917
1917
|
|
|
1918
1918
|
/* The upstream component replaces only the composer's two background plates. */
|
|
@@ -393,6 +393,12 @@ html[data-dsh-prts] [data-prts-conversation-control="danger"] {
|
|
|
393
393
|
}
|
|
394
394
|
|
|
395
395
|
/* Empty session: emblem owns the upper field, composer anchors in the lower third. */
|
|
396
|
+
/* Reserve the native blank header before session restoration adds its controls.
|
|
397
|
+
Otherwise its height grows from 0 to 40px after the emblem has been positioned. */
|
|
398
|
+
html[data-dsh-prts] [data-phase="hero"] > [data-slot="conversation.header"] > header {
|
|
399
|
+
min-height: 40px;
|
|
400
|
+
}
|
|
401
|
+
|
|
396
402
|
html[data-dsh-prts] [data-prts-hero-active] {
|
|
397
403
|
--prts-hero-narrow-spacer: clamp(24px, 8dvh, 72px);
|
|
398
404
|
--prts-hero-wide-spacer: clamp(96px, 22dvh, 340px);
|
|
@@ -970,6 +970,7 @@ html[data-dsh-prts] [data-prts-transfer] {
|
|
|
970
970
|
& [data-prts-transfer-file-hint] { display:grid; place-items:center; flex:1; gap:6px; padding:12px; border:1px dashed var(--prts-line-strong); }
|
|
971
971
|
& [data-prts-transfer-file-hint] > span { font-size:24px; color:var(--prts-muted); }
|
|
972
972
|
& [data-prts-transfer-buttons] { display:flex; gap:8px; }
|
|
973
|
+
& [data-prts-transfer-compatibility] { margin:0; padding:10px 12px; border:1px solid var(--prts-yellow); overflow-wrap:anywhere; }
|
|
973
974
|
& [data-prts-transfer-recovery] { border:0; border-top:1px solid var(--prts-line); padding:16px 0 0; }
|
|
974
975
|
& [data-prts-transfer-status]:empty { display:none; }
|
|
975
976
|
}
|