@ash-qw/dsh-theme-prts 2.3.1 → 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 +10 -0
- package/README.en.md +3 -3
- package/README.md +3 -3
- package/lib/client.js +79 -21
- package/package.json +1 -1
- package/src/client/background-layout.js +13 -7
- package/src/client/custom-background.js +2 -2
- package/src/client/host-geometry-adapter.js +6 -1
- 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/theme-controller-v2.js +2 -0
- package/src/client/theme-transfer-panel.js +9 -1
- package/src/client/theme-transfer.js +27 -0
- package/src/styles/custom-background.css +27 -7
- package/src/styles/theme-settings-workbench.css +1 -0
package/package.json
CHANGED
|
@@ -4,21 +4,22 @@ export function createBackgroundLayout({window,document}) {
|
|
|
4
4
|
const root=document.documentElement
|
|
5
5
|
const raf=window.requestAnimationFrame?.bind(window) ?? (fn=>window.setTimeout(fn,16))
|
|
6
6
|
const caf=window.cancelAnimationFrame?.bind(window) ?? window.clearTimeout.bind(window)
|
|
7
|
-
let state={}, frame, disposed=false, enabled=false, observing=false, operation, sessions
|
|
7
|
+
let state={}, frame, disposed=false, enabled=false, observing=false, hostFrame, operation, sessions
|
|
8
8
|
const set=(name,value)=>{if(root.style.getPropertyValue(name)!==value) root.style.setProperty(name,value)}
|
|
9
9
|
const update=()=>{
|
|
10
10
|
frame=undefined
|
|
11
11
|
if(disposed||!observing)return
|
|
12
|
+
const nextFrame=document.querySelector('[data-prts-region="frame"]')
|
|
12
13
|
const nextOperation=document.querySelector('[data-prts-region="operation"]')
|
|
13
14
|
const nextSessions=document.querySelector('[data-prts-region="sessions"]')
|
|
14
|
-
if(operation!==nextOperation || sessions!==nextSessions){
|
|
15
|
-
resize?.disconnect(); operation=nextOperation;sessions=nextSessions
|
|
16
|
-
for(const node of [operation,sessions])if(node)resize?.observe(node)
|
|
15
|
+
if(hostFrame!==nextFrame || operation!==nextOperation || sessions!==nextSessions){
|
|
16
|
+
resize?.disconnect(); hostFrame=nextFrame;operation=nextOperation;sessions=nextSessions
|
|
17
|
+
for(const node of [hostFrame||operation,sessions])if(node)resize?.observe(node)
|
|
17
18
|
}
|
|
18
19
|
const railWidth=parseFloat(window.getComputedStyle(root).getPropertyValue('--prts-nav-width'))||0
|
|
19
20
|
for(const slot of ['full','conversation','sidebar']){
|
|
20
21
|
const entry=state[slot]
|
|
21
|
-
const node=slot==='sidebar'?sessions:operation
|
|
22
|
+
const node=slot==='sidebar'?sessions:slot==='conversation'?(hostFrame||operation):operation
|
|
22
23
|
const width=slot==='full'?window.innerWidth:(node?.clientWidth||0)+(slot==='sidebar'?railWidth:0)
|
|
23
24
|
const height=slot==='full'?window.innerHeight:node?.clientHeight||0
|
|
24
25
|
const active=entry?.region && width>0 && height>0
|
|
@@ -35,14 +36,19 @@ export function createBackgroundLayout({window,document}) {
|
|
|
35
36
|
}
|
|
36
37
|
}
|
|
37
38
|
function schedule(){if(!disposed && observing && frame===undefined)frame=raf(update)}
|
|
38
|
-
|
|
39
|
+
// ResizeObserver runs after layout, before paint. Deferring again to rAF
|
|
40
|
+
// paints the new container with the previous image bounds for one frame.
|
|
41
|
+
const resize=window.ResizeObserver?new window.ResizeObserver(()=>{
|
|
42
|
+
if(frame!==undefined)caf(frame)
|
|
43
|
+
update()
|
|
44
|
+
}):null
|
|
39
45
|
const mutations=new window.MutationObserver(()=>{
|
|
40
46
|
if(!operation?.isConnected || !sessions?.isConnected)schedule()
|
|
41
47
|
})
|
|
42
48
|
const attributes=new window.MutationObserver(schedule)
|
|
43
49
|
function clear(){
|
|
44
50
|
caf(frame);frame=undefined;resize?.disconnect();mutations.disconnect();attributes.disconnect();window.removeEventListener('resize',schedule)
|
|
45
|
-
operation=undefined;sessions=undefined;observing=false
|
|
51
|
+
hostFrame=undefined;operation=undefined;sessions=undefined;observing=false
|
|
46
52
|
for(const slot of ['full','conversation','sidebar'])for(const axis of ['x','y','width'])root.style.removeProperty(`--prts-protected-${slot}-${axis}`)
|
|
47
53
|
}
|
|
48
54
|
function reconcile(){
|
|
@@ -2,7 +2,7 @@ import { createBackgroundLayout } from './background-layout.js'
|
|
|
2
2
|
import { mountBackgroundRegionEditor, backgroundRegionMarkup } from './background-region-editor.js'
|
|
3
3
|
import { opacityDialMarkup, mountOpacityDials } from './opacity-dial.js'
|
|
4
4
|
const BACKGROUND_ENDPOINT = '/prts-theme-api/backgrounds'
|
|
5
|
-
const backgroundLabels = { sidebar: '
|
|
5
|
+
const backgroundLabels = { sidebar: '左侧边栏', conversation: '会话正文', full: '完整背景' }
|
|
6
6
|
function backgroundImageUrl(entry) {
|
|
7
7
|
return /^[a-f0-9-]{36}$/.test(entry?.id ?? '') ? `${BACKGROUND_ENDPOINT}/image/${entry.id}` : null
|
|
8
8
|
}
|
|
@@ -77,7 +77,7 @@ export function backgroundMarkup(heroContrastControl = '') {
|
|
|
77
77
|
return `<section data-prts-background-settings>
|
|
78
78
|
<header><strong>会话背景</strong><small>分区使用不同图片,或让一张完整背景贯穿侧边栏与正文。</small></header>
|
|
79
79
|
<div data-prts-background-modes role="group" aria-label="背景模式">
|
|
80
|
-
${[['off','默认背景','使用主题原有底色'],['split','分区背景','
|
|
80
|
+
${[['off','默认背景','使用主题原有底色'],['split','分区背景','左侧边栏与正文独立配置'],['full','完整背景','一张图片贯穿整个界面']].map(([value,label,hint]) => `<button type="button" data-prts-setting-key="backgroundMode" data-prts-setting-value="${value}" aria-pressed="false"><span data-prts-background-diagram="${value}" aria-hidden="true"><i></i><i></i></span><span><strong>${label}</strong><small>${hint}</small></span></button>`).join('')}
|
|
81
81
|
</div>
|
|
82
82
|
<div data-prts-background-images hidden>${Object.entries(backgroundLabels).map(([slot,label]) => `<article data-prts-background-card="${slot}" hidden>
|
|
83
83
|
<div data-prts-background-card-heading><strong>${label}</strong><button type="button" data-prts-background-remove="${slot}" aria-label="移除${label}图片" hidden>移除</button></div>
|
|
@@ -78,7 +78,12 @@ export function createHostGeometryAdapter({ document, window }) {
|
|
|
78
78
|
shouldMeasure = true
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
|
-
|
|
81
|
+
// Keep shared wallpaper geometry in the same paint as the resized sidebar.
|
|
82
|
+
if (shouldMeasure || !entries.length) {
|
|
83
|
+
if (frameRequest !== undefined) window?.cancelAnimationFrame?.(frameRequest)
|
|
84
|
+
frameRequest = undefined
|
|
85
|
+
measure()
|
|
86
|
+
}
|
|
82
87
|
}
|
|
83
88
|
|
|
84
89
|
function stopObservation() {
|
|
@@ -28,6 +28,13 @@ export function createNativePreferences({ storage, onChange }) {
|
|
|
28
28
|
migrationAttempted = true
|
|
29
29
|
pending = { ...legacy, ...pending }
|
|
30
30
|
}
|
|
31
|
+
// Materialize the old shared sidebar values once, so later left-side edits
|
|
32
|
+
// cannot change a right-side preference that was never written by the Host.
|
|
33
|
+
if (s.value?.preferencesInitialized && writable(s)) {
|
|
34
|
+
for (const key of ['rightSidebarOpacity', 'rightSidebarBlur']) {
|
|
35
|
+
if (!Object.hasOwn(s.value, key) && !Object.hasOwn(pending, key) && !Object.hasOwn(inFlight, key) && !Object.hasOwn(failed, key)) pending[key] = host[key]
|
|
36
|
+
}
|
|
37
|
+
}
|
|
31
38
|
current = normalizePreferences({ ...host, ...inFlight, ...pending })
|
|
32
39
|
if (s.value?.preferencesInitialized) {
|
|
33
40
|
legacy = null
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// Ring geometry adapted from React Bits CometDial (David Haz).
|
|
2
2
|
// See src/vendor/react-bits/LICENSE.md and THIRD_PARTY_NOTICES.md.
|
|
3
3
|
export const BACKGROUND_OPACITY_CONTROLS = Object.freeze([
|
|
4
|
-
{ key: 'sidebarOpacity', label: '
|
|
4
|
+
{ key: 'sidebarOpacity', label: '左侧边栏', property: '--prts-sidebar-opacity', value: 94, blurKey: 'sidebarBlur', blurProperty: '--prts-sidebar-blur', blurDefault: 8 },
|
|
5
|
+
{ key: 'rightSidebarOpacity', label: '右侧边栏', property: '--prts-right-sidebar-opacity', value: 94, blurKey: 'rightSidebarBlur', blurProperty: '--prts-right-sidebar-blur', blurDefault: 8 },
|
|
5
6
|
{ key: 'railOpacity', label: '设置侧栏', property: '--prts-rail-opacity', value: 70, blurKey: 'railBlur', blurProperty: '--prts-rail-blur', blurDefault: 12 },
|
|
6
7
|
{ key: 'bakerOpacity', label: 'Baker 气泡', property: '--prts-baker-opacity', value: 50, blurKey: 'bakerBlur', blurProperty: '--prts-baker-blur', blurDefault: 8, theme: 'deck-chat' },
|
|
7
8
|
{ key: 'classicUserOpacity', label: '经典用户消息', property: '--prts-classic-user-opacity', value: 50, blurKey: 'classicUserBlur', blurProperty: '--prts-classic-user-blur', blurDefault: 8, theme: 'native' },
|
|
@@ -16,7 +17,8 @@ export function opacityDialMarkup() {
|
|
|
16
17
|
const controls = BACKGROUND_OPACITY_CONTROLS.map(({key,label,value,blurDefault}) => {
|
|
17
18
|
const sample = key === 'bakerOpacity' ? '<span>这是一条回复</span><i></i>'
|
|
18
19
|
: key === 'classicUserOpacity' ? '<span>这是我发送的消息</span><i></i>'
|
|
19
|
-
: key === 'railOpacity' ? '<b>◇</b><b>☼</b><b>⚙</b>'
|
|
20
|
+
: key === 'railOpacity' ? '<b>◇</b><b>☼</b><b>⚙</b>'
|
|
21
|
+
: key === 'rightSidebarOpacity' ? '<span>侧边栏</span><i></i><i></i>' : '<span>工作区</span><i></i><i></i>'
|
|
20
22
|
return `<div data-prts-opacity-control="${key}">
|
|
21
23
|
<div data-prts-opacity-heading><strong>${label}</strong><button type="button" data-prts-opacity-reset data-prts-opacity-reset-tooltip="恢复到 ${value}%" aria-label="恢复${label}透明度到 ${value}%"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 10a7.5 7.5 0 1 1 .7 5.2M4.5 4.5V10H10"/></svg></button></div>
|
|
22
24
|
<div data-prts-opacity-dial><svg viewBox="0 0 200 200" role="slider" tabindex="0" aria-label="${label}透明度" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${value}" aria-valuetext="${value}%"><circle data-dial-focus cx="100" cy="100" r="97"/><path data-dial-track d="${dialArc(110,430)}"/><path data-dial-lit/><path data-dial-tail/><circle data-dial-head r="8"/></svg><label data-prts-opacity-number><input type="number" min="0" max="100" step="1" value="${value}" aria-label="${label}透明度百分比"><span>%</span></label></div>
|
|
@@ -24,8 +26,8 @@ export function opacityDialMarkup() {
|
|
|
24
26
|
<div data-prts-blur-control hidden><label><span>模糊</span><input type="range" min="0" max="32" step="1" value="${blurDefault}" aria-label="${label}模糊度"></label><output>${blurDefault}px</output><button type="button" data-prts-blur-reset title="恢复到 ${blurDefault}px" aria-label="恢复${label}模糊度到 ${blurDefault}px"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4.5 10a7.5 7.5 0 1 1 .7 5.2M4.5 4.5V10H10"/></svg></button></div>
|
|
25
27
|
<small data-prts-opacity-note></small>
|
|
26
28
|
</div>`
|
|
27
|
-
})
|
|
28
|
-
return `<fieldset data-prts-opacity-settings hidden><legend>材质透明度与模糊度</legend><small>透明度数值越高越透明;模糊度以像素计。调整时预览,松手后保存。</small><div data-prts-opacity-grid>${controls}</div></fieldset>`
|
|
29
|
+
})
|
|
30
|
+
return `<fieldset data-prts-opacity-settings hidden><legend>材质透明度与模糊度</legend><small>透明度数值越高越透明;模糊度以像素计。调整时预览,松手后保存。</small><div data-prts-opacity-grid>${controls.join('')}</div></fieldset>`
|
|
29
31
|
}
|
|
30
32
|
export function mountOpacityDials({ section, window, onChange }) {
|
|
31
33
|
const root = section.ownerDocument.documentElement
|
|
@@ -108,14 +110,14 @@ export function mountOpacityDials({ section, window, onChange }) {
|
|
|
108
110
|
for(const [el,type,fn] of bindings)el.addEventListener(type,fn)
|
|
109
111
|
paint(value)
|
|
110
112
|
return {
|
|
111
|
-
update(preferences, active) {
|
|
113
|
+
update(preferences, active, inactiveNote) {
|
|
112
114
|
container.hidden = Boolean(definition.theme && preferences.conversationStyle !== definition.theme)
|
|
113
115
|
saved = 100 - Math.round((preferences[definition.key] ?? (100 - definition.value) / 100) * 100)
|
|
114
116
|
if (!grip && section.ownerDocument.activeElement !== input) paint(saved)
|
|
115
117
|
savedBlur = preferences[definition.blurKey] ?? definition.blurDefault
|
|
116
118
|
if (section.ownerDocument.activeElement !== blurInput) paintBlur(savedBlur)
|
|
117
119
|
blurControl.hidden = !active
|
|
118
|
-
container.querySelector('[data-prts-opacity-note]').textContent = active ? '' :
|
|
120
|
+
container.querySelector('[data-prts-opacity-note]').textContent = active ? '' : inactiveNote
|
|
119
121
|
},
|
|
120
122
|
dispose() { if(grip)finish({},true);window.clearTimeout(timer);for(const [el,type,fn] of bindings)el.removeEventListener(type,fn) },
|
|
121
123
|
}
|
|
@@ -127,10 +129,13 @@ export function mountOpacityDials({ section, window, onChange }) {
|
|
|
127
129
|
section.querySelector('[data-prts-opacity-settings]').hidden = preferences.backgroundMode === 'off'
|
|
128
130
|
const conversationImage = full || (split && Boolean(state.conversation))
|
|
129
131
|
const sidebarImage = full || (split && Boolean(state.sidebar))
|
|
130
|
-
const active = [sidebarImage, sidebarImage,
|
|
132
|
+
const active = [sidebarImage || conversationImage, conversationImage, sidebarImage,
|
|
131
133
|
conversationImage && preferences.conversationStyle === 'deck-chat',
|
|
132
134
|
conversationImage && preferences.conversationStyle === 'native']
|
|
133
|
-
|
|
135
|
+
const missingImage = full || !split ? '上传完整背景图片后生效' : null
|
|
136
|
+
const inactiveNotes = [missingImage || '上传左侧边栏或正文图片后生效', missingImage || '上传正文图片后生效',
|
|
137
|
+
missingImage || '上传左侧边栏图片后生效', missingImage || '上传正文图片后生效', missingImage || '上传正文图片后生效']
|
|
138
|
+
controls.forEach((control,i)=>control.update(preferences,active[i],inactiveNotes[i]))
|
|
134
139
|
},
|
|
135
140
|
dispose() {for(const control of controls)control.dispose()},
|
|
136
141
|
}
|
|
@@ -29,10 +29,12 @@ export const DEFAULT_PREFERENCES = Object.freeze({
|
|
|
29
29
|
backgroundDim: .45,
|
|
30
30
|
heroContrast: true,
|
|
31
31
|
sidebarOpacity: .06,
|
|
32
|
+
rightSidebarOpacity: .06,
|
|
32
33
|
railOpacity: .3,
|
|
33
34
|
bakerOpacity: .5,
|
|
34
35
|
classicUserOpacity: .5,
|
|
35
36
|
sidebarBlur: 8,
|
|
37
|
+
rightSidebarBlur: 8,
|
|
36
38
|
railBlur: 12,
|
|
37
39
|
bakerBlur: 8,
|
|
38
40
|
classicUserBlur: 8,
|
|
@@ -100,7 +102,7 @@ export const PARTICLE_DETAIL_LEVELS = Object.freeze({
|
|
|
100
102
|
precise: Object.freeze({ conversationParticleDensity: 'dense', heroParticleDensity: 'ultra' }),
|
|
101
103
|
})
|
|
102
104
|
export const PREFERENCE_GROUPS = Object.freeze({
|
|
103
|
-
background: Object.freeze(['texture', 'backgroundMode', 'backgroundDim', 'heroContrast', 'sidebarOpacity', 'railOpacity', 'bakerOpacity', 'classicUserOpacity', 'sidebarBlur', 'railBlur', 'bakerBlur', 'classicUserBlur']),
|
|
105
|
+
background: Object.freeze(['texture', 'backgroundMode', 'backgroundDim', 'heroContrast', 'sidebarOpacity', 'rightSidebarOpacity', 'railOpacity', 'bakerOpacity', 'classicUserOpacity', 'sidebarBlur', 'rightSidebarBlur', 'railBlur', 'bakerBlur', 'classicUserBlur']),
|
|
104
106
|
particles: Object.freeze(['conversationParticleDensity', 'heroParticleDensity', 'particleTraversalSpeed']),
|
|
105
107
|
material: Object.freeze(['glass', 'composerGlass']),
|
|
106
108
|
accessibility: Object.freeze(['motion', 'bootAnimation', 'cursor']),
|
|
@@ -214,10 +216,12 @@ export function normalizePreferences(value) {
|
|
|
214
216
|
bakerLoading: input.bakerLoading === true,
|
|
215
217
|
backgroundMode: ['off', 'split', 'full'].includes(input.backgroundMode) ? input.backgroundMode : 'off',
|
|
216
218
|
sidebarOpacity: typeof input.sidebarOpacity === 'number' && Number.isFinite(input.sidebarOpacity) ? Math.min(1, Math.max(0, input.sidebarOpacity)) : .06,
|
|
219
|
+
rightSidebarOpacity: typeof input.rightSidebarOpacity === 'number' && Number.isFinite(input.rightSidebarOpacity) ? Math.min(1, Math.max(0, input.rightSidebarOpacity)) : typeof input.sidebarOpacity === 'number' && Number.isFinite(input.sidebarOpacity) ? Math.min(1, Math.max(0, input.sidebarOpacity)) : .06,
|
|
217
220
|
railOpacity: typeof input.railOpacity === 'number' && Number.isFinite(input.railOpacity) ? Math.min(1, Math.max(0, input.railOpacity)) : .3,
|
|
218
221
|
bakerOpacity: typeof input.bakerOpacity === 'number' && Number.isFinite(input.bakerOpacity) ? Math.min(1, Math.max(0, input.bakerOpacity)) : .5,
|
|
219
222
|
classicUserOpacity: typeof input.classicUserOpacity === 'number' && Number.isFinite(input.classicUserOpacity) ? Math.min(1, Math.max(0, input.classicUserOpacity)) : .5,
|
|
220
223
|
sidebarBlur: materialBlurOr(input.sidebarBlur, DEFAULT_PREFERENCES.sidebarBlur),
|
|
224
|
+
rightSidebarBlur: materialBlurOr(input.rightSidebarBlur, materialBlurOr(input.sidebarBlur, DEFAULT_PREFERENCES.sidebarBlur)),
|
|
221
225
|
railBlur: materialBlurOr(input.railBlur, DEFAULT_PREFERENCES.railBlur),
|
|
222
226
|
bakerBlur: materialBlurOr(input.bakerBlur, DEFAULT_PREFERENCES.bakerBlur),
|
|
223
227
|
classicUserBlur: materialBlurOr(input.classicUserBlur, DEFAULT_PREFERENCES.classicUserBlur),
|
|
@@ -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
|
}
|
|
@@ -7,6 +7,33 @@ const transferRoles = ['user', 'assistant']
|
|
|
7
7
|
const transferBuiltins = ['rhodes-island', 'lungmen', 'penguin-logistics', 'rhine-lab', 'reunion']
|
|
8
8
|
const record = value => value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
9
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
|
+
}
|
|
10
37
|
|
|
11
38
|
export function validateThemePackage(value) {
|
|
12
39
|
if (!record(value) || value.format !== 'prts-theme' || value.version !== 1) throw Error('不支持的主题文件格式或版本')
|
|
@@ -11,8 +11,27 @@ html[data-dsh-prts][data-prts-background-mode="split"][data-prts-background-side
|
|
|
11
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) var(--prts-protected-conversation-x, 50%) var(--prts-protected-conversation-y, 50%) / cover no-repeat;
|
|
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; }
|
|
@@ -74,8 +93,7 @@ html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full]
|
|
|
74
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; }
|
|
75
94
|
|
|
76
95
|
/* Filter the plate, not the ancestor of fixed controls. */
|
|
77
|
-
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] [data-prts-region="sessions"]::before
|
|
78
|
-
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 {
|
|
79
97
|
content: "";
|
|
80
98
|
position: absolute;
|
|
81
99
|
inset: 0;
|
|
@@ -87,8 +105,7 @@ backdrop-filter: blur(var(--prts-sidebar-blur, 8px)) saturate(1.15);
|
|
|
87
105
|
box-shadow: inset -1px 0 color-mix(in srgb, var(--prts-ink) 10%, transparent), inset 0 1px rgb(255 255 255 / 8%);
|
|
88
106
|
}
|
|
89
107
|
/* Raise sidebar controls above glass. */
|
|
90
|
-
html[data-dsh-prts][data-prts-background-mode="full"][data-prts-background-full] [data-prts-region="sessions"] [data-slot="sidebar"] > :first-child
|
|
91
|
-
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 {
|
|
92
109
|
position: relative;
|
|
93
110
|
z-index: 1;
|
|
94
111
|
}
|
|
@@ -273,13 +290,16 @@ border-radius: 6px;
|
|
|
273
290
|
background: var(--prts-background-full, linear-gradient(135deg, #8bb6c2, #c7b69c)) center / cover;
|
|
274
291
|
--prts-preview-surface: var(--prts-panel);
|
|
275
292
|
}
|
|
276
|
-
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)); }
|
|
277
296
|
html[data-prts-background-mode="split"] [data-prts-material-preview="bakerOpacity"] { background-image: var(--prts-background-conversation, linear-gradient(135deg, #8bb6c2, #c7b69c)); }
|
|
278
297
|
html[data-prts-background-mode="split"] [data-prts-material-preview="classicUserOpacity"] { background-image: var(--prts-background-conversation, linear-gradient(135deg, #8bb6c2, #c7b69c)); }
|
|
279
298
|
[data-prts-material-preview]::before { content: ""; position: absolute; inset: 0; background: var(--prts-background-wash); }
|
|
280
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); }
|
|
281
300
|
[data-prts-material-preview-surface] i { display: block; height: 8px; border-radius: 2px; background: color-mix(in srgb, var(--prts-ink) 18%, transparent); }
|
|
282
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); }
|
|
283
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); }
|
|
284
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); }
|
|
285
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%)); }
|
|
@@ -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
|
}
|