Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
132 changes: 132 additions & 0 deletions composables/guiConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { BaseDirectory, exists, mkdir, readDir, readTextFile, remove, writeTextFile } from "@tauri-apps/plugin-fs";
import type { RoomProfile } from "@/stores/index";

const GUI_CONFIG_VERSION = 1;

type RoomGuiConfigFile = {
version: number;
savedAt: string;
roomOrder?: number;
room: RoomProfile;
};

const isRecord = (value: unknown): value is Record<string, unknown> => typeof value === "object" && value !== null;

const isRoomGuiConfigFile = (value: unknown): value is RoomGuiConfigFile => {
if (!isRecord(value) || value.version !== GUI_CONFIG_VERSION || !isRecord(value.room)) {
return false;
}
const room = value.room;
return (
typeof value.savedAt === "string" &&
Number.isFinite(Date.parse(value.savedAt)) &&
(value.roomOrder === undefined || (typeof value.roomOrder === "number" && Number.isInteger(value.roomOrder))) &&
typeof room.id === "string" &&
Boolean(room.id) &&
typeof room.name === "string" &&
typeof room.rpcPortal === "number" &&
isRecord(room.config)
);
};

const ensureGuiConfigDirectory = async () => {
const directoryPath = import.meta.env.VITE_GUI_CONFIG_PATH;
if (!(await exists(directoryPath, { baseDir: BaseDirectory.Resource }))) {
await mkdir(directoryPath, { baseDir: BaseDirectory.Resource, recursive: true });
}
return directoryPath;
};

const writeRoomGuiConfig = async (directoryPath: string, room: RoomProfile, roomOrder: number) => {
const filePath = `${directoryPath}${room.id}.json`;
const content = JSON.stringify(
{
version: GUI_CONFIG_VERSION,
savedAt: new Date().toISOString(),
roomOrder,
room
},
null,
4
);
await writeTextFile(filePath, content, { baseDir: BaseDirectory.Resource });
return filePath;
};

export const saveRoomGuiConfig = async (room: RoomProfile, roomOrder: number) => {
const directoryPath = await ensureGuiConfigDirectory();
return writeRoomGuiConfig(directoryPath, room, roomOrder);
};

export const saveRoomGuiConfigs = async (rooms: RoomProfile[]) => {
const directoryPath = await ensureGuiConfigDirectory();
const writtenFilePaths: string[] = [];
try {
for (const [index, room] of rooms.entries()) {
const filePath = `${directoryPath}${room.id}.json`;
try {
writtenFilePaths.push(await writeRoomGuiConfig(directoryPath, room, index));
} catch (error) {
writtenFilePaths.push(filePath);
throw error;
}
}
return writtenFilePaths;
} catch (error) {
// 首次迁移必须全量成功,避免下次启动把不完整目录误判为完整的多房间数据源。
await Promise.allSettled(
writtenFilePaths.map(filePath => remove(filePath, { baseDir: BaseDirectory.Resource }))
);
throw error;
}
};

export const loadRoomGuiConfigs = async () => {
const directoryPath = import.meta.env.VITE_GUI_CONFIG_PATH;
if (!(await exists(directoryPath, { baseDir: BaseDirectory.Resource }))) {
return [] as RoomProfile[];
}

const entries = await readDir(directoryPath, { baseDir: BaseDirectory.Resource });
const configs = await Promise.all(
entries
.filter(entry => entry.isFile && entry.name.toLowerCase().endsWith(".json"))
.map(async entry => {
const filePath = `${directoryPath}${entry.name}`;
try {
const parsedConfig: unknown = JSON.parse(await readTextFile(filePath, { baseDir: BaseDirectory.Resource }));
if (!isRoomGuiConfigFile(parsedConfig)) {
throw new Error("文件结构或版本不受支持");
}
return parsedConfig;
} catch (error) {
console.error(`读取 GUI 房间配置“${entry.name}”失败`, error);
return null;
}
})
);

const roomConfigById = new Map<string, RoomGuiConfigFile>();
for (const config of configs) {
if (!config) continue;
const savedConfig = roomConfigById.get(config.room.id);
if (!savedConfig || Date.parse(config.savedAt) >= Date.parse(savedConfig.savedAt)) {
roomConfigById.set(config.room.id, config);
}
}

return [...roomConfigById.values()]
.sort(
(a, b) =>
(a.roomOrder ?? Number.MAX_SAFE_INTEGER) - (b.roomOrder ?? Number.MAX_SAFE_INTEGER) ||
a.room.name.localeCompare(b.room.name, "zh-CN")
)
.map(config => config.room);
};

export const removeRoomGuiConfig = async (roomId: string) => {
const filePath = `${import.meta.env.VITE_GUI_CONFIG_PATH}${roomId}.json`;
if (await exists(filePath, { baseDir: BaseDirectory.Resource })) {
await remove(filePath, { baseDir: BaseDirectory.Resource });
}
};
14 changes: 9 additions & 5 deletions composables/windows.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ import { updateConfigJsonBounce, type ConfigServerUrlType } from "./configJson";

const _listenersMaps: { [key: string]: [UnlistenFn | null] } = {};

export const MAIN_WINDOW_LOGICAL_SIZE = Object.freeze({
width: 340,
height: 350
});

const dealCloseListener = async (dialog: WebviewWindow, label: string, beforeCloseFunc?: () => void | null) => {
const unlistenFnList: [UnlistenFn | null] = _listenersMaps[label] || [null];
let [unListenlogClose] = unlistenFnList;
Expand Down Expand Up @@ -44,13 +49,12 @@ export default async (
defaultOpts.parent = appWindow;
// console.error(logicalPosition);
if (defaultOpts.x == 0 && defaultOpts.y == 0) {
const logicalAppSize = {
width: 340,
height: 305
}
const factor = await appWindow.scaleFactor();
const appPosition = await appWindow.outerPosition();
const logicalPosition = new PhysicalPosition(appPosition.x + Math.ceil((logicalAppSize.width + 5) * factor), appPosition.y).toLogical(factor);
const logicalPosition = new PhysicalPosition(
appPosition.x + Math.ceil((MAIN_WINDOW_LOGICAL_SIZE.width + 5) * factor),
appPosition.y
).toLogical(factor);
defaultOpts.x = logicalPosition.x;
defaultOpts.y = logicalPosition.y;
}
Expand Down
1 change: 1 addition & 0 deletions env/.env.dev
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_CONFIG_PATH=easytier/config/
VITE_CONFIG_ADMIN_PATH=easytier/config/.admin/
VITE_CONFIG_FILE_NAME=easytier/config.json
VITE_GUI_CONFIG_PATH=easytier/gui_config/
VITE_LOG_PATH=easytier/logs/
VITE_CACHE_PATH=easytier/cache/
VITE_GAME_LIST_PATH=easytier/gameList/
Expand Down
1 change: 1 addition & 0 deletions env/.env.prod
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
VITE_CONFIG_PATH=easytier/config/
VITE_CONFIG_ADMIN_PATH=easytier/config/.admin/
VITE_CONFIG_FILE_NAME=easytier/config.json
VITE_GUI_CONFIG_PATH=easytier/gui_config/
VITE_LOG_PATH=easytier/logs/
VITE_CACHE_PATH=easytier/cache/
VITE_GAME_LIST_PATH=easytier/gameList/
Expand Down
21 changes: 17 additions & 4 deletions pages/cidr.vue
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,12 @@
import { dataSubscribe } from "@/composables/windows";
import { invoke } from "@tauri-apps/api/core";
import { reactive, onMounted, onBeforeUnmount } from "vue";
import { ATJ, parseCliInfo } from "@/utils";
import { ATJ } from "@/utils";
import { listen, type UnlistenFn } from "@tauri-apps/api/event";
const mainStore = useMainStore();
if (!mainStore.activeRoomProfile) {
mainStore.initializeRoomProfiles();
}

const data = reactive<{ [key: string]: any }>({
route: [],
Expand All @@ -67,7 +70,13 @@

const listenOutput = async () => {
data.isRunning = true;
const [error, member] = await ATJ(invoke<string>("get_route_by_cli"));
const rpcPortal = mainStore.activeRoomProfile?.rpcPortal;
if (!rpcPortal) {
data.isRunning = false;
data.route = [];
return;
}
const [error, member] = await ATJ(invoke<string>("get_route_by_cli", { rpc_portal: rpcPortal }));
data.isRunning = false;
if(!member) return;
if (error) {
Expand All @@ -76,10 +85,14 @@
}
if (member === "_EasytierGameCliFailedToConnect_") {
data.route = [];
await listenOutput();
return;
}
data.route = JSON.parse(member);
try {
data.route = JSON.parse(member);
} catch (error) {
console.error("解析当前房间的子网代理路由失败", error);
data.route = [];
}
};

const listenStart = async () => {
Expand Down
Loading