Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .changeset/desktop-ui-studio.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"@sfmc-bds/desktop": minor
"@sfmc-bds/sdk": patch
---

Desktop 在「开发工具」中内置 UI Studio,编辑器随客户端打包,无需连接实例即可离线使用;切换页面或实例时保留编辑状态,支持命令面板与 Ctrl+9 入口。

UI Studio 改用编辑器内的输入、确认和提示弹窗,修复 Electron 中新建工程、重命名页面与自定义绑定无法使用的问题。

将桌面的 SnowUI 令牌、字体、Phosphor 图标与 Base UI 控件提取为内部公共 UI 层。UI Studio 复用同一套按钮、输入、选择器、菜单和对话框;嵌入时跟随桌面外观,并按 app/pages/components/lib/store/styles 分层整理界面代码。
3 changes: 3 additions & 0 deletions .github/workflows/desktop-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: [main]
paths:
- "packages/desktop/**"
- "packages/ui/**"
- "packages/management/**"
- "packages/cli/**"
- "modules/sdk/**"
Expand Down Expand Up @@ -54,6 +55,8 @@ jobs:
- run: pnpm --filter @sfmc-bds/management typecheck
- run: pnpm --filter @sfmc-bds/cli typecheck
- run: pnpm --filter @sfmc-bds/desktop typecheck
- run: pnpm --filter @sfmc-bds/ui typecheck
- run: node packages/tools/tsc7.mjs --noEmit -p modules/sdk/@sfmc-sdk/ui-studio-web/tsconfig.json
- name: 核对桌面标签与平台版本
if: startsWith(github.ref, 'refs/tags/')
shell: pwsh
Expand Down
3 changes: 3 additions & 0 deletions LICENSES.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,15 @@
| `@sfmc-bds/qq-bridge` | AGPL-3.0-only | 见仓库根 `LICENSE` |
| `@sfmc-bds/bds-tools` | AGPL-3.0-only | 见仓库根 `LICENSE` |
| `@sfmc-bds/tools` | AGPL-3.0-only(**不发 npm**,仓内脚本) | 见仓库根 `LICENSE` |
| `@sfmc-bds/ui`(内部公共界面包) | AGPL-3.0-only(**不发 npm**) | 见仓库根 `LICENSE` |
| `@sfmc-bds/sfmc`(meta) | AGPL-3.0-only | 见仓库根 `LICENSE` |
| `@sfmc-bds/devkit` | AGPL-3.0-only | 见仓库根 `LICENSE` |
| `sfmc-extension`(VS Code) | AGPL-3.0-only | 见仓库根 `LICENSE` |

## 变更记录

UI Studio 的浏览器应用复用桌面公共 UI,界面应用随静态产物附带 AGPL 许可证与源码入口。SDK 的模块运行时、契约、校验器和作者向 API 仍按 ISC 分发;使用这些 API 开发模块的许可边界保持一致。

- 统一 `cli` 的 SPDX 拼写为 `AGPL-3.0-only`(原 `AGPL-v3.0-only` 无效)
- `meta` 由 `GPL-3.0-only` 改为 `AGPL-3.0-only`(与聚合的 AGPL 服务一致)
- `qq-bridge`、`bds-tools` 由 ISC 改为 AGPL-3.0-only(平台网络/运维组件)
Expand Down
6 changes: 6 additions & 0 deletions docs/zh/dev/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,3 +176,9 @@ ScriptsForMinecraftServer/
- `packages/*`:平台基础包与核心工具,均独立发布至 npm。
- `modules/packages/*`:运行时的业务模块目录,与平台核心代码相互独立。
:::

## 桌面端与 UI Studio 的界面分层

`packages/ui` 提供共享的 SnowUI 令牌与字体、Phosphor 图标和 Base UI 控件/浮层。Desktop 的外观提供者负责 Monaco 与原生窗口配色;Studio 独立运行时保存自己的外观偏好,内嵌时跟随 Desktop。基础视觉和交互只维护一份,领域逻辑由各应用承担。

UI Studio 浏览器源码按 `app`(路由与宿主)、`pages`(工程列表与编辑器)、`components`(工程树、属性面板与画布)、`lib`(草稿/导入导出/预览派生)、`store`(工程格式与 IndexedDB)、`styles`(编辑器布局)组织。Desktop 复用 SDK 的 Vite 配置打包 Studio,内置入口在「开发工具」;切页隐藏文档并保留编辑状态,不依赖实例握手或 CLI 服务。
8 changes: 8 additions & 0 deletions docs/zh/guide/desktop.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,14 @@ gh attestation verify '.\SFMC-Desktop-0.1.2-x64.exe' --repo DogeLakeDev/ScriptsF

**配置** 编辑服务和模块配置,保存后根据提示重载或重启相关服务。**控制台** 查看不同服务日志、发送 BDS 命令;**任务** 查看操作进度、失败原因与备份编号。

## 内置 UI Studio(下个桌面版本)

左侧栏的 **开发工具 → UI Studio** 打开声明式界面的可视化编辑器,也可用 `Ctrl+9` 或命令面板搜索「UI Studio」。无需添加或连接实例,也无需执行 `sfmc ui studio`;编辑器随桌面客户端打包,可离线使用。控件、图标、字体与桌面端统一,外观跟随桌面顶栏的浅色 / 深色 / 跟随系统设置。

支持新建工程、导入文件夹或 ZIP、拖放组件、编辑属性、预览和导出 ZIP。切换桌面页面或实例后返回,当前工程、选中项与撤销记录仍保留。工程自动保存在当前电脑的客户端数据中,跨电脑使用时先导出 ZIP。

浏览器中的 Studio 工程与桌面客户端分别保存;已有浏览器工程请先导出 ZIP,再导入内置编辑器。导出的工程需按模块开发流程放回模块中,编辑器不会自动修改或部署服务器文件。

## 建议的首次验收

1. 先在独立测试目录完成接入;已有正式部署优先选择 **仅查看**。
Expand Down
13 changes: 6 additions & 7 deletions modules/sdk/@sfmc-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,16 +134,15 @@
"typecheck": "tsc7 --noEmit -p tsconfig.types.json"
},
"devDependencies": {
"@headlessui/react": "^2.2.10",
"@sfmc-bds/tools": "workspace:*",
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@types/react": "19.3.0",
"@types/react-dom": "19.3.0",
"@vitejs/plugin-react": "^5.0.0",
"fflate": "^0.8.3",
"lucide-react": "^1.45.0",
"react": "^19.2.8",
"react-dom": "^19.2.8",
"vite": "^7.0.0"
"react": "19.3.0",
"react-dom": "19.3.0",
"vite": "^7.0.0",
"@sfmc-bds/ui": "workspace:*"
},
"peerDependencies": {
"@minecraft/diagnostics": "1.0.0-beta.1.26.40-preview.30",
Expand Down
57 changes: 57 additions & 0 deletions modules/sdk/@sfmc-sdk/ui-studio-web/src/app/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/** 应用外壳:路由、公共 UI 提供者、宿主外观同步与弹窗出口。 */
import { TooltipProvider } from "@sfmc-bds/ui/tooltip";
import { useCallback, useEffect, useState } from "react";
import { StudioDialogHost } from "../components/StudioDialogs";
import { App as EditorPage } from "../pages/EditorPage";
import { ProjectList } from "../pages/ProjectList";
import { embedded } from "./host";
import { applyResolvedTheme } from "./theme";

function parseRoute(): { name: "list" } | { name: "editor"; projectId: string } {
const match = /^#\/p\/([^/]+)/.exec(window.location.hash);
if (match) {
try {
return { name: "editor", projectId: decodeURIComponent(match[1]!) };
} catch {
/* 无效路由回到列表 */
}
}
return { name: "list" };
}

export function App() {
const [route, setRoute] = useState(parseRoute);
useEffect(() => {
const onHashChange = () => setRoute(parseRoute());
window.addEventListener("hashchange", onHashChange);
return () => window.removeEventListener("hashchange", onHashChange);
}, []);
useEffect(() => {
if (!embedded) return;
document.documentElement.dataset.embedded = "true";
const initial = window.parent.document.documentElement.dataset.theme;
if (initial === "light" || initial === "dark") applyResolvedTheme(initial);
const onAppearance = (event: Event) => {
const detail = (event as CustomEvent<{ dark?: unknown }>).detail;
if (typeof detail?.dark === "boolean") applyResolvedTheme(detail.dark ? "dark" : "light");
};
window.addEventListener("sfmc:appearance", onAppearance);
return () => window.removeEventListener("sfmc:appearance", onAppearance);
}, []);
const openProject = useCallback((id: string) => {
window.location.hash = `#/p/${encodeURIComponent(id)}`;
}, []);
const goHome = useCallback(() => {
window.location.hash = "#/";
}, []);
return (
<TooltipProvider>
{route.name === "editor" ? (
<EditorPage key={route.projectId} projectId={route.projectId} onExit={goHome} />
) : (
<ProjectList onOpen={openProject} />
)}
<StudioDialogHost />
</TooltipProvider>
);
}
2 changes: 2 additions & 0 deletions modules/sdk/@sfmc-sdk/ui-studio-web/src/app/host.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
/** 内嵌时由 desktop 外壳提供标题栏与外观控制。 */
export const embedded = new URLSearchParams(window.location.search).get("embedded") === "1";
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 偏好写入 localStorage,与 IndexedDB 一样绑在固定 origin(127.0.0.1:3003)。
* index.html 内联脚本用同一 STORAGE_KEY,避免首屏闪深色。
*/
import { PALETTES, applyCssVariables, registerPunctuationFont } from "@sfmc-bds/ui/theme";

export type ThemePref = "system" | "light" | "dark";
export type ResolvedTheme = "light" | "dark";
Expand Down Expand Up @@ -44,7 +45,10 @@ export function systemPrefersDark(): boolean {

/** 把解析结果写到 <html data-theme>。 */
export function applyResolvedTheme(theme: ResolvedTheme): void {
registerPunctuationFont();
applyCssVariables(PALETTES[theme]);
document.documentElement.setAttribute("data-theme", theme);
document.documentElement.style.colorScheme = theme;
}

/** 按偏好解析并应用到文档。 */
Expand Down
Loading
Loading