check group and var on build
This commit is contained in:
1 parent
4fb851c8cc
commit
b8817fd2c9
10 files changed
+164
-61
No files matched your search
+12
-2
@@ -5,6 +5,7 @@ import os from "node:os";
|
|||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { argv } from "node:process";
|
import { argv } from "node:process";
|
||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { VERSION, type FileVarDecl } from "../../src/misc/b3type";
|
||||||
import * as b3util from "../../src/misc/b3util";
|
import * as b3util from "../../src/misc/b3util";
|
||||||
import Path from "../../src/misc/path";
|
import Path from "../../src/misc/path";
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ for (let i = 0; i < argv.length; i++) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const printHelp = () => {
|
const printHelp = () => {
|
||||||
console.log("Usage: Behavior3 Editor [options]");
|
console.log(`Usage: Behavior3 Editor ${VERSION} [options]`);
|
||||||
console.log("Options:");
|
console.log("Options:");
|
||||||
console.log(" --project <path> Set the project path");
|
console.log(" --project <path> Set the project path");
|
||||||
console.log(" --build <path> Set the build output path");
|
console.log(" --build <path> Set the build output path");
|
||||||
@@ -64,11 +65,20 @@ if (buildOutput || buildProject || buildHelp) {
|
|||||||
if (path.endsWith(".json")) {
|
if (path.endsWith(".json")) {
|
||||||
const buildpath = buildDir + "/" + path.substring(workdir.length + 1);
|
const buildpath = buildDir + "/" + path.substring(workdir.length + 1);
|
||||||
const treeModel = b3util.createBuildData(path);
|
const treeModel = b3util.createBuildData(path);
|
||||||
if (treeModel && treeModel.export === false) {
|
if (!treeModel) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (treeModel.export === false) {
|
||||||
console.log("skip:", buildpath);
|
console.log("skip:", buildpath);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log("build:", buildpath);
|
console.log("build:", buildpath);
|
||||||
|
const declare: FileVarDecl = {
|
||||||
|
import: treeModel.import.map((v) => ({ path: v, vars: [], depends: [] })),
|
||||||
|
declvar: treeModel.declvar.map((v) => ({ name: v.name, desc: v.desc })),
|
||||||
|
subtree: [],
|
||||||
|
};
|
||||||
|
b3util.refreshDeclare(treeModel, declare);
|
||||||
if (!b3util.checkNodeData(treeModel?.root)) {
|
if (!b3util.checkNodeData(treeModel?.root)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"about": "About Behavior3",
|
"about": "About Behavior3",
|
||||||
"add": "Add",
|
"add": "Add",
|
||||||
"actuaSize": "Actual Size",
|
"actuaSize": "Actual Size",
|
||||||
|
"alertNewVersion": "This file is created by a newer version of Behavior3({{version}}), please upgrade to the latest version.",
|
||||||
"build": "Build",
|
"build": "Build",
|
||||||
"batch": "Batch Script",
|
"batch": "Batch Script",
|
||||||
"batchCompleted": "Batch completed",
|
"batchCompleted": "Batch completed",
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
"about": "关于 Behavior3",
|
"about": "关于 Behavior3",
|
||||||
"add": "添加",
|
"add": "添加",
|
||||||
"actualSize": "实际大小",
|
"actualSize": "实际大小",
|
||||||
|
"alertNewVersion": "此文件由新版本 Behavior3({{version}}) 创建,请升级到最新版本。",
|
||||||
"build": "构建",
|
"build": "构建",
|
||||||
"batch": "批处理脚本",
|
"batch": "批处理脚本",
|
||||||
"batchCompleted": "批处理完成",
|
"batchCompleted": "批处理完成",
|
||||||
|
|||||||
@@ -3,13 +3,8 @@
|
|||||||
"desc": "",
|
"desc": "",
|
||||||
"firstid": 1,
|
"firstid": 1,
|
||||||
"export": true,
|
"export": true,
|
||||||
"group": [
|
"group": ["Client"],
|
||||||
"Client"
|
"import": ["vars/declare-core.json", "vars/declare-vars.json"],
|
||||||
],
|
|
||||||
"import": [
|
|
||||||
"vars/declare-core.json",
|
|
||||||
"vars/declare-vars.json"
|
|
||||||
],
|
|
||||||
"declvar": [
|
"declvar": [
|
||||||
{
|
{
|
||||||
"name": "target",
|
"name": "target",
|
||||||
@@ -33,16 +28,12 @@
|
|||||||
"id": 4,
|
"id": 4,
|
||||||
"name": "FindEnemy",
|
"name": "FindEnemy",
|
||||||
"args": {},
|
"args": {},
|
||||||
"output": [
|
"output": ["target1"]
|
||||||
"target1"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 5,
|
"id": 5,
|
||||||
"name": "Attack",
|
"name": "Attack",
|
||||||
"input": [
|
"input": ["target1"]
|
||||||
"target1"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 6,
|
"id": 6,
|
||||||
@@ -54,16 +45,12 @@
|
|||||||
{
|
{
|
||||||
"id": 7,
|
"id": 7,
|
||||||
"name": "MoveToTarget",
|
"name": "MoveToTarget",
|
||||||
"input": [
|
"input": ["target"]
|
||||||
"target"
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 8,
|
"id": 8,
|
||||||
"name": "Clear",
|
"name": "Clear",
|
||||||
"output": [
|
"output": ["test"],
|
||||||
"test"
|
|
||||||
],
|
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"id": 9,
|
"id": 9,
|
||||||
@@ -73,4 +60,4 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+34
-14
@@ -21,7 +21,15 @@ import {
|
|||||||
EditTree,
|
EditTree,
|
||||||
useWorkspace,
|
useWorkspace,
|
||||||
} from "../contexts/workspace-context";
|
} from "../contexts/workspace-context";
|
||||||
import { ImportDef, isExprType, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type";
|
import {
|
||||||
|
ImportDef,
|
||||||
|
isExprType,
|
||||||
|
NodeModel,
|
||||||
|
TreeGraphData,
|
||||||
|
TreeModel,
|
||||||
|
VarDef,
|
||||||
|
VERSION,
|
||||||
|
} from "../misc/b3type";
|
||||||
import * as b3util from "../misc/b3util";
|
import * as b3util from "../misc/b3util";
|
||||||
import { message } from "../misc/hooks";
|
import { message } from "../misc/hooks";
|
||||||
import i18n from "../misc/i18n";
|
import i18n from "../misc/i18n";
|
||||||
@@ -777,20 +785,32 @@ export const Editor: FC<EditorProps> = ({ onUpdate: updateState, data: editor, .
|
|||||||
};
|
};
|
||||||
|
|
||||||
const save = () => {
|
const save = () => {
|
||||||
if (editor.unsave || !fs.existsSync(editor.path)) {
|
if (b3util.isNewVersion(editor.data.version)) {
|
||||||
const path = editor.path;
|
message.error(t("alertNewVersion", { version: editor.data.version }), 6);
|
||||||
editor.autoId = b3util.refreshTreeDataId(editor.root, editor.data.firstid);
|
return;
|
||||||
editor.data.root = b3util.createFileData(editor.root);
|
|
||||||
editor.modifiedTime = Date.now();
|
|
||||||
fs.writeFileSync(path, JSON.stringify(editor.data, null, 2));
|
|
||||||
workspace.updateFileMeta(editor);
|
|
||||||
editor.unsave = false;
|
|
||||||
editor.graph.changeData(editor.root);
|
|
||||||
editor.graph.layout();
|
|
||||||
restoreViewport();
|
|
||||||
updateSearchState();
|
|
||||||
updateState();
|
|
||||||
}
|
}
|
||||||
|
const path = editor.path;
|
||||||
|
editor.autoId = b3util.refreshTreeDataId(editor.root, editor.data.firstid);
|
||||||
|
editor.data.root = b3util.createFileData(editor.root);
|
||||||
|
editor.modifiedTime = Date.now();
|
||||||
|
fs.writeFileSync(
|
||||||
|
path,
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
...editor.data,
|
||||||
|
version: VERSION,
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
)
|
||||||
|
);
|
||||||
|
workspace.updateFileMeta(editor);
|
||||||
|
editor.unsave = false;
|
||||||
|
editor.graph.changeData(editor.root);
|
||||||
|
editor.graph.layout();
|
||||||
|
restoreViewport();
|
||||||
|
updateSearchState();
|
||||||
|
updateState();
|
||||||
};
|
};
|
||||||
|
|
||||||
const editSubtree = () => {
|
const editSubtree = () => {
|
||||||
|
|||||||
@@ -152,12 +152,14 @@ G6.registerNode(
|
|||||||
!checkTreeData(data) ||
|
!checkTreeData(data) ||
|
||||||
(nodeDef.group && !usingGroups[nodeDef.group]) ||
|
(nodeDef.group && !usingGroups[nodeDef.group]) ||
|
||||||
(usingVars &&
|
(usingVars &&
|
||||||
(data.input?.some((v) => usingVars?.[v] === undefined) ||
|
(data.input?.some((v) => v && usingVars?.[v] === undefined) ||
|
||||||
data.output?.some((v) => usingVars?.[v] === undefined) ||
|
data.output?.some((v) => v && usingVars?.[v] === undefined) ||
|
||||||
nodeDef.args?.some(
|
nodeDef.args?.some(
|
||||||
(v) =>
|
(v) =>
|
||||||
isExprType(v.type) &&
|
isExprType(v.type) &&
|
||||||
parseExpr(data.args?.[v.name] ?? "").some((vv) => usingVars?.[vv] === undefined)
|
parseExpr(data.args?.[v.name] ?? "").some(
|
||||||
|
(vv) => vv && usingVars?.[vv] === undefined
|
||||||
|
)
|
||||||
)))
|
)))
|
||||||
) {
|
) {
|
||||||
classify = "Error";
|
classify = "Error";
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ import * as fs from "fs";
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { create } from "zustand";
|
import { create } from "zustand";
|
||||||
import { NodeDef } from "../behavior3/src/behavior3";
|
import { NodeDef } from "../behavior3/src/behavior3";
|
||||||
import { ImportDef, NodeModel, TreeGraphData, TreeModel, VarDef } from "../misc/b3type";
|
import {
|
||||||
|
FileVarDecl,
|
||||||
|
ImportDef,
|
||||||
|
NodeModel,
|
||||||
|
TreeGraphData,
|
||||||
|
TreeModel,
|
||||||
|
VarDef,
|
||||||
|
} from "../misc/b3type";
|
||||||
import * as b3util from "../misc/b3util";
|
import * as b3util from "../misc/b3util";
|
||||||
import { message } from "../misc/hooks";
|
import { message } from "../misc/hooks";
|
||||||
import i18n from "../misc/i18n";
|
import i18n from "../misc/i18n";
|
||||||
@@ -46,11 +53,7 @@ export class EditorStore {
|
|||||||
data: TreeModel;
|
data: TreeModel;
|
||||||
|
|
||||||
root: TreeGraphData;
|
root: TreeGraphData;
|
||||||
declare: {
|
declare: FileVarDecl;
|
||||||
import: ImportDef[];
|
|
||||||
subtree: ImportDef[];
|
|
||||||
declvar: VarDef[];
|
|
||||||
};
|
|
||||||
|
|
||||||
autoId: number = 1;
|
autoId: number = 1;
|
||||||
dragSrcId?: string;
|
dragSrcId?: string;
|
||||||
@@ -354,11 +357,20 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
workspace.allFiles.forEach((file) => {
|
workspace.allFiles.forEach((file) => {
|
||||||
const buildpath = buildDir + "/" + workspace.relative(file.path);
|
const buildpath = buildDir + "/" + workspace.relative(file.path);
|
||||||
const treeModel = b3util.createBuildData(file.path);
|
const treeModel = b3util.createBuildData(file.path);
|
||||||
if (treeModel && treeModel.export === false) {
|
if (!treeModel) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (treeModel.export === false) {
|
||||||
console.log("skip:", buildpath);
|
console.log("skip:", buildpath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
console.log("build:", buildpath);
|
console.log("build:", buildpath);
|
||||||
|
const declare: FileVarDecl = {
|
||||||
|
import: treeModel.import.map((v) => ({ path: v, vars: [], depends: [] })),
|
||||||
|
declvar: treeModel.declvar.map((v) => ({ name: v.name, desc: v.desc })),
|
||||||
|
subtree: [],
|
||||||
|
};
|
||||||
|
b3util.refreshDeclare(treeModel, declare);
|
||||||
if (!b3util.checkNodeData(treeModel?.root)) {
|
if (!b3util.checkNodeData(treeModel?.root)) {
|
||||||
hasError = true;
|
hasError = true;
|
||||||
}
|
}
|
||||||
@@ -479,6 +491,10 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
set({ editors: workspace.editors });
|
set({ editors: workspace.editors });
|
||||||
workspace.updateFileMeta(editor);
|
workspace.updateFileMeta(editor);
|
||||||
workspace.edit(editor.path, selectedNode);
|
workspace.edit(editor.path, selectedNode);
|
||||||
|
|
||||||
|
if (b3util.isNewVersion(editor.data.version)) {
|
||||||
|
message.warning(i18n.t("alertNewVersion", { version: editor.data.version }));
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
message.error(`invalid file: ${path}`);
|
message.error(`invalid file: ${path}`);
|
||||||
@@ -538,18 +554,7 @@ export const useWorkspace = create<WorkspaceStore>((set, get) => ({
|
|||||||
if (!editor) {
|
if (!editor) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const vars: Set<VarDef> = new Set(editor.declare.declvar.slice());
|
b3util.refreshDeclare(editor.data, editor.declare);
|
||||||
editor.declare.subtree = b3util.findSubtrees(editor.root).map((v) => ({
|
|
||||||
path: v,
|
|
||||||
vars: [],
|
|
||||||
depends: [],
|
|
||||||
}));
|
|
||||||
b3util.loadVarDef(editor.declare.import).forEach((v) => vars.add(v));
|
|
||||||
b3util.loadVarDef(editor.declare.subtree).forEach((v) => {
|
|
||||||
vars.add(v);
|
|
||||||
});
|
|
||||||
b3util.updateUsingGroups(editor.data.group);
|
|
||||||
b3util.updateUsingVars(Array.from(vars));
|
|
||||||
},
|
},
|
||||||
|
|
||||||
save: () => {
|
save: () => {
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ declare module "../behavior3/src/behavior3" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const VERSION = "1.8.0";
|
||||||
|
|
||||||
export type NodeType = NodeDef["type"] | "Other" | "Error";
|
export type NodeType = NodeDef["type"] | "Other" | "Error";
|
||||||
export type NodeArg = Exclude<NodeDef["args"], undefined>[number];
|
export type NodeArg = Exclude<NodeDef["args"], undefined>[number];
|
||||||
|
|
||||||
@@ -51,7 +53,14 @@ export interface ImportDef {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface FileVarDecl {
|
||||||
|
import: ImportDef[];
|
||||||
|
subtree: ImportDef[];
|
||||||
|
declvar: VarDef[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface TreeModel {
|
export interface TreeModel {
|
||||||
|
version: string;
|
||||||
name: string;
|
name: string;
|
||||||
desc?: string;
|
desc?: string;
|
||||||
export?: boolean;
|
export?: boolean;
|
||||||
|
|||||||
+69
-2
@@ -1,6 +1,7 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { NodeDef } from "../behavior3/src/behavior3";
|
import { NodeDef } from "../behavior3/src/behavior3";
|
||||||
import {
|
import {
|
||||||
|
FileVarDecl,
|
||||||
ImportDef,
|
ImportDef,
|
||||||
isBoolType,
|
isBoolType,
|
||||||
isEnumType,
|
isEnumType,
|
||||||
@@ -14,6 +15,7 @@ import {
|
|||||||
TreeGraphData,
|
TreeGraphData,
|
||||||
TreeModel,
|
TreeModel,
|
||||||
VarDef,
|
VarDef,
|
||||||
|
VERSION,
|
||||||
} from "./b3type";
|
} from "./b3type";
|
||||||
import Path from "./path";
|
import Path from "./path";
|
||||||
import { readJson, readTree } from "./util";
|
import { readJson, readTree } from "./util";
|
||||||
@@ -87,6 +89,16 @@ export const parseExpr = (expr: string) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const isNewVersion = (version: string) => {
|
||||||
|
const [major, minor, patch] = version.split(".").map(Number);
|
||||||
|
const [major2, minor2, patch2] = VERSION.split(".").map(Number);
|
||||||
|
return (
|
||||||
|
major > major2 ||
|
||||||
|
(major === major2 && minor > minor2) ||
|
||||||
|
(major === major2 && minor === minor2 && patch > patch2)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export const isValidVariableName = (name: string) => {
|
export const isValidVariableName = (name: string) => {
|
||||||
return /^[a-zA-Z_]/.test(name);
|
return /^[a-zA-Z_]/.test(name);
|
||||||
};
|
};
|
||||||
@@ -286,6 +298,45 @@ export const checkNodeData = (data: NodeModel | null | undefined) => {
|
|||||||
|
|
||||||
let hasError = false;
|
let hasError = false;
|
||||||
|
|
||||||
|
if (conf.group) {
|
||||||
|
if (!usingGroups[conf.group]) {
|
||||||
|
error(data, `node group '${conf.group}' is not enabled`);
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usingVars) {
|
||||||
|
if (data.input) {
|
||||||
|
for (const v of data.input) {
|
||||||
|
if (v && !usingVars[v]) {
|
||||||
|
error(data, `input variable '${v}' is not defined`);
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.output) {
|
||||||
|
for (const v of data.output) {
|
||||||
|
if (v && !usingVars[v]) {
|
||||||
|
error(data, `output variable '${v}' is not defined`);
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.args && conf.args) {
|
||||||
|
for (const arg of conf.args) {
|
||||||
|
if (isExprType(arg.type)) {
|
||||||
|
for (const v of parseExpr(data.args[arg.name] ?? "")) {
|
||||||
|
if (v && !usingVars[v]) {
|
||||||
|
error(data, `expr variable '${arg.name}' is not defined`);
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (conf.children !== undefined && conf.children !== -1) {
|
if (conf.children !== undefined && conf.children !== -1) {
|
||||||
const count = data.children?.length || 0;
|
const count = data.children?.length || 0;
|
||||||
if (conf.children !== count) {
|
if (conf.children !== count) {
|
||||||
@@ -712,6 +763,7 @@ export const createFileData = (data: TreeGraphData, includeSubtree?: boolean) =>
|
|||||||
|
|
||||||
export const createNewTree = (path: string) => {
|
export const createNewTree = (path: string) => {
|
||||||
const tree: TreeModel = {
|
const tree: TreeModel = {
|
||||||
|
version: VERSION,
|
||||||
name: Path.basenameWithoutExt(path),
|
name: Path.basenameWithoutExt(path),
|
||||||
firstid: 1,
|
firstid: 1,
|
||||||
group: [],
|
group: [],
|
||||||
@@ -796,9 +848,9 @@ export const loadVarDef = (list: ImportDef[]) => {
|
|||||||
return Array.from(all);
|
return Array.from(all);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const findSubtrees = (data: TreeGraphData) => {
|
const findSubtrees = (data: NodeModel) => {
|
||||||
const list: string[] = [];
|
const list: string[] = [];
|
||||||
const traverse = (v: TreeGraphData) => {
|
const traverse = (v: NodeModel) => {
|
||||||
if (v.path) {
|
if (v.path) {
|
||||||
list.push(v.path);
|
list.push(v.path);
|
||||||
}
|
}
|
||||||
@@ -809,3 +861,18 @@ export const findSubtrees = (data: TreeGraphData) => {
|
|||||||
traverse(data);
|
traverse(data);
|
||||||
return list;
|
return list;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const refreshDeclare = (tree: TreeModel, declare: FileVarDecl) => {
|
||||||
|
const vars: Set<VarDef> = new Set(declare.declvar.slice());
|
||||||
|
declare.subtree = findSubtrees(tree.root).map((v) => ({
|
||||||
|
path: v,
|
||||||
|
vars: [],
|
||||||
|
depends: [],
|
||||||
|
}));
|
||||||
|
loadVarDef(declare.import).forEach((v) => vars.add(v));
|
||||||
|
loadVarDef(declare.subtree).forEach((v) => {
|
||||||
|
vars.add(v);
|
||||||
|
});
|
||||||
|
updateUsingGroups(tree.group);
|
||||||
|
updateUsingVars(Array.from(vars));
|
||||||
|
};
|
||||||
+2
-1
@@ -1,5 +1,5 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import type { TreeModel } from "./b3type";
|
import { VERSION, type TreeModel } from "./b3type";
|
||||||
|
|
||||||
export const readJson = (path: string) => {
|
export const readJson = (path: string) => {
|
||||||
const str = fs.readFileSync(path, "utf-8");
|
const str = fs.readFileSync(path, "utf-8");
|
||||||
@@ -9,6 +9,7 @@ export const readJson = (path: string) => {
|
|||||||
export const readTree = (path: string) => {
|
export const readTree = (path: string) => {
|
||||||
const str = fs.readFileSync(path, "utf-8");
|
const str = fs.readFileSync(path, "utf-8");
|
||||||
const data = JSON.parse(str) as TreeModel;
|
const data = JSON.parse(str) as TreeModel;
|
||||||
|
data.version = data.version ?? VERSION;
|
||||||
data.firstid = data.firstid ?? 1;
|
data.firstid = data.firstid ?? 1;
|
||||||
data.group = data.group || [];
|
data.group = data.group || [];
|
||||||
data.import = data.import || [];
|
data.import = data.import || [];
|
||||||
|
|||||||
Reference in new issue
Block a user