From c62328c4198bfa17a9c85a859024e968f3c4e03f Mon Sep 17 00:00:00 2001 From: 7015725 Date: Sat, 23 May 2026 02:12:28 +0800 Subject: [PATCH] refactor: polish schema editor blueprint UI --- code/th_14_schema_editor.js | 619 ++++++++++++++++++++---------------- manifest.json | 6 +- manifest.sig | 2 +- 3 files changed, 346 insertions(+), 281 deletions(-) diff --git a/code/th_14_schema_editor.js b/code/th_14_schema_editor.js index dc3fa8b..d64b967 100644 --- a/code/th_14_schema_editor.js +++ b/code/th_14_schema_editor.js @@ -1,327 +1,392 @@ -// ToolHub - Schema 编辑器模块 -// 依赖:th_14_panels.js 中的设置页基础 UI / showPanelAvoidBall,th_05_persistence.js 的 ConfigManager。 -// 注意:加载顺序必须位于 th_14_panels.js 之后、th_15_extra.js 之前。 +// ToolHub - 高级蓝图编辑器模块 +// 依赖:th_14_panels.js 的设置页主题/基础 UI,th_05_persistence.js 的 ConfigManager。 +// 加载顺序:th_14_panels.js 之后,th_15_extra.js 之前。 -// =======================【Schema 编辑面板】====================== FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() { var self = this; - if (this.state.editingSchemaIndex === undefined) { - this.state.editingSchemaIndex = null; - } + if (this.state.editingSchemaIndex === undefined) this.state.editingSchemaIndex = null; if (!this.state.keepSchemaEditorState || !this.state.tempSchema) { - var current = ConfigManager.loadSchema(); - this.state.tempSchema = JSON.parse(JSON.stringify(current)); + var current = ConfigManager.loadSchema(); + this.state.tempSchema = JSON.parse(JSON.stringify(current)); } this.state.keepSchemaEditorState = false; - var schema = this.state.tempSchema; - var isEditing = (this.state.editingSchemaIndex !== null); + var schema = this.state.tempSchema || []; + var isEditing = (this.state.editingSchemaIndex !== null && this.state.editingSchemaIndex !== undefined); var isDark = this.isDarkTheme(); var C = this.ui.colors; + var T = this.getAnimalIslandTheme ? this.getAnimalIslandTheme() : null; + try { if (this.applySettingsTheme) this.applySettingsTheme(T, isDark, C, this.state.pendingUserCfg || this.config); } catch(eTheme) {} - var bgColor = isDark ? C.bgDark : C.bgLight; - var cardColor = isDark ? C.cardDark : C.cardLight; - var textColor = isDark ? C.textPriDark : C.textPriLight; - var subTextColor = isDark ? C.textSecDark : C.textSecLight; - var dividerColor = isDark ? C.dividerDark : C.dividerLight; + var bgColor = T && T.bg ? T.bg : (isDark ? C.bgDark : C.bgLight); + var cardColor = T && T.card ? T.card : (isDark ? C.cardDark : C.cardLight); + var textColor = T && T.text ? T.text : (isDark ? C.textPriDark : C.textPriLight); + var subTextColor = T && T.sub ? T.sub : (isDark ? C.textSecDark : C.textSecLight); + var primaryColor = T && T.primary ? T.primary : C.primary; + var primaryDeep = T && T.primaryDeep ? T.primaryDeep : C.primary; + var primarySoft = T && T.primarySoft ? T.primarySoft : self.withAlpha(primaryColor, isDark ? 0.22 : 0.12); + var strokeColor = T && T.stroke ? T.stroke : (isDark ? C.dividerDark : C.dividerLight); var inputBgColor = isDark ? C.inputBgDark : C.inputBgLight; + var dangerColor = C.danger || C.error || 0xffd32f2f; - var panel = this.ui.createStyledPanel(this, 16); + function lpFullWrap() { + return new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT); + } - // --- Header --- - var header = this.ui.createStyledHeader(this, 12); + function addWithMargins(parent, view, l, t, r, b) { + var lp = lpFullWrap(); + lp.setMargins(self.dp(l || 0), self.dp(t || 0), self.dp(r || 0), self.dp(b || 0)); + parent.addView(view, lp); + } - // Title removed to avoid redundancy with Wrapper Title - // var titleTv = new android.widget.TextView(context); - // titleTv.setText(isEditing ? (this.state.editingSchemaIndex === -1 ? "新增项" : "编辑项") : "设置布局管理"); - // ... - // header.addView(titleTv); + function makeText(txt, sp, color, bold) { + var tv = new android.widget.TextView(context); + tv.setText(String(txt || "")); + tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, sp || 13); + tv.setTextColor(color || textColor); + if (bold) tv.setTypeface(null, android.graphics.Typeface.BOLD); + return tv; + } - // Placeholder to push buttons to right - header.addView(this.ui.createSpacer(this)); + function makeChip(txt, color, onClick) { + var tv = new android.widget.TextView(context); + tv.setText(String(txt || "")); + tv.setTextColor(color || primaryDeep); + tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); + tv.setGravity(android.view.Gravity.CENTER); + tv.setTypeface(null, android.graphics.Typeface.BOLD); + tv.setMinHeight(self.dp(32)); + tv.setPadding(self.dp(10), 0, self.dp(10), 0); + try { tv.setBackground(self.ui.createStrokeDrawable(self.withAlpha(color || primaryColor, isDark ? 0.16 : 0.08), self.withAlpha(color || primaryColor, isDark ? 0.45 : 0.28), self.dp(1), self.dp(16))); } catch(eBg) {} + if (onClick) { + tv.setOnClickListener(new android.view.View.OnClickListener({ onClick: function(v) { + try { self.touchActivity(); } catch(eTouch) {} + try { onClick(v); } catch(eClick) { safeLog(null, 'e', "schema chip click err=" + String(eClick)); } + }})); + } + return tv; + } + + function showToast(msg) { try { self.toast(String(msg || "")); } catch(eToast) {} } function refreshPanel() { self.state.keepSchemaEditorState = true; self.showPanelAvoidBall("schema_editor"); } + function clearDraft() { + self.state.schemaEditingDraft = null; + self.state.schemaEditingDraftIndex = null; + } + + function normalizeItem(item) { + var it = item || {}; + it.type = String(it.type || "bool"); + if (it.type !== "section") { + it.key = String(it.key || "").trim(); + } + it.name = String(it.name || it.key || "").trim(); + if (it.type === "section") { + delete it.key; delete it.min; delete it.max; delete it.step; delete it.action; + } else if (it.type === "bool" || it.type === "text") { + delete it.min; delete it.max; delete it.step; delete it.action; + } else if (it.type === "int" || it.type === "float") { + delete it.action; + if (it.min === "" || isNaN(Number(it.min))) delete it.min; else it.min = Number(it.min); + if (it.max === "" || isNaN(Number(it.max))) delete it.max; else it.max = Number(it.max); + if (it.step === "" || isNaN(Number(it.step))) delete it.step; else it.step = Number(it.step); + } else if (it.type === "action") { + delete it.min; delete it.max; delete it.step; + it.action = String(it.action || "").trim(); + } + return it; + } + + function validateItem(item) { + if (!item.name) return "请填写显示名字"; + if (item.type !== "section" && !item.key) return "请填写配置键"; + if (item.type === "action" && !item.action) return "请填写动作 ID"; + return ""; + } + + var panel = this.ui.createStyledPanel(this, 16); + try { panel.setBackground(this.ui.createRoundDrawable(bgColor, this.dp(20))); } catch(ePanelBg) {} + if (!isEditing) { - // List Mode - header.addView(self.ui.createFlatButton(self, "重置", C.danger, function() { - ConfigManager.resetSchema(); - self.state.tempSchema = null; - self.toast("已重置为默认布局"); - refreshPanel(); + var topCard = new android.widget.LinearLayout(context); + topCard.setOrientation(android.widget.LinearLayout.VERTICAL); + topCard.setPadding(self.dp(14), self.dp(12), self.dp(14), self.dp(12)); + try { topCard.setBackground(self.ui.createStrokeDrawable(cardColor, self.withAlpha(strokeColor, isDark ? 0.32 : 0.42), self.dp(1), self.dp(20))); } catch(eTopBg) {} + + var titleRow = new android.widget.LinearLayout(context); + titleRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + titleRow.setGravity(android.view.Gravity.CENTER_VERTICAL); + var titleBox = new android.widget.LinearLayout(context); + titleBox.setOrientation(android.widget.LinearLayout.VERTICAL); + titleBox.addView(makeText("岛屿蓝图", 17, textColor, true)); + titleBox.addView(makeText("这里会改变设置页结构,建议只在需要整理入口时使用", 12, subTextColor, false)); + titleRow.addView(titleBox, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + titleRow.addView(makeChip("添加", primaryDeep, function() { + self.state.editingSchemaIndex = -1; + clearDraft(); + if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor"); + else refreshPanel(); })); + topCard.addView(titleRow); - header.addView(self.ui.createFlatButton(self, "新增", C.primary, function() { - self.state.editingSchemaIndex = -1; - if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor"); - else refreshPanel(); - })); + var stat = makeText("共 " + schema.length + " 个蓝图项 · 保存后才会生效", 12, subTextColor, false); + stat.setPadding(0, self.dp(8), 0, 0); + topCard.addView(stat); + addWithMargins(panel, topCard, 0, 0, 0, 8); - var btnClose = self.ui.createFlatButton(self, "✕", C.textSecLight, function() { - self.state.tempSchema = null; - self.hideAllPanels(); - }); - header.addView(btnClose); - panel.addView(header); - panel.setTag(header); // 暴露 Header - - // Save Button - var btnSaveAll = self.ui.createSolidButton(self, "保存生效", C.primary, android.graphics.Color.WHITE, function() { - ConfigManager.saveSchema(schema); - self.state.tempSchema = null; - self.toast("布局已保存"); - if (self.state.toolAppActive && self.popToolAppPage) { - self.state.editingSchemaIndex = null; - self.popToolAppPage("schema_save_all"); - } else { - self.hideAllPanels(); - self.showPanelAvoidBall("settings"); - } - }); - var saveLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT); - saveLp.setMargins(0, 0, 0, self.dp(8)); - btnSaveAll.setLayoutParams(saveLp); - panel.addView(btnSaveAll); - - // List var scroll = new android.widget.ScrollView(context); - try { scroll.setVerticalScrollBarEnabled(false); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + try { scroll.setVerticalScrollBarEnabled(false); scroll.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(eScroll) {} var list = new android.widget.LinearLayout(context); list.setOrientation(android.widget.LinearLayout.VERTICAL); - list.setPadding(0, self.dp(4), 0, self.dp(4)); + list.setPadding(0, self.dp(2), 0, self.dp(10)); + + if (!schema.length) { + var empty = makeText("蓝图列表为空,可点上方“添加”创建新项。", 13, subTextColor, false); + empty.setGravity(android.view.Gravity.CENTER); + empty.setPadding(self.dp(12), self.dp(24), self.dp(12), self.dp(24)); + list.addView(empty, lpFullWrap()); + } for (var i = 0; i < schema.length; i++) { (function(idx) { - var item = schema[idx]; - + var item = schema[idx] || {}; var card = new android.widget.LinearLayout(context); - card.setOrientation(android.widget.LinearLayout.HORIZONTAL); - card.setGravity(android.view.Gravity.CENTER_VERTICAL); - card.setBackground(self.ui.createRoundDrawable(cardColor, self.dp(8))); - try { card.setElevation(self.dp(2)); } catch(e) { safeLog(null, 'e', "catch " + String(e)); } + card.setOrientation(android.widget.LinearLayout.VERTICAL); + card.setPadding(self.dp(13), self.dp(11), self.dp(13), self.dp(10)); + try { card.setBackground(self.ui.createStrokeDrawable(cardColor, self.withAlpha(strokeColor, isDark ? 0.28 : 0.36), self.dp(1), self.dp(18))); card.setElevation(self.dp(1)); } catch(eCardBg) {} - var cardLp = new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT); - cardLp.setMargins(self.dp(2), self.dp(4), self.dp(2), self.dp(4)); - card.setLayoutParams(cardLp); - card.setPadding(self.dp(12), self.dp(12), self.dp(8), self.dp(12)); + var row = new android.widget.LinearLayout(context); + row.setOrientation(android.widget.LinearLayout.HORIZONTAL); + row.setGravity(android.view.Gravity.CENTER_VERTICAL); + var badge = makeText(item.type === "section" ? "分组" : String(item.type || "项"), 11, primaryDeep, true); + badge.setGravity(android.view.Gravity.CENTER); + badge.setPadding(self.dp(8), self.dp(3), self.dp(8), self.dp(3)); + try { badge.setBackground(self.ui.createStrokeDrawable(primarySoft, self.withAlpha(primaryDeep, 0.25), self.dp(1), self.dp(12))); } catch(eBadge) {} + row.addView(badge); - // Info - var infoBox = new android.widget.LinearLayout(context); - infoBox.setOrientation(android.widget.LinearLayout.VERTICAL); - var infoLp = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT); - infoLp.weight = 1; - infoBox.setLayoutParams(infoLp); + var info = new android.widget.LinearLayout(context); + info.setOrientation(android.widget.LinearLayout.VERTICAL); + info.setPadding(self.dp(10), 0, 0, 0); + var nameTv = makeText(String(item.name || item.key || "未命名蓝图项"), 14, textColor, true); + info.addView(nameTv); + var sub = item.type === "section" ? "分组标题" : ("配置键:" + String(item.key || "未填写")); + info.addView(makeText(sub, 11, subTextColor, false)); + row.addView(info, new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + card.addView(row); - var nameTv = new android.widget.TextView(context); - nameTv.setText(String(item.name || item.key || "未命名")); - nameTv.setTextColor(textColor); - nameTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); - if (item.type === "section") nameTv.setTypeface(null, android.graphics.Typeface.BOLD); - infoBox.addView(nameTv); - - var typeTv = new android.widget.TextView(context); - var typeTxt = item.type; - if (item.type !== "section") typeTxt += " (" + (item.key || "?") + ")"; - typeTv.setText(String(typeTxt)); - typeTv.setTextColor(subTextColor); - typeTv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - infoBox.addView(typeTv); - - card.addView(infoBox); - - // Actions var actions = new android.widget.LinearLayout(context); actions.setOrientation(android.widget.LinearLayout.HORIZONTAL); - - if (idx > 0) { - actions.addView(self.ui.createFlatButton(self, "↑", subTextColor, function() { - var temp = schema[idx]; - schema[idx] = schema[idx - 1]; - schema[idx - 1] = temp; - refreshPanel(); - })); - } - if (idx < schema.length - 1) { - actions.addView(self.ui.createFlatButton(self, "↓", subTextColor, function() { - var temp = schema[idx]; - schema[idx] = schema[idx + 1]; - schema[idx + 1] = temp; - refreshPanel(); - })); - } - actions.addView(self.ui.createFlatButton(self, "✎", C.primary, function() { - self.state.editingSchemaIndex = idx; - if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor"); - else refreshPanel(); + actions.setGravity(android.view.Gravity.RIGHT | android.view.Gravity.CENTER_VERTICAL); + actions.setPadding(0, self.dp(9), 0, 0); + if (idx > 0) actions.addView(makeChip("上搬", subTextColor, function() { var tmp = schema[idx]; schema[idx] = schema[idx - 1]; schema[idx - 1] = tmp; refreshPanel(); })); + if (idx < schema.length - 1) actions.addView(makeChip("下搬", subTextColor, function() { var tmp = schema[idx]; schema[idx] = schema[idx + 1]; schema[idx + 1] = tmp; refreshPanel(); })); + actions.addView(makeChip("编辑", primaryDeep, function() { + self.state.editingSchemaIndex = idx; + clearDraft(); + if (self.state.toolAppActive && self.pushToolAppPage) self.pushToolAppPage("schema_editor"); + else refreshPanel(); })); - actions.addView(self.ui.createFlatButton(self, "✕", C.danger, function() { - schema.splice(idx, 1); - refreshPanel(); - })); - + actions.addView(makeChip("移除", dangerColor, function() { schema.splice(idx, 1); refreshPanel(); })); card.addView(actions); - list.addView(card); + addWithMargins(list, card, 2, 4, 2, 6); })(i); } + scroll.addView(list); - panel.addView(scroll); + panel.addView(scroll, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1)); - } else { - // Edit Mode - var editIdx = this.state.editingSchemaIndex; - var editItem = (editIdx === -1) ? { type: "bool", name: "", key: "" } : JSON.parse(JSON.stringify(schema[editIdx])); - - var btnBack = self.ui.createFlatButton(self, "返回", C.textSecLight, function() { + var bottom = new android.widget.LinearLayout(context); + bottom.setOrientation(android.widget.LinearLayout.HORIZONTAL); + bottom.setGravity(android.view.Gravity.CENTER_VERTICAL); + bottom.setPadding(0, self.dp(8), 0, 0); + var resetBtn = makeChip("恢复默认蓝图", dangerColor, function() { + ConfigManager.resetSchema(); + self.state.tempSchema = null; + clearDraft(); + showToast("已恢复默认蓝图"); + refreshPanel(); + }); + bottom.addView(resetBtn, new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1)); + var saveBtn = self.ui.createSolidButton(self, "保存蓝图", primaryColor, T && T.onPrimary ? T.onPrimary : android.graphics.Color.WHITE, function() { + ConfigManager.saveSchema(schema); + self.state.tempSchema = null; + clearDraft(); + showToast("蓝图已保存"); + if (self.state.toolAppActive && self.popToolAppPage) { self.state.editingSchemaIndex = null; - if (self.state.toolAppActive && self.popToolAppPage) { - self.state.keepSchemaEditorState = true; - self.popToolAppPage("schema_edit_back"); - } else refreshPanel(); + self.popToolAppPage("schema_save_all"); + } else { + self.hideAllPanels(); + self.showPanelAvoidBall("settings"); + } }); - header.addView(btnBack); - panel.addView(header); - panel.setTag(header); // 暴露 Header - - var schemaInlineNotice = new android.widget.TextView(context); - schemaInlineNotice.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - schemaInlineNotice.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); - schemaInlineNotice.setGravity(android.view.Gravity.CENTER_VERTICAL); - schemaInlineNotice.setVisibility(android.view.View.GONE); - function updateSchemaInlineNotice(msg, kind) { - try { - var k = String(kind || "info"); - var color = (k === "error") ? C.error : C.primary; - var bg = (k === "error") ? self.withAlpha(C.error, isDark ? 0.20 : 0.10) : self.withAlpha(C.primary, isDark ? 0.18 : 0.10); - var stroke = (k === "error") ? self.withAlpha(C.error, isDark ? 0.44 : 0.30) : self.withAlpha(C.primary, isDark ? 0.34 : 0.22); - schemaInlineNotice.setText(String(msg || "")); - schemaInlineNotice.setTextColor(color); - schemaInlineNotice.setBackground(self.ui.createStrokeDrawable(bg, stroke, self.dp(1), self.dp(14))); - schemaInlineNotice.setVisibility(android.view.View.VISIBLE); - } catch(eSIN) { safeLog(null, 'e', "catch " + String(eSIN)); } - } - - var scroll = new android.widget.ScrollView(context); - var form = new android.widget.LinearLayout(context); - form.setOrientation(android.widget.LinearLayout.VERTICAL); - form.addView(schemaInlineNotice, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT)); - scroll.addView(form); - - function createInput(label, key, inputType, hint) { - var box = new android.widget.LinearLayout(context); - box.setOrientation(android.widget.LinearLayout.VERTICAL); - box.setPadding(0, 0, 0, self.dp(12)); - - var lb = new android.widget.TextView(context); - lb.setText(label); - lb.setTextColor(subTextColor); - lb.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 12); - box.addView(lb); - - var et = new android.widget.EditText(context); - et.setText(String(editItem[key] !== undefined ? editItem[key] : "")); - et.setTextColor(textColor); - et.setHint(hint || ""); - et.setHintTextColor(self.withAlpha(subTextColor, 0.5)); - et.setBackground(self.ui.createRoundDrawable(inputBgColor, self.dp(8))); - et.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); - if (inputType) et.setInputType(inputType); - box.addView(et); - - form.addView(box); - return { - getValue: function() { return String(et.getText()); }, - getView: function() { return box; } - }; - } - - // Type Selector - var typeBox = new android.widget.LinearLayout(context); - typeBox.setOrientation(android.widget.LinearLayout.HORIZONTAL); - typeBox.setGravity(android.view.Gravity.CENTER_VERTICAL); - typeBox.setPadding(0, 0, 0, self.dp(12)); - var typeLb = new android.widget.TextView(context); - typeLb.setText("类型: "); - typeLb.setTextColor(textColor); - typeBox.addView(typeLb); - - var types = ["section", "bool", "int", "float", "text", "action"]; - var typeBtn = self.ui.createSolidButton(self, editItem.type, C.accent, android.graphics.Color.WHITE, function(v) { - var currIdx = types.indexOf(editItem.type); - var nextIdx = (currIdx + 1) % types.length; - editItem.type = types[nextIdx]; - refreshPanel(); - }); - typeBox.addView(typeBtn); - form.addView(typeBox); - - var inputName = createInput("显示名称 (Name)", "name", android.text.InputType.TYPE_CLASS_TEXT, "例如:悬浮球大小"); - - var inputKey = null; - var inputMin = null; - var inputMax = null; - var inputStep = null; - var inputAction = null; - - if (editItem.type !== "section") { - inputKey = createInput("配置键名 (Key)", "key", android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS, "例如:BALL_SIZE_DP"); - } - - if (editItem.type === "int" || editItem.type === "float") { - inputMin = createInput("最小值 (Min)", "min", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED); - inputMax = createInput("最大值 (Max)", "max", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED); - inputStep = createInput("步进 (Step)", "step", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL); - } - - if (editItem.type === "action") { - inputAction = createInput("动作ID (Action)", "action", android.text.InputType.TYPE_CLASS_TEXT, "例如:open_btn_mgr"); - } - - panel.addView(scroll); - - // Save Button - var btnSave = self.ui.createSolidButton(self, "暂存", C.primary, android.graphics.Color.WHITE, function() { - try { - editItem.name = inputName.getValue(); - if (inputKey) editItem.key = inputKey.getValue(); - - if (inputMin) editItem.min = Number(inputMin.getValue()); - if (inputMax) editItem.max = Number(inputMax.getValue()); - if (inputStep) editItem.step = Number(inputStep.getValue()); - if (inputAction) editItem.action = inputAction.getValue(); - - // Clean up properties based on type - if (editItem.type === "section") { delete editItem.key; delete editItem.min; delete editItem.max; delete editItem.step; delete editItem.action; } - else if (editItem.type === "bool") { delete editItem.min; delete editItem.max; delete editItem.step; delete editItem.action; } - else if (editItem.type === "int" || editItem.type === "float") { delete editItem.action; } - else if (editItem.type === "action") { delete editItem.min; delete editItem.max; delete editItem.step; } - - if (editIdx === -1) { - schema.push(editItem); - } else { - schema[editIdx] = editItem; - } - - self.state.editingSchemaIndex = null; - if (self.state.toolAppActive && self.popToolAppPage) { - self.state.keepSchemaEditorState = true; - self.popToolAppPage("schema_edit_save"); - } else refreshPanel(); - } catch (e) { - updateSchemaInlineNotice("暂存失败: " + e, "error"); - try { scroll.post(new java.lang.Runnable({ run: function() { try { scroll.fullScroll(android.view.View.FOCUS_UP); } catch(eScrollSchema) {} } })); } catch(ePostSchema) {} - } - }); - - var bottomBar = new android.widget.LinearLayout(context); - bottomBar.setOrientation(android.widget.LinearLayout.VERTICAL); - bottomBar.setPadding(0, self.dp(8), 0, 0); - bottomBar.addView(btnSave); - panel.addView(bottomBar); + var saveLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1); + saveLp.setMargins(self.dp(8), 0, 0, 0); + bottom.addView(saveBtn, saveLp); + panel.addView(bottom, lpFullWrap()); + return panel; } + var editIdx = this.state.editingSchemaIndex; + if (this.state.schemaEditingDraftIndex !== editIdx || !this.state.schemaEditingDraft) { + var baseItem = (editIdx === -1) ? { type: "bool", name: "", key: "" } : (schema[editIdx] ? JSON.parse(JSON.stringify(schema[editIdx])) : { type: "bool", name: "", key: "" }); + this.state.schemaEditingDraft = baseItem; + this.state.schemaEditingDraftIndex = editIdx; + } + var editItem = this.state.schemaEditingDraft; + if (!editItem.type) editItem.type = "bool"; + + var formCard = new android.widget.LinearLayout(context); + formCard.setOrientation(android.widget.LinearLayout.VERTICAL); + formCard.setPadding(self.dp(14), self.dp(12), self.dp(14), self.dp(12)); + try { formCard.setBackground(self.ui.createStrokeDrawable(cardColor, self.withAlpha(strokeColor, isDark ? 0.32 : 0.42), self.dp(1), self.dp(20))); } catch(eFormBg) {} + + var editTitle = makeText(editIdx === -1 ? "添加蓝图项" : "整理蓝图项", 17, textColor, true); + formCard.addView(editTitle); + var editHint = makeText("修改后先“暂存”,回到列表再统一保存蓝图。", 12, subTextColor, false); + editHint.setPadding(0, self.dp(4), 0, self.dp(10)); + formCard.addView(editHint); + + var schemaInlineNotice = makeText("", 12, primaryDeep, false); + schemaInlineNotice.setPadding(self.dp(12), self.dp(8), self.dp(12), self.dp(8)); + schemaInlineNotice.setVisibility(android.view.View.GONE); + function updateSchemaInlineNotice(msg, kind) { + try { + var color = String(kind || "info") === "error" ? dangerColor : primaryDeep; + schemaInlineNotice.setText(String(msg || "")); + schemaInlineNotice.setTextColor(color); + schemaInlineNotice.setBackground(self.ui.createStrokeDrawable(self.withAlpha(color, isDark ? 0.18 : 0.08), self.withAlpha(color, isDark ? 0.42 : 0.25), self.dp(1), self.dp(14))); + schemaInlineNotice.setVisibility(android.view.View.VISIBLE); + } catch(eNotice) {} + } + formCard.addView(schemaInlineNotice, lpFullWrap()); + + var scroll2 = new android.widget.ScrollView(context); + try { scroll2.setVerticalScrollBarEnabled(false); scroll2.setOverScrollMode(android.view.View.OVER_SCROLL_NEVER); } catch(eScroll2) {} + var form = new android.widget.LinearLayout(context); + form.setOrientation(android.widget.LinearLayout.VERTICAL); + scroll2.addView(form); + + function createInput(label, key, inputType, hint) { + var box = new android.widget.LinearLayout(context); + box.setOrientation(android.widget.LinearLayout.VERTICAL); + box.setPadding(0, 0, 0, self.dp(10)); + box.addView(makeText(label, 12, subTextColor, false)); + var et = new android.widget.EditText(context); + et.setText(String(editItem[key] !== undefined && editItem[key] !== null ? editItem[key] : "")); + et.setTextColor(textColor); + et.setHint(String(hint || "")); + et.setHintTextColor(self.withAlpha(subTextColor, 0.55)); + et.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, 14); + et.setSingleLine(true); + et.setBackground(self.ui.createStrokeDrawable(inputBgColor, self.withAlpha(strokeColor, 0.55), self.dp(1), self.dp(12))); + et.setPadding(self.dp(12), self.dp(7), self.dp(12), self.dp(7)); + if (inputType) et.setInputType(inputType); + box.addView(et, lpFullWrap()); + form.addView(box, lpFullWrap()); + return { input: et, getValue: function() { return String(et.getText()); } }; + } + + var inputName = null; + var inputKey = null; + var inputMin = null; + var inputMax = null; + var inputStep = null; + var inputAction = null; + + function syncDraftFromInputs() { + try { + if (inputName) editItem.name = inputName.getValue(); + if (inputKey) editItem.key = inputKey.getValue(); + if (inputMin) editItem.min = inputMin.getValue(); + if (inputMax) editItem.max = inputMax.getValue(); + if (inputStep) editItem.step = inputStep.getValue(); + if (inputAction) editItem.action = inputAction.getValue(); + self.state.schemaEditingDraft = editItem; + self.state.schemaEditingDraftIndex = editIdx; + } catch(eSync) { safeLog(null, 'e', "schema draft sync err=" + String(eSync)); } + } + + var typeRow = new android.widget.LinearLayout(context); + typeRow.setOrientation(android.widget.LinearLayout.HORIZONTAL); + typeRow.setGravity(android.view.Gravity.CENTER_VERTICAL); + typeRow.setPadding(0, 0, 0, self.dp(10)); + typeRow.addView(makeText("项目类型", 12, subTextColor, false), new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1)); + var types = ["section", "bool", "int", "float", "text", "action"]; + var typeNames = { section: "分组标题", bool: "开关", int: "整数", float: "小数", text: "文本", action: "动作入口" }; + typeRow.addView(makeChip(typeNames[String(editItem.type)] || String(editItem.type), primaryDeep, function() { + syncDraftFromInputs(); + var currIdx = types.indexOf(String(editItem.type || "bool")); + if (currIdx < 0) currIdx = 1; + editItem.type = types[(currIdx + 1) % types.length]; + self.state.schemaEditingDraft = editItem; + self.state.schemaEditingDraftIndex = editIdx; + refreshPanel(); + })); + form.addView(typeRow, lpFullWrap()); + + inputName = createInput("显示名字", "name", android.text.InputType.TYPE_CLASS_TEXT, "例如:漂浮气球"); + if (editItem.type !== "section") { + inputKey = createInput("配置键", "key", android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS, "例如:BALL_SIZE_DP"); + } + if (editItem.type === "int" || editItem.type === "float") { + inputMin = createInput("最小值", "min", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED, "可留空"); + inputMax = createInput("最大值", "max", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL | android.text.InputType.TYPE_NUMBER_FLAG_SIGNED, "可留空"); + inputStep = createInput("步进", "step", android.text.InputType.TYPE_CLASS_NUMBER | android.text.InputType.TYPE_NUMBER_FLAG_DECIMAL, "可留空"); + } + if (editItem.type === "action") { + inputAction = createInput("动作 ID", "action", android.text.InputType.TYPE_CLASS_TEXT | android.text.InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS, "例如:open_btn_mgr"); + } + + formCard.addView(scroll2, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1)); + panel.addView(formCard, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1)); + + var editBottom = new android.widget.LinearLayout(context); + editBottom.setOrientation(android.widget.LinearLayout.HORIZONTAL); + editBottom.setGravity(android.view.Gravity.CENTER_VERTICAL); + editBottom.setPadding(0, self.dp(8), 0, 0); + editBottom.addView(makeChip("不改了", subTextColor, function() { + clearDraft(); + self.state.editingSchemaIndex = null; + if (self.state.toolAppActive && self.popToolAppPage) { + self.state.keepSchemaEditorState = true; + self.popToolAppPage("schema_edit_back"); + } else refreshPanel(); + }), new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1)); + + var saveDraftBtn = self.ui.createSolidButton(self, "暂存蓝图项", primaryColor, T && T.onPrimary ? T.onPrimary : android.graphics.Color.WHITE, function() { + try { + syncDraftFromInputs(); + normalizeItem(editItem); + var err = validateItem(editItem); + if (err) { + updateSchemaInlineNotice(err, "error"); + try { scroll2.fullScroll(android.view.View.FOCUS_UP); } catch(eFocusUp) {} + return; + } + if (editIdx === -1) schema.push(JSON.parse(JSON.stringify(editItem))); + else schema[editIdx] = JSON.parse(JSON.stringify(editItem)); + clearDraft(); + self.state.editingSchemaIndex = null; + if (self.state.toolAppActive && self.popToolAppPage) { + self.state.keepSchemaEditorState = true; + self.popToolAppPage("schema_edit_save"); + } else refreshPanel(); + } catch(eSave) { + updateSchemaInlineNotice("暂存失败:" + String(eSave), "error"); + try { scroll2.fullScroll(android.view.View.FOCUS_UP); } catch(eFocusUp2) {} + } + }); + var saveDraftLp = new android.widget.LinearLayout.LayoutParams(0, self.dp(42), 1); + saveDraftLp.setMargins(self.dp(8), 0, 0, 0); + editBottom.addView(saveDraftBtn, saveDraftLp); + panel.addView(editBottom, lpFullWrap()); + return panel; }; diff --git a/manifest.json b/manifest.json index e4e03ca..e7661fb 100644 --- a/manifest.json +++ b/manifest.json @@ -66,8 +66,8 @@ "size": 267207 }, "th_14_schema_editor.js": { - "sha256": "798dbd880f22e8efe3d05f522cd0f149384366d22f373c0a13e2c603874c7faf", - "size": 14758 + "sha256": "5669d0b5a16f770bed24eedee24203df57f7cbc7910c840931e533adac1ef146", + "size": 20484 }, "th_15_extra.js": { "sha256": "f49d9e94702ff6b69b800aea10ae2d21dc0d52246ad176a92904a55352dbbf82", @@ -80,5 +80,5 @@ }, "keyId": "toolhub-targets-2026-rsa3072", "schema": 2, - "version": 20260522174733 + "version": 20260522181206 } diff --git a/manifest.sig b/manifest.sig index 5327e81..87e8f8c 100644 --- a/manifest.sig +++ b/manifest.sig @@ -1 +1 @@ -fiGtRS2KbVOMY9vKGhE3Axv7TD1O2D7sw4DwnwYBjdu6fDkQUDjbbtC8wOElpTRr0uiMI41JBDmi03O5SiXwr0Ua4u3nwdAzS6Uz1F8ZMaBDeW/iQFtTSRLxUm2t5ZsyR/L1BqfpEIx3IwkXX7/yi19qwExdQSfAojkkGXu4c1+e1miTRXNLcOsWniWvDM6BnECTE82ECMHH7w7sc22RaSYkDxLas4yn7uizuoLGfz1no0vzmwuHl3LYEe8UAZ4nETuXvwyxNZKhZ9yHQ8HBuXOYe0HyCpOfA2lxS4GUT2GkByGeU4MYykJqILcLpYYb5UPu8AMAoVmjQjIyy42fzl4VX9nW67ajCys/3wGxDU/r4EqrqII8hVMOkMzE0wxR1zAeHZWJNNjJLv8zD4TEdUh11xpGfTfIsAlWLGbL6dvim8iE3OTjIAAlNGQmNRK9NJRv3zrYzaLjXHvmcl02eWbcVM+hWi2d7pVyzE1YkGz033U/MdvwhCqwY7gJ4tvH +Oj/koe1KG25KEOTq/5jJfYFRBPL3ue9kSuhhKc7h0DGGpKJPwt0P1lmLCwJAKpIbwaQiuJo806UKW6r1qtHdjptDU0XreC8URd27lLWg1YgT61mVTKZK7qW8XSV71AebS51sHP+AmqAP9aiIdDRF1atUr0IX4+lxZAcN5t14aDLPwAqd0Kq5YJUUAjGACPbA9JuwjEwyXxlZ2NoyM2KZyDE983VlbJTEYUpvQYGZKKCSpPsowdNdz5OQMouDKigyLbCKQNva3+JtFfX4ZTzz4NMVJesBw82Q2MhlosCoAnrubhwj15MAYEMEP6TpajxjZVGVvFqCLJGh6d4lTq3i1RWPBGq7jTgIZPhlhUXSypt+v8TvWgv5UdmmDE+tSvPdNE+j8oLsah3toquYgiUcWnNaN2qdU9taa3cLb9JBOY1+mgs/M21yjYUC7A4WYpX7N4FPOqGLRUF0tPABBY1L6hY/gJ9YKPvvM4LC6e0C0cZThuBCJu/obk40Dl7iTcpS