Files
ShortX_ToolHub/code/th_14_schema_editor.js
2026-05-23 02:12:28 +08:00

393 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// ToolHub - 高级蓝图编辑器模块
// 依赖th_14_panels.js 的设置页主题/基础 UIth_05_persistence.js 的 ConfigManager。
// 加载顺序th_14_panels.js 之后th_15_extra.js 之前。
FloatBallAppWM.prototype.buildSchemaEditorPanelView = function() {
var self = this;
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));
}
this.state.keepSchemaEditorState = false;
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 = 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;
function lpFullWrap() {
return new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT);
}
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);
}
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;
}
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) {
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);
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 scroll = new android.widget.ScrollView(context);
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(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 card = new android.widget.LinearLayout(context);
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 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);
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 actions = new android.widget.LinearLayout(context);
actions.setOrientation(android.widget.LinearLayout.HORIZONTAL);
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(makeChip("移除", dangerColor, function() { schema.splice(idx, 1); refreshPanel(); }));
card.addView(actions);
addWithMargins(list, card, 2, 4, 2, 6);
})(i);
}
scroll.addView(list);
panel.addView(scroll, new android.widget.LinearLayout.LayoutParams(android.widget.LinearLayout.LayoutParams.MATCH_PARENT, 0, 1));
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;
self.popToolAppPage("schema_save_all");
} else {
self.hideAllPanels();
self.showPanelAvoidBall("settings");
}
});
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;
};