cardbuddy 0.7.0 → 0.9.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/README.md +21 -0
- package/dist/cli.js +5 -0
- package/dist/preview-editor/assets/index-CrcrVs4F.css +1 -0
- package/dist/preview-editor/assets/index-DnpHHKWl.js +80 -0
- package/dist/preview-editor/index.html +2 -2
- package/dist/server-core/codeBridge.js +359 -0
- package/dist/server-core/index.js +27 -2
- package/dist/server-core/logStream.js +275 -0
- package/dist/standalone.js +33 -6
- package/dist/vendored/App.vue +28 -19
- package/dist/vendored/ansi.js +185 -0
- package/dist/vendored/api.js +164 -28
- package/dist/vendored/components/ApiCallPanel.vue +13 -1
- package/dist/vendored/components/ApiPayloadTree.vue +34 -1
- package/dist/vendored/components/CardGallery.vue +2 -2
- package/dist/vendored/components/ComponentPalette.vue +46 -39
- package/dist/vendored/components/ConfirmDialog.vue +2 -2
- package/dist/vendored/components/DebugPanel.vue +420 -69
- package/dist/vendored/components/EventEditor.vue +29 -3
- package/dist/vendored/components/LogValue.vue +2 -1
- package/dist/vendored/components/NodeInspector.vue +1 -1
- package/dist/vendored/components/PropControl.vue +6 -2
- package/dist/vendored/components/RemotePreview.vue +3 -1
- package/dist/vendored/components/StructureTree.vue +2 -0
- package/dist/vendored/components/setters/SetCode.vue +111 -3
- package/dist/vendored/styles/app.css +529 -96
- package/dist/vendored/styles/card-gallery.css +4 -11
- package/dist/vendored/styles/card-select.css +0 -15
- package/dist/vendored/styles/dropdown.css +3 -15
- package/dist/vendored/styles/json-tree.css +1 -1
- package/dist/vendored/styles/node-inspector.css +204 -131
- package/dist/vendored/styles/setters.css +91 -2
- package/dist/vendored/styles/structure-tree.css +27 -12
- package/package.json +1 -1
- package/dist/preview-editor/assets/index-BlscexDE.js +0 -79
- package/dist/preview-editor/assets/index-dlbSPb5W.css +0 -1
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
- 编辑即时(防抖)重建预览(emit apply),「应用」把改动暂存进内存工作副本(currentSchema),落盘由顶栏「保存到本地」负责;不再 emit write-back。
|
|
12
12
|
-->
|
|
13
13
|
<template>
|
|
14
|
-
<div class="ee-overlay"
|
|
14
|
+
<div class="ee-overlay">
|
|
15
15
|
<div class="ee-panel">
|
|
16
16
|
<div class="ee-head">
|
|
17
17
|
<span class="ee-title">
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
v-for="evt in eventModel.eventOptions"
|
|
30
30
|
:key="evt.eventKey"
|
|
31
31
|
class="ee-evt"
|
|
32
|
-
:class="{ 'is-on': isOn(evt) }"
|
|
32
|
+
:class="{ 'is-on': isOn(evt), 'is-sel': activeKey === evt.eventKey }"
|
|
33
33
|
@click="onSelectEvent(evt)"
|
|
34
34
|
>
|
|
35
35
|
<span class="ee-evt-name" :title="evt.eventDesc || evt.eventName || evt.eventKey">{{ evt.eventName || evt.eventKey }}</span>
|
|
@@ -43,6 +43,26 @@
|
|
|
43
43
|
><span class="ee-switch__dot"></span></button>
|
|
44
44
|
</div>
|
|
45
45
|
</div>
|
|
46
|
+
<!-- 帮助图标:与事件项左右靠边、垂直居中;点击弹出当前动作的使用说明 -->
|
|
47
|
+
<button
|
|
48
|
+
class="ee-help"
|
|
49
|
+
type="button"
|
|
50
|
+
:title="helpOpen ? '关闭使用说明' : '使用说明'"
|
|
51
|
+
@click.stop="helpOpen = !helpOpen"
|
|
52
|
+
>
|
|
53
|
+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
|
54
|
+
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
|
55
|
+
<circle cx="12" cy="12" r="10"></circle>
|
|
56
|
+
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path>
|
|
57
|
+
<line x1="12" y1="17" x2="12.01" y2="17"></line>
|
|
58
|
+
</svg>
|
|
59
|
+
</button>
|
|
60
|
+
<div v-if="helpOpen" class="ee-help__mask" @click="helpOpen = false"></div>
|
|
61
|
+
<Transition name="pop">
|
|
62
|
+
<div v-if="helpOpen" class="ee-help__pop" @click.stop>
|
|
63
|
+
<p class="ee-help__text">{{ helpText }}</p>
|
|
64
|
+
</div>
|
|
65
|
+
</Transition>
|
|
46
66
|
</aside>
|
|
47
67
|
|
|
48
68
|
<section class="ee-detail">
|
|
@@ -58,7 +78,6 @@
|
|
|
58
78
|
@change="onActionType(activeRow, $event)"
|
|
59
79
|
/>
|
|
60
80
|
</div>
|
|
61
|
-
<p v-if="actionDesc(activeRow.actionType)" class="ee-evt-desc">{{ actionDesc(activeRow.actionType) }}</p>
|
|
62
81
|
</div>
|
|
63
82
|
|
|
64
83
|
<div class="ee-detail-body">
|
|
@@ -108,6 +127,7 @@ export default {
|
|
|
108
127
|
return {
|
|
109
128
|
eventModel: null,
|
|
110
129
|
activeKey: '',
|
|
130
|
+
helpOpen: false,
|
|
111
131
|
_t: null,
|
|
112
132
|
_snapshot: null,
|
|
113
133
|
_dirty: false,
|
|
@@ -127,6 +147,11 @@ export default {
|
|
|
127
147
|
if (!this.eventModel || !this.activeKey) return null
|
|
128
148
|
return this.eventModel.rowForEvent(this.activeKey) || null
|
|
129
149
|
},
|
|
150
|
+
/** 帮助弹层文案:当前动作类型的使用说明;无选中动作时给通用引导 */
|
|
151
|
+
helpText() {
|
|
152
|
+
const d = this.activeRow ? this.actionDesc(this.activeRow.actionType) : ''
|
|
153
|
+
return d || '先在上方开启事件,再选择动作类型并填写参数;编辑即时预览,「应用」写入历史。'
|
|
154
|
+
},
|
|
130
155
|
},
|
|
131
156
|
watch: {
|
|
132
157
|
comp: {
|
|
@@ -141,6 +166,7 @@ export default {
|
|
|
141
166
|
const sn = this.schemaNode
|
|
142
167
|
this.eventModel = sn ? reactive(new EventEditModel(sn)) : null
|
|
143
168
|
this.activeKey = ''
|
|
169
|
+
this.helpOpen = false
|
|
144
170
|
this._snapshot = sn ? this.snapshotEventActions(sn) : null
|
|
145
171
|
this._dirty = false
|
|
146
172
|
this._written = false
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
-->
|
|
8
8
|
<template>
|
|
9
9
|
<span v-if="!complex" class="log__val" :class="'jt__val--' + typeClass">{{ display }}</span>
|
|
10
|
-
|
|
10
|
+
<!-- 日志里的对象/数组参数:根节点默认一行摘要、点击展开,保证日志行密度 -->
|
|
11
|
+
<div v-else class="log__json"><ApiPayloadTree :value="value" :max-depth="14" root-collapsed /></div>
|
|
11
12
|
</template>
|
|
12
13
|
|
|
13
14
|
<script>
|
|
@@ -233,9 +233,13 @@ export default {
|
|
|
233
233
|
.pc-body { line-height: 0; }
|
|
234
234
|
.pc-srcs { display: flex; gap: 4px; line-height: 1.5; }
|
|
235
235
|
.pc-src {
|
|
236
|
-
flex: 1; font-size: 12px; padding:
|
|
236
|
+
flex: 1; height: 24px; font-size: 12px; padding: 0 10px; border: 1px solid var(--border);
|
|
237
237
|
background: transparent; color: var(--text-dim); border-radius: 6px; cursor: pointer;
|
|
238
|
+
transition: background 0.12s, color 0.12s, border-color 0.12s;
|
|
238
239
|
}
|
|
239
|
-
.pc-src
|
|
240
|
+
.pc-src:hover { background: var(--bg-hover); color: var(--text); }
|
|
241
|
+
/* 选中态与分段控件 .st-seg 同语言:浅底 + 同色文字 + 发丝描边,不做实心填充 */
|
|
242
|
+
.pc-src.is-active { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }
|
|
243
|
+
.pc-src.is-active:hover { background: var(--accent-soft); color: var(--accent); }
|
|
240
244
|
.pc--readonly .pc-src { opacity: .6; cursor: not-allowed; }
|
|
241
245
|
</style>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<template>
|
|
2
|
+
<transition name="modal">
|
|
2
3
|
<div v-if="visible" class="rp-mask" @click.self="$emit('close')">
|
|
3
|
-
<div class="rp-card" role="dialog" aria-label="远端预览二维码">
|
|
4
|
+
<div class="rp-card modal-card" role="dialog" aria-label="远端预览二维码">
|
|
4
5
|
<div class="rp-head">
|
|
5
6
|
<span class="rp-title">远端预览(真机扫码)</span>
|
|
6
7
|
<button class="rp-x" type="button" @click="$emit('close')" aria-label="关闭">×</button>
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
</div>
|
|
27
28
|
</div>
|
|
28
29
|
</div>
|
|
30
|
+
</transition>
|
|
29
31
|
</template>
|
|
30
32
|
|
|
31
33
|
<script>
|
|
@@ -172,6 +172,7 @@
|
|
|
172
172
|
<line x1="12" y1="17" x2="12" y2="17.01" />
|
|
173
173
|
</svg>
|
|
174
174
|
</button>
|
|
175
|
+
<Transition name="pop">
|
|
175
176
|
<div v-if="helpOpen" class="st__help-pop" @click.stop>
|
|
176
177
|
<div class="st__help-pop-head">
|
|
177
178
|
<span class="st__help-pop-title">快捷键</span>
|
|
@@ -189,6 +190,7 @@
|
|
|
189
190
|
</li>
|
|
190
191
|
</ul>
|
|
191
192
|
</div>
|
|
193
|
+
</Transition>
|
|
192
194
|
</div>
|
|
193
195
|
</div>
|
|
194
196
|
</template>
|
|
@@ -12,9 +12,40 @@
|
|
|
12
12
|
-->
|
|
13
13
|
<template>
|
|
14
14
|
<div class="st-code">
|
|
15
|
-
<div
|
|
15
|
+
<div
|
|
16
|
+
ref="editor"
|
|
17
|
+
class="st-code__editor"
|
|
18
|
+
:class="{ 'is-disabled': disabled, 'is-bridged': !!bridge }"
|
|
19
|
+
></div>
|
|
20
|
+
<!-- 右上角语言标签:弱化的只读标识,不参与交互 -->
|
|
21
|
+
<span class="st-code__lang" aria-hidden="true">JS</span>
|
|
16
22
|
<div class="st-code__foot">
|
|
17
23
|
<span class="st-code__hint">{{ lines }} 行 · {{ chars }} 字符</span>
|
|
24
|
+
<!-- VSCode 代码桥:生成临时 js 用外部编辑器编辑,保存实时回写;断开即删临时文件 -->
|
|
25
|
+
<span class="st-code__foot-actions">
|
|
26
|
+
<span v-if="bridge" class="st-code__bridge-tag" :title="bridge.file">VSCode 同步中</span>
|
|
27
|
+
<button
|
|
28
|
+
type="button"
|
|
29
|
+
class="st-code__vscode"
|
|
30
|
+
:class="{ 'is-on': !!bridge }"
|
|
31
|
+
:title="
|
|
32
|
+
bridge
|
|
33
|
+
? '断开 VSCode 桥接并删除临时文件'
|
|
34
|
+
: '在 VSCode 中编辑:保存实时同步回编辑区,断开后清除临时文件'
|
|
35
|
+
"
|
|
36
|
+
:disabled="disabled"
|
|
37
|
+
@click="toggleBridge"
|
|
38
|
+
>
|
|
39
|
+
<!-- 图标左侧小字:未桥接时是动作说明;桥接后由状态徽标接替,避免两段文字挤一行 -->
|
|
40
|
+
<span v-if="!bridge" class="st-code__vscode-label">在编辑器中打开</span>
|
|
41
|
+
<svg viewBox="0 0 24 24" width="14" height="14" aria-hidden="true">
|
|
42
|
+
<path
|
|
43
|
+
fill="currentColor"
|
|
44
|
+
d="M23.15 2.587 18.21.21a1.494 1.494 0 0 0-1.705.29l-9.46 8.63-4.12-3.128a.999.999 0 0 0-1.276.057L.327 7.261A1 1 0 0 0 .326 8.74L3.899 12 .326 15.26a1 1 0 0 0 .001 1.479L1.65 17.94a.999.999 0 0 0 1.276.057l4.12-3.128 9.46 8.63a1.492 1.492 0 0 0 1.704.29l4.942-2.377A1.5 1.5 0 0 0 24 20.06V3.939a1.5 1.5 0 0 0-.85-1.352zm-5.146 14.861L10.826 12l7.178-5.448v10.896z"
|
|
45
|
+
/>
|
|
46
|
+
</svg>
|
|
47
|
+
</button>
|
|
48
|
+
</span>
|
|
18
49
|
</div>
|
|
19
50
|
</div>
|
|
20
51
|
</template>
|
|
@@ -24,13 +55,20 @@ import { CodeJar } from 'codejar'
|
|
|
24
55
|
import Prism from 'prismjs'
|
|
25
56
|
import 'prismjs/components/prism-javascript'
|
|
26
57
|
import { setterProps, setterEmits } from './props.js'
|
|
58
|
+
import {
|
|
59
|
+
codeBridgeOpen,
|
|
60
|
+
codeBridgeFetch,
|
|
61
|
+
codeBridgeHeartbeat,
|
|
62
|
+
codeBridgeClose,
|
|
63
|
+
subscribeCodeBridge,
|
|
64
|
+
} from '../../api.js'
|
|
27
65
|
|
|
28
66
|
export default {
|
|
29
67
|
name: 'SetCode',
|
|
30
68
|
props: setterProps,
|
|
31
69
|
emits: setterEmits,
|
|
32
70
|
data() {
|
|
33
|
-
return { code: '' }
|
|
71
|
+
return { code: '', bridge: null }
|
|
34
72
|
},
|
|
35
73
|
computed: {
|
|
36
74
|
codeKey() {
|
|
@@ -49,6 +87,8 @@ export default {
|
|
|
49
87
|
},
|
|
50
88
|
beforeUnmount() {
|
|
51
89
|
if (this._t) clearTimeout(this._t)
|
|
90
|
+
// 组件卸载(抽屉关闭 / 切节点 / key 重建)即结束桥接会话,服务端删除临时文件
|
|
91
|
+
this.teardownBridge(true)
|
|
52
92
|
if (this._jar && this._jar.destroy) this._jar.destroy()
|
|
53
93
|
},
|
|
54
94
|
methods: {
|
|
@@ -75,7 +115,7 @@ export default {
|
|
|
75
115
|
this._jar.onUpdate((code) => this.onCode(code))
|
|
76
116
|
},
|
|
77
117
|
onCode(code) {
|
|
78
|
-
if (this.disabled) return
|
|
118
|
+
if (this.disabled || this.bridge) return
|
|
79
119
|
this.code = code
|
|
80
120
|
// 保留 compiled 等未知键,只覆写 codeKey
|
|
81
121
|
const base = this.value && typeof this.value === 'object' ? Object.assign({}, this.value) : {}
|
|
@@ -85,6 +125,74 @@ export default {
|
|
|
85
125
|
if (this._t) clearTimeout(this._t)
|
|
86
126
|
this._t = setTimeout(() => this.$emit('change'), 400)
|
|
87
127
|
},
|
|
128
|
+
/* ---------------- VSCode 代码桥 ---------------- */
|
|
129
|
+
toggleBridge() {
|
|
130
|
+
if (this.bridge) this.teardownBridge(true)
|
|
131
|
+
else this.setupBridge()
|
|
132
|
+
},
|
|
133
|
+
async setupBridge() {
|
|
134
|
+
if (this.disabled || this.bridge) return
|
|
135
|
+
const label = this.itemInfo && this.itemInfo.itemKey ? String(this.itemInfo.itemKey) : ''
|
|
136
|
+
try {
|
|
137
|
+
const info = await codeBridgeOpen(this.code, label)
|
|
138
|
+
this.bridge = { id: info.id, file: info.file }
|
|
139
|
+
} catch (e) {
|
|
140
|
+
console.error('[VSCode 桥接建立失败]', e)
|
|
141
|
+
return
|
|
142
|
+
}
|
|
143
|
+
// 桥接期间 Web 编辑区只读:外部编辑器为唯一写入源,避免双向冲突
|
|
144
|
+
this.setEditorEditable(false)
|
|
145
|
+
this._hb = setInterval(() => {
|
|
146
|
+
if (!this.bridge) return
|
|
147
|
+
codeBridgeHeartbeat(this.bridge.id).catch(() => {})
|
|
148
|
+
}, 5000)
|
|
149
|
+
this._bridgeUnsub = subscribeCodeBridge((payload) => {
|
|
150
|
+
if (!this.bridge || !payload || payload.id !== this.bridge.id) return
|
|
151
|
+
// code-bridge-closed 带 reason(ttl / client):服务端已清理,本地同步收 UI
|
|
152
|
+
if (payload.reason) return this.teardownBridge(false)
|
|
153
|
+
this.pullBridgeCode()
|
|
154
|
+
})
|
|
155
|
+
},
|
|
156
|
+
async pullBridgeCode() {
|
|
157
|
+
if (!this.bridge) return
|
|
158
|
+
let r
|
|
159
|
+
try {
|
|
160
|
+
r = await codeBridgeFetch(this.bridge.id)
|
|
161
|
+
} catch (e) {
|
|
162
|
+
return // 会话已失效:等 closed 事件或 TTL 收尾
|
|
163
|
+
}
|
|
164
|
+
const code = String(r.code == null ? '' : r.code)
|
|
165
|
+
if (code === this.code) return
|
|
166
|
+
this.applyExternalCode(code)
|
|
167
|
+
},
|
|
168
|
+
applyExternalCode(code) {
|
|
169
|
+
this.code = code
|
|
170
|
+
// callOnUpdate=false:不触发 onUpdate,切断「回写 → input → 再写临时文件」回声环
|
|
171
|
+
if (this._jar) this._jar.updateCode(code, false)
|
|
172
|
+
const base = this.value && typeof this.value === 'object' ? Object.assign({}, this.value) : {}
|
|
173
|
+
base[this.codeKey] = code
|
|
174
|
+
this.$emit('input', base)
|
|
175
|
+
if (this._t) clearTimeout(this._t)
|
|
176
|
+
this._t = setTimeout(() => this.$emit('change'), 400)
|
|
177
|
+
},
|
|
178
|
+
teardownBridge(notifyServer) {
|
|
179
|
+
const b = this.bridge
|
|
180
|
+
this.bridge = null
|
|
181
|
+
if (this._hb) {
|
|
182
|
+
clearInterval(this._hb)
|
|
183
|
+
this._hb = null
|
|
184
|
+
}
|
|
185
|
+
if (this._bridgeUnsub) {
|
|
186
|
+
this._bridgeUnsub()
|
|
187
|
+
this._bridgeUnsub = null
|
|
188
|
+
}
|
|
189
|
+
this.setEditorEditable(true)
|
|
190
|
+
if (b && notifyServer) codeBridgeClose(b.id).catch(() => {})
|
|
191
|
+
},
|
|
192
|
+
setEditorEditable(on) {
|
|
193
|
+
const el = this.$refs.editor
|
|
194
|
+
if (el) el.setAttribute('contenteditable', on ? 'true' : 'false')
|
|
195
|
+
},
|
|
88
196
|
},
|
|
89
197
|
}
|
|
90
198
|
</script>
|