陶泥儿移除发行包下载功能
- 删除左半面板的「下载发行包」按钮与下载失败弹窗接线 - 退役 useTaonierExport 的 artifactReady 状态及其构建收尾写入 - 删除 tabs/taonier/DownloadFailureDialog.tsx - 同步陶泥儿面板测试与实施计划文档
This commit is contained in:
+1
-13
@@ -229,14 +229,6 @@ export function useTaonierExport({
|
||||
*/
|
||||
const [readSettled, setReadSettled] = useState(false);
|
||||
const [running, setRunning] = useState(false);
|
||||
/**
|
||||
* 这次会话里是否已经成功打出过分区:只有它为真,面板才给「下载产物」。
|
||||
*
|
||||
* 宿主不报告磁盘上有没有产物,所以初值只能是 `false`(哪怕上一轮会话刚打过);成功构建置真、
|
||||
* 构建失败置假——上次成功的那份此刻可能已经被失败的那次覆盖或删掉,不给一个下不了的下载按钮。
|
||||
* 用户改表单不影响它:磁盘上那份仍是可下载的,只是内容旧了。
|
||||
*/
|
||||
const [artifactReady, setArtifactReady] = useState(false);
|
||||
const [dirty, setDirty] = useState(false);
|
||||
/**
|
||||
* 「还没适配」的阻断提示被用户处理过一次就收起:无论选了「发给 code agent」还是「我自己来」,
|
||||
@@ -321,7 +313,6 @@ export function useTaonierExport({
|
||||
setLoading(false);
|
||||
setReadSettled(false);
|
||||
setRunning(false);
|
||||
setArtifactReady(false);
|
||||
setDirty(false);
|
||||
setAdaptPromptDismissed(false);
|
||||
setOnline(null);
|
||||
@@ -775,14 +766,12 @@ export function useTaonierExport({
|
||||
setFailure(null);
|
||||
try {
|
||||
await runTaonierExportBuild(invoke, projectPath);
|
||||
// 构建期间换了项目:这次产物属于上一个项目,不能让新项目出现可下载的按钮。
|
||||
// 构建期间换了项目:这次产物属于上一个项目,不能拿来给新项目发布。
|
||||
if (projectPathRef.current !== projectPath) return false;
|
||||
setArtifactReady(true);
|
||||
return true;
|
||||
} catch (error) {
|
||||
// 旧项目的构建失败不该记到新项目头上。
|
||||
if (projectPathRef.current !== projectPath) return false;
|
||||
setArtifactReady(false);
|
||||
const structured = asTaonierBuildFailure(error);
|
||||
if (!structured) {
|
||||
throwToErrorPool(String(error), error);
|
||||
@@ -955,7 +944,6 @@ export function useTaonierExport({
|
||||
loading,
|
||||
readSettled,
|
||||
running,
|
||||
artifactReady,
|
||||
adaptPromptDismissed,
|
||||
online,
|
||||
onlineState,
|
||||
|
||||
+5
-80
@@ -1,19 +1,10 @@
|
||||
import { type ReactNode, useState } from 'react';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import type { GameDistributionPublishResult } from '../../../../../services/gameDistributionPublish';
|
||||
import {
|
||||
revealExportArtifactInManager,
|
||||
saveExportArtifactToDisk,
|
||||
} from '../../state/exportArtifactDownload';
|
||||
import { TAONIER_ARTIFACT_RELATIVE_PATH } from '../../state/taonierInstruction';
|
||||
import type { TaonierExportState } from '../../state/useTaonierExport';
|
||||
import { PaneButton } from '../common/PaneButton';
|
||||
import { AdaptPromptDialog } from './AdaptPromptDialog';
|
||||
import { ConflictCard } from './ConflictCard';
|
||||
import {
|
||||
DownloadFailureDialog,
|
||||
type TaonierDownloadFailure,
|
||||
} from './DownloadFailureDialog';
|
||||
import { FailureDialog } from './FailureDialog';
|
||||
import { FormCard } from './FormCard';
|
||||
|
||||
@@ -27,10 +18,10 @@ export type TaonierPublishPanel = {
|
||||
/**
|
||||
* 陶泥儿工作台左半:发布表单(线上值跟着每个字段走)+ 唯一的「打包并发布」。
|
||||
*
|
||||
* 没有单独的「打包」步骤:打包是发布的第一步,由 `publishGame` 内部跑 `packageAll()`。用户填完表
|
||||
* 单点一次,就是「打包 + 提交游戏分发」。打包成功后给「下载发行包」——没打过就不给一个下不了的
|
||||
* 按钮。适配入口不在按钮行里——未配置好导出时由阻断弹窗(`AdaptPromptDialog`)给出「让陶泥儿帮我
|
||||
* 调通 / 我自己来」两个选择,失败现场由失败弹窗兜底。
|
||||
* 没有单独的「打包」步骤,也没有「下载发行包」这类本地产物出口:打包是发布的第一步,由
|
||||
* `publishGame` 内部跑 `packageAll()`,用户填完表单点一次,就是「打包 + 提交游戏分发」。适配入口
|
||||
* 不在按钮行里——未配置好导出时由阻断弹窗(`AdaptPromptDialog`)给出「让陶泥儿帮我调通 / 我自己
|
||||
* 来」两个选择,失败现场由失败弹窗兜底。
|
||||
*
|
||||
* 线上值(`read_game_distribution_publication` 的只读投影)在 `FormCard` 里逐字段内联展示,草稿不
|
||||
* 自动回填、不被覆盖;「沿用」是用户显式动作。发布入口按线上状态给「打包并发布 / 打包并更新」,
|
||||
@@ -48,14 +39,6 @@ export function ArtifactsPane({
|
||||
publish: TaonierPublishPanel;
|
||||
}) {
|
||||
const { form, conflict, failure, online, onlineState } = taonier;
|
||||
/**
|
||||
* 下载失败现场:宿主拒绝写盘或对话框打不开时,用弹窗把「哪一份、为什么、下一步」一次说清,
|
||||
* 并给出「在文件管理器中打开原件」这条兜底出口。
|
||||
*/
|
||||
const [downloadFailure, setDownloadFailure] =
|
||||
useState<TaonierDownloadFailure | null>(null);
|
||||
/** 「在文件管理器中打开」自身失败时的一句话;没失败时为空。 */
|
||||
const [revealFailure, setRevealFailure] = useState('');
|
||||
/**
|
||||
* 未适配:`hasScript` 的初值是 `false`,注册表还没读回来(或读失败)时我们不能替项目下
|
||||
* 「还没适配」的结论——那种情况只该显示读取中或失败现场。
|
||||
@@ -73,47 +56,6 @@ export function ArtifactsPane({
|
||||
*/
|
||||
const showAdaptPrompt = needsAdaptation && !taonier.adaptPromptDismissed;
|
||||
|
||||
const downloadArtifact = (): void => {
|
||||
if (!projectPath) return;
|
||||
const relativePath = TAONIER_ARTIFACT_RELATIVE_PATH;
|
||||
void saveExportArtifactToDisk({
|
||||
projectPath,
|
||||
relativePath,
|
||||
fallbackFileName: 'taonier.zip',
|
||||
title: '保存发行包',
|
||||
}).then((result) => {
|
||||
if (result.status !== 'failed') return;
|
||||
setRevealFailure('');
|
||||
setDownloadFailure({
|
||||
kind: 'artifact',
|
||||
relativePath,
|
||||
reason: result.reason,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
/** 兜底出口:让系统文件管理器定位原件;打不开就把原因留在弹窗里。 */
|
||||
const revealDownloadedFile = (): void => {
|
||||
if (!projectPath || !downloadFailure) return;
|
||||
void revealExportArtifactInManager({
|
||||
projectPath,
|
||||
relativePath: downloadFailure.relativePath,
|
||||
}).then((result) => {
|
||||
if (result.status === 'failed') {
|
||||
setRevealFailure(result.reason);
|
||||
return;
|
||||
}
|
||||
// 文件管理器已经弹出:收起弹窗,别挡着用户去看。
|
||||
setDownloadFailure(null);
|
||||
setRevealFailure('');
|
||||
});
|
||||
};
|
||||
|
||||
const dismissDownloadFailure = (): void => {
|
||||
setDownloadFailure(null);
|
||||
setRevealFailure('');
|
||||
};
|
||||
|
||||
const publishDisabled =
|
||||
taonier.running ||
|
||||
publish.publishing ||
|
||||
@@ -212,16 +154,6 @@ export function ArtifactsPane({
|
||||
>
|
||||
{publishLabel}
|
||||
</PaneButton>
|
||||
{/* 下载入口只在打包成功之后出现:宿主不报告磁盘上有没有产物,没打过就不给一个下不了的按钮。 */}
|
||||
{taonier.artifactReady ? (
|
||||
<PaneButton
|
||||
ariaLabel="下载发行包"
|
||||
className="justify-center"
|
||||
onClick={downloadArtifact}
|
||||
>
|
||||
下载发行包
|
||||
</PaneButton>
|
||||
) : null}
|
||||
{publish.result ? (
|
||||
<p
|
||||
role="status"
|
||||
@@ -247,13 +179,6 @@ export function ArtifactsPane({
|
||||
onRepair={taonier.repair}
|
||||
onDismiss={taonier.dismissFailure}
|
||||
/>
|
||||
|
||||
<DownloadFailureDialog
|
||||
failure={downloadFailure}
|
||||
revealFailure={revealFailure}
|
||||
onReveal={revealDownloadedFile}
|
||||
onDismiss={dismissDownloadFailure}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
import { ThemedModal } from '../../../../../components/modal/ThemedModal';
|
||||
import { PaneButton } from '../common/PaneButton';
|
||||
|
||||
/** 哪一份产物没存下来。当前只有发行包一份,留成对象是为了以后加封面之类时有位置放。 */
|
||||
export type TaonierDownloadFailure = {
|
||||
kind: 'artifact';
|
||||
/** 项目内相对路径:用户要在文件管理器里打开的就是它。 */
|
||||
relativePath: string;
|
||||
/** 保存失败的原因(宿主给的中文说明,或一句通用说明)。 */
|
||||
reason: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* 下载失败弹窗:一句「哪一份没存下来」+ 原因 + 兜底出口。
|
||||
*
|
||||
* 保存失败不再只留一行会被滚出视野的小字:左半本来就矮,而这是用户点过下载之后必须看见的结果。
|
||||
* 弹窗给两条路:关掉,或者让系统文件管理器打开原件所在位置自己复制——宿主拒绝写盘时(权限、
|
||||
* 目标不存在、路径被门禁拦下),用户至少还能把项目里那份原件拿出去。
|
||||
*
|
||||
* 与失败弹窗(`FailureDialog`)同一种阻断层:背景点击与 Esc 不生效。宿主的原始错误不在这里展开,
|
||||
* `reason` 只用调用方收好的那一句人话。
|
||||
*/
|
||||
export function DownloadFailureDialog({
|
||||
failure,
|
||||
revealFailure,
|
||||
onReveal,
|
||||
onDismiss,
|
||||
}: {
|
||||
/** 没有下载失败现场时为 `null`,弹窗关着。 */
|
||||
failure: TaonierDownloadFailure | null;
|
||||
/** 「在文件管理器中打开」自身失败时的原因;空串表示没有。 */
|
||||
revealFailure: string;
|
||||
onReveal: () => void;
|
||||
onDismiss: () => void;
|
||||
}) {
|
||||
return (
|
||||
<ThemedModal
|
||||
open={failure !== null}
|
||||
ariaLabel="下载失败"
|
||||
onClose={onDismiss}
|
||||
closeOnBackdrop={false}
|
||||
closeOnEscape={false}
|
||||
panelClassName="w-full max-w-md rounded-2xl border border-(--platform-surface-border) p-4"
|
||||
>
|
||||
<p className="text-sm leading-relaxed text-(--platform-text-strong)">
|
||||
{failure ? '没能把发行包保存到你选的位置。' : null}
|
||||
</p>
|
||||
{failure ? (
|
||||
<p className="mt-2 text-xs leading-relaxed text-(--platform-text-base)">
|
||||
{failure.reason}
|
||||
</p>
|
||||
) : null}
|
||||
<p className="mt-2 text-xs leading-relaxed text-(--platform-text-base)">
|
||||
也可以让系统在文件管理器里打开它原来的位置,自己复制过去。
|
||||
</p>
|
||||
{revealFailure ? (
|
||||
<p role="status" className="mt-2 text-xs text-(--platform-warm-text)">
|
||||
{revealFailure}
|
||||
</p>
|
||||
) : null}
|
||||
<div className="mt-4 flex flex-wrap justify-end gap-2">
|
||||
<PaneButton onClick={onDismiss}>关闭</PaneButton>
|
||||
<PaneButton variant="primary" onClick={onReveal}>
|
||||
在文件管理器中打开
|
||||
</PaneButton>
|
||||
</div>
|
||||
</ThemedModal>
|
||||
);
|
||||
}
|
||||
@@ -273,6 +273,12 @@ describe('交给 code agent 的指令', () => {
|
||||
path: TAONIER_ARTIFACT_RELATIVE_PATH,
|
||||
}),
|
||||
).toBe('导出跑完了,但没有生成代码包。可以让陶泥儿看看。');
|
||||
expect(
|
||||
describeTaonierFailure({
|
||||
type: 'projectUnavailable',
|
||||
cause: 'package.json 中的 build:taonier 脚本已变化',
|
||||
}),
|
||||
).toBe('这个项目的导出配置对不上,需要先按提示调整项目后再试。');
|
||||
});
|
||||
|
||||
it('分类的判据是要改的东西在不在项目代码里', () => {
|
||||
@@ -309,6 +315,12 @@ describe('交给 code agent 的指令', () => {
|
||||
cause: '项目根不可用:找不到目录',
|
||||
}),
|
||||
).toBe(TaonierFailureRepairability.OnlyHumanRepairable);
|
||||
expect(
|
||||
classifyTaonierFailure({
|
||||
type: 'projectUnavailable',
|
||||
cause: 'package.json 中的 build:taonier 脚本已变化',
|
||||
}),
|
||||
).toBe(TaonierFailureRepairability.CodeAgentRepairable);
|
||||
});
|
||||
|
||||
it('字段红字交给 agent 的指令带上红字原话、当前值与落点', () => {
|
||||
@@ -419,6 +431,10 @@ describe('命令失败的载体', () => {
|
||||
},
|
||||
{ type: 'commandDenied', commandId: 'command.exec' },
|
||||
{ type: 'artifactMissing', path: TAONIER_ARTIFACT_RELATIVE_PATH },
|
||||
{
|
||||
type: 'projectUnavailable',
|
||||
cause: 'package.json 中的 build:taonier 脚本已变化',
|
||||
},
|
||||
{ type: 'exportUnavailable', cause: '项目根不可用' },
|
||||
];
|
||||
for (const payload of buildCases) {
|
||||
@@ -911,7 +927,7 @@ describe('hook 状态', () => {
|
||||
});
|
||||
|
||||
describe('面板左半', () => {
|
||||
it('只有一颗「打包并发布」入口,发行包只在打包成功后给下载入口', () => {
|
||||
it('只有一颗「打包并发布」入口,没有打包或下载产物这类旁路', () => {
|
||||
const publish = vi.fn();
|
||||
renderPane(fakeTaonier({ online: ONLINE_FIRST_PUBLISH_VALUES }), {
|
||||
publishing: false,
|
||||
@@ -920,13 +936,10 @@ describe('面板左半', () => {
|
||||
});
|
||||
// 没有单独的「打包」步骤:打包是发布的第一步,由发布入口内部跑。
|
||||
expect(screen.queryByRole('button', { name: '打包' })).toBeNull();
|
||||
// 没有本地产物出口:产物只服务发布,不给用户单独下载。
|
||||
expect(screen.queryByRole('button', { name: '下载发行包' })).toBeNull();
|
||||
fireEvent.click(screen.getByRole('button', { name: '打包并发布' }));
|
||||
expect(publish).toHaveBeenCalledTimes(1);
|
||||
expect(screen.queryByRole('button', { name: '下载发行包' })).toBeNull();
|
||||
|
||||
cleanup();
|
||||
renderPane(fakeTaonier({ artifactReady: true }));
|
||||
screen.getByRole('button', { name: '下载发行包' });
|
||||
});
|
||||
|
||||
it('线上值读取失败时不留手动入口,提示正在自动重试', () => {
|
||||
@@ -1014,6 +1027,25 @@ describe('面板左半', () => {
|
||||
).toBeTruthy();
|
||||
// 要改的东西不在项目代码里,不给陶泥儿入口。
|
||||
expect(screen.queryByRole('button', { name: '让陶泥儿来修' })).toBeNull();
|
||||
|
||||
// 项目侧预检没过(脚本漂移等):要改的东西在项目代码里,仍给陶泥儿入口。
|
||||
cleanup();
|
||||
renderPane(
|
||||
fakeTaonier({
|
||||
failure: {
|
||||
type: 'projectUnavailable',
|
||||
cause: 'package.json 中的 build:taonier 脚本已变化',
|
||||
} as TaonierCardFailure,
|
||||
repair,
|
||||
}),
|
||||
);
|
||||
expect(
|
||||
screen.getByText(
|
||||
'这个项目的导出配置对不上,需要先按提示调整项目后再试。',
|
||||
),
|
||||
).toBeTruthy();
|
||||
fireEvent.click(screen.getByRole('button', { name: '让陶泥儿来修' }));
|
||||
expect(repair).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it('字段被限制挡下时给出「交给陶泥儿」', () => {
|
||||
@@ -1100,7 +1132,6 @@ function fakeTaonier(
|
||||
loading: false,
|
||||
readSettled: true,
|
||||
running: false,
|
||||
artifactReady: false,
|
||||
adaptPromptDismissed: false,
|
||||
online: null,
|
||||
onlineState: 'ready',
|
||||
|
||||
@@ -189,7 +189,7 @@
|
||||
- `export/state/useTaonierExport.ts`:自动保存(baseHash)、2s 轮询、冲突、失败、`packageAll`、`adapt`、`repair`、`fillForm`、`handFieldErrorsToAgent`、`online` 与「沿用线上值」动作;返回面与 xhs hook 同构。
|
||||
|
||||
**Step 3 UI**
|
||||
- `tabs/taonier/`:`Page.tsx`(2 列)、`ArtifactsPane.tsx`(表单槽/读取中/重试/打包/下载产物/冲突/线上值卡/发布链接/三个 dialog)、`FormCard.tsx`(逐字段红字 + 帮我填 + 交给陶泥儿)、`OnlineValuesCard.tsx`(线上值只读 + 沿用操作)、`ConflictCard.tsx`、`FailureDialog.tsx`、`AdaptPromptDialog.tsx`、`DownloadFailureDialog.tsx`。
|
||||
- `tabs/taonier/`:`Page.tsx`(2 列)、`ArtifactsPane.tsx`(表单槽/读取中/重试/冲突/唯一的「打包并发布」入口/两个 dialog)、`FormCard.tsx`(逐字段红字 + 帮我填 + 交给陶泥儿 + 线上值逐字段内联沿用)、`ConflictCard.tsx`、`FailureDialog.tsx`、`AdaptPromptDialog.tsx`。
|
||||
- 复用 `common/PaneButton`、`ThemedModal`,去掉旧 `PublishPane` 对 `GameDistributionPublishFormView` 的依赖。
|
||||
|
||||
**Step 4 发布媒体**
|
||||
|
||||
Reference in New Issue
Block a user