@kubohiroya/turbowarp-plus-editor-git 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +22 -6
- package/README.md +9 -1
- package/dist/block-diff-preview.d.ts +18 -2
- package/dist/file-diff-view.d.ts +3 -1
- package/dist/index.js +11 -4
- package/dist/{libesm-OWRWzmu_.js → project-asset-paths-KV715791.js} +23 -1
- package/dist/project-asset-paths.d.ts +12 -0
- package/dist/project-assets.d.ts +19 -0
- package/dist/project-source-diff.d.ts +5 -1
- package/dist/project-source-diff.js +10 -7
- package/dist/project-source-review-model.d.ts +4 -1
- package/dist/project-source-review.d.ts +6 -2
- package/dist/{repository-DplMQiuo.js → repository-BQkADJmK.js} +71 -3
- package/dist/repository.d.ts +10 -0
- package/dist/repository.js +1 -1
- package/dist/{scratch-block-diff-renderer-CLYoOIB2.js → scratch-block-diff-renderer-xS8OYsRl.js} +453 -87
- package/dist/scratch-block-diff-renderer.d.ts +69 -6
- package/dist/style.css +31 -2
- package/package.json +7 -2
- package/src/block-diff-preview.tsx +89 -8
- package/src/file-diff-view.tsx +4 -3
- package/src/github-pull-requests.ts +73 -6
- package/src/index.tsx +5 -2
- package/src/project-asset-paths.ts +33 -0
- package/src/project-assets.ts +66 -0
- package/src/project-source-diff.css +31 -2
- package/src/project-source-diff.tsx +14 -7
- package/src/project-source-review-model.ts +4 -1
- package/src/project-source-review.tsx +56 -23
- package/src/repository.ts +58 -2
- package/src/scratch-block-diff-renderer.ts +129 -11
package/README.ja.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
TurboWarp Plus Editor のファイルメニューに、読み取り専用の「ローカル Git」を追加します。リポジトリルートを一度選び、配下の sb3-toolchain v1 作品フォルダをパネルから開きます。作品の編集と保存には Editor の project-folder 接続を使います。
|
|
6
6
|
|
|
7
|
-
ステージ済み・未ステージ・未追跡の変更、現在のブランチ、HEAD、ローカルブランチと直近50件の履歴を表示します。ファイルを選ぶとテキスト差分またはバイナリのサイズを確認できます。作品データ(`project.source.json
|
|
7
|
+
ステージ済み・未ステージ・未追跡の変更、現在のブランチ、HEAD、ローカルブランチと直近50件の履歴を表示します。ファイルを選ぶとテキスト差分またはバイナリのサイズを確認できます。作品データ(`project.source.json`)は、ホストがブロックの描画関数を渡していればステージ・スプライト別、コード別のブロックで比べ、「テキスト」に切り替えると行単位の差分を表示します。ステージ・スプライトの見出しには、その版の現在のコスチューム(ステージは背景)のサムネイルを添えます。素材は比較する版から読みます(ステージ済みは HEAD とインデックス、未ステージはインデックスと作業ツリー、未追跡は作業ツリー、コミットは第一親とそのコミット)。素材のフォルダはその版の `sb3-source.json` の `assetsDirectory`(既定は `assets`)です。読めない版では名前だけを示します。コミットを選ぶと第一親との差分を表示します。作品を選んでいるときは、変更一覧と差分をそのフォルダに絞ります。
|
|
8
8
|
|
|
9
9
|
パネルを開いている間は、2 秒ごと、ウィンドウにフォーカスが戻ったとき、対応環境では `FileSystemObserver` の通知でリポジトリを確認します。HEAD、インデックス、`packed-refs`、`config`、現在のブランチの参照、選択中の作品フォルダのファイル(作品を選んでいない場合は作業ツリー全体、5,000 件まで)のサイズと更新時刻を比べます。変更があり、差分やコミットを開いていなければ読み直します。差分・コミット・プルリクエストのレビューを開いている間は表示を保ち、**再取得** を案内します。Git がメタデータをロックしている間は確認を待ちます。**作品を再読込** では、Editor の未保存編集を確認してから同じ作品を開き直します。Git の閲覧には読み取り専用のアダプターを使い、インデックスの更新を無効にします。読み取り中にファイルや Git の状態が変わった場合は、外部操作の完了後に再取得します。
|
|
10
10
|
|
|
@@ -63,17 +63,23 @@ const diffFile = new Blob([patch], {type: 'text/x-diff'});
|
|
|
63
63
|
|
|
64
64
|
`getBlockly` は読み込み済みの Scratch Blocks、`deserializeBlocks` と `VMBlocks` はホストの Scratch VM が提供するものを使います。表示用の VM ブロックコンテナーとワークスペースは差分ごとに新しく作ります。ホストに定義がない拡張ブロックやプレビュー非対応の mutation には描画不可と表示します。
|
|
65
65
|
|
|
66
|
-
ブロックはステージまたはスプライト名とブロック ID
|
|
66
|
+
ブロックはステージまたはスプライト名とブロック ID で照合します。変更されたブロックとその入力を読み取り専用で並べ、追加・削除・入力値などの注釈を付けます。「元のコード内で強調」を選ぶと、つながったコードの中で対象を確認できます。オレンジの枠は変更された入力欄やフィールドを示します。画像・音声など、コード以外に変更があればその旨を表示します。切り戻す場合は、このコンポーネントの呼び出しを外します。
|
|
67
67
|
|
|
68
68
|
### 編集画面のブロックと照合する
|
|
69
69
|
|
|
70
|
-
`locateBlock` に `createScratchBlocksDiffLocator(resolveWorkspace)` を渡すと、各版のプレビューに「編集画面で強調」が出ます。`resolveWorkspace` は指定された `version`、対象のステージ・スプライト、ブロック ID に対応するワークスペースを返します。ホストは作品と版を照合し、必要なら対象を切り替えてブロックが表示されるまで待ちます。一致する編集画面がなければ `null`
|
|
70
|
+
`locateBlock` に `createScratchBlocksDiffLocator(resolveWorkspace)` を渡すと、各版のプレビューに「編集画面で強調」が出ます。`resolveWorkspace` は指定された `version`、対象のステージ・スプライト、ブロック ID に対応するワークスペースを返します。ホストは作品と版を照合し、必要なら対象を切り替えてブロックが表示されるまで待ちます。一致する編集画面がなければ `null` を返します。削除済みのブロックは変更前のコードを展開して確認できます。
|
|
71
71
|
|
|
72
72
|
ロケーターは一致したブロックを中央に移動し、差分ビューと同じ入力欄やフィールドを強調します。別の差分の選択・差分ファイルの切り替え・ビューの終了で強調を解除します。
|
|
73
73
|
|
|
74
|
+
### 表示枠
|
|
75
|
+
|
|
76
|
+
変更前・変更後の各枠は読み取り専用の作業領域です。Editor のコードエリアと同じ倍率でブロックを描き、枠の高さをブロックに合わせます(160〜420 px。枠の右下をドラッグすると高くできます)。ドラッグとスクロールバーで動かし、拡大縮小ボタンと Ctrl+ホイール(トラックパッドのピンチ)で拡大縮小します。ホイールだけの操作は、レビュー全体のスクロールに使います。同じ比較の枠は拡大率・高さ・スクロール位置を連動させ、「変更前と変更後を連動して動かす」で切り離せます。
|
|
77
|
+
|
|
78
|
+
`createScratchBlocksDiffRenderer` の `attachNavigation(workspace, blockly, watermark)` に、ホストのコードエリアと同じ操作部品を付ける関数を渡せます。TurboWarp Plus Editor は右下にコスチュームのサムネイル、ミニマップ、拡大縮小ボタンの列を並べます。`watermark` はその版の現在のコスチュームを表示する要素で、素材を読む `loadAsset` があるときに渡します。`attachNavigation` がなければ Scratch Blocks の拡大縮小ボタンを使います。独自の描画関数は `{dispose, view}` を返すと、`createPreviewSync` による連動に加われます。
|
|
79
|
+
|
|
74
80
|
## PR 単位でレビューする
|
|
75
81
|
|
|
76
|
-
同じエントリの `ProjectSourceReview` は、作品ファイルを複数含む変更セットをまとめてレビューするためのビューです。`title`・`description`・`verification` には PR 作者が記した目的と確認観点を渡します。`files` に作品ファイルの `{path, patch, beforeSource?}` や、関連ファイルの `{path, status, summary?}` を渡します。ブロックを描画する `renderBlock` は個別差分ビューと共通です。
|
|
82
|
+
同じエントリの `ProjectSourceReview` は、作品ファイルを複数含む変更セットをまとめてレビューするためのビューです。`title`・`description`・`verification` には PR 作者が記した目的と確認観点を渡します。`files` に作品ファイルの `{path, patch, beforeSource?, loadAsset?}` や、関連ファイルの `{path, status, summary?}` を渡します。ブロックを描画する `renderBlock` は個別差分ビューと共通です。
|
|
77
83
|
|
|
78
84
|
```tsx
|
|
79
85
|
<ProjectSourceReview
|
|
@@ -89,13 +95,15 @@ const diffFile = new Blob([patch], {type: 'text/x-diff'});
|
|
|
89
95
|
/>
|
|
90
96
|
```
|
|
91
97
|
|
|
92
|
-
|
|
98
|
+
作品ファイルはステージ・スプライトごとに整理し、変更のあったコードだけを比較項目にします。ひとかたまりのブロックは Editor のタブ名と同じく「コード」と呼び、見出しには「コードの変更 · N か所」と示します。各項目は帽子ブロックの名前で呼びます。分割・結合したコードも、両版に共通するブロック ID を使って一項目にまとめます。展開すると前後のコードをブロックで表示し、編集箇所を枠で示します。変数、コスチュームなどの変更は別に示します。関連ファイルの要約はホストから渡した情報を表示します。
|
|
99
|
+
|
|
100
|
+
`loadAsset` は `({version, md5ext, signal}) => Promise<Blob | null>` の形で、`version`(`before`/`after`)の作品フォルダの素材を読みます。渡すと、ステージ・スプライトの見出しにその版の現在のコスチューム(ステージは背景)のサムネイルを添えます。変更前後でコスチュームが違えば「変更前 → 変更後」と並べます。関数がないとき、または画像を読めないときは名前だけを示します。`createGitHubPullRequests` が返す作品ファイルには、変更前(マージベース)と変更後(head)の素材を raw.githubusercontent.com から読む `loadAsset` が付きます。この読み込みは GitHub API の利用上限を消費せず、1 ファイル 10 MiB までです。
|
|
93
101
|
|
|
94
102
|
確認済みの印は表示中の比較データに紐づくローカル状態です。PR 本文・ファイル差分の取得と GitHub へのレビュー送信は、呼び出し側が担当します。
|
|
95
103
|
|
|
96
104
|
### 2 つの版を直接比べる
|
|
97
105
|
|
|
98
|
-
`ProjectSourceComparison` は、同じ比較を 1 つの作品ファイルについて表示します。変更前と変更後の `project.source.json` の文字列を `before`・`after` に渡します。ファイルがない側は `null`
|
|
106
|
+
`ProjectSourceComparison` は、同じ比較を 1 つの作品ファイルについて表示します。変更前と変更後の `project.source.json` の文字列を `before`・`after` に渡します。ファイルがない側は `null` とし、すべてのコードを追加または削除として示します。`loadAsset` は `ProjectSourceReview` の作品ファイルと同じで、見出しのサムネイルに使います。ローカル Git パネルは作業ツリーと履歴の差分にこのビューを使います。
|
|
99
107
|
|
|
100
108
|
```tsx
|
|
101
109
|
<ProjectSourceComparison before={beforeText} after={afterText} renderBlock={renderBlock} locale="ja"
|
|
@@ -103,3 +111,11 @@ const diffFile = new Blob([patch], {type: 'text/x-diff'});
|
|
|
103
111
|
```
|
|
104
112
|
|
|
105
113
|
どちらかの版を作品として読めないときは理由を示し、`fallback` を表示します。`fallback` がなければエラーだけを示します。
|
|
114
|
+
|
|
115
|
+
## 公開
|
|
116
|
+
|
|
117
|
+
npm への公開は `.github/workflows/release.yml` が行う。`package.json` の版と同じ `v<版>` のタグを
|
|
118
|
+
`main` のコミットに push すると、テストを通したうえで npm に入れるアーカイブを作り、GitHub Release に
|
|
119
|
+
添付してから npm に公開し、`latest` と `next` をその版に向ける。認証はトークンではなく npm の
|
|
120
|
+
Trusted Publisher(OIDC)で、provenance を付けて公開する。npm 側の設定にはこのワークフローの
|
|
121
|
+
ファイル名をそのまま登録するため、改名するときは npm の設定も合わせる。
|
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
Local Git adds a read-only panel to the TurboWarp Plus Editor File menu. Select the repository root once, then browse to an sb3-toolchain v1 project folder inside it. Project editing and saving are supplied by the Editor's project-folder integration.
|
|
6
6
|
|
|
7
|
-
The panel separates staged, unstaged, and untracked changes. It shows the current branch, HEAD, local branches, and the latest 50 commits. Select a file to inspect its text diff or binary size. When the host supplies a block renderer, project data (`project.source.json`) is compared as blocks by stage, sprite and
|
|
7
|
+
The panel separates staged, unstaged, and untracked changes. It shows the current branch, HEAD, local branches, and the latest 50 commits. Select a file to inspect its text diff or binary size. When the host supplies a block renderer, project data (`project.source.json`) is compared as blocks by stage, sprite and code, and **Text** switches to the line diff. Each stage and sprite heading shows a thumbnail of its current costume (the backdrop for the stage) in each version. Assets are read from the compared versions: HEAD and the index for staged changes, the index and the working tree for unstaged changes, the working tree for untracked files, and the first parent and the commit for history. The assets folder is the `assetsDirectory` of that version's `sb3-source.json` (`assets` by default). When an asset cannot be read, only the name is shown. Pull request reviews read the same thumbnails from raw.githubusercontent.com at the merge base and head, without using the GitHub API rate limit. Select a commit to compare its files with its first parent. The selected project limits the file list and diffs to that folder.
|
|
8
8
|
|
|
9
9
|
While the panel is open, it checks the repository every two seconds, when the window regains focus, and on `FileSystemObserver` notifications where available. It compares the sizes and modification times of HEAD, the index, `packed-refs`, `config`, the current branch ref, and the files in the selected project folder (the whole work tree when no project is selected, up to 5,000 files). When something changed and no diff or commit is open, the panel reads the repository again. While a diff, a commit or a pull request review is open, it keeps the view and asks you to **Refresh** instead. Checks wait while Git holds a lock on its metadata. **Reload Project** opens the selected project again using the Editor's unsaved-edit confirmation. Git inspection uses a read-only adapter and isomorphic-git with index refresh disabled. If the repository changes during a read, refresh after the other operation completes.
|
|
10
10
|
|
|
@@ -34,3 +34,11 @@ Each receive-pack POST carries a new UUID v4 in `Git-Push-Id`. A browser replay
|
|
|
34
34
|
`npm test` runs the local Git tests and a headless Smart HTTP fixture on IPv4 loopback using `git-http-backend`. The fixture uses disposable repositories and test credentials. [Proxy, authentication and CORS contract](https://github.com/kubohiroya/turbowarp-plus-editor/issues/315).
|
|
35
35
|
|
|
36
36
|
When the Editor host supplies connection settings, the Local Git panel offers fetch, fast-forward pull and push for the current branch and its existing GitHub HTTPS `origin`. Authorize push when connecting to enable the explicit push action. Fetch updates Git metadata. Pull requires a stopped project, a clean worktree, and regular non-executable files. Pull stages the changes, rechecks the local state, and then applies them. Reopen the project from the panel after a successful pull.
|
|
37
|
+
|
|
38
|
+
## Release
|
|
39
|
+
|
|
40
|
+
`.github/workflows/release.yml` publishes to npm. Push a `v<version>` tag that names the
|
|
41
|
+
`package.json` version on a `main` commit: the workflow runs the tests, packs the archive, attaches it
|
|
42
|
+
to the GitHub Release, publishes it to npm and points `latest` and `next` at it. It authenticates with
|
|
43
|
+
npm Trusted Publishing (OIDC), not a token, and publishes with provenance. The npm settings name this
|
|
44
|
+
workflow file, so rename both together.
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { BlockPart, BlockSide } from './project-source-patch.js';
|
|
3
|
-
import type { ProjectBlockRenderer, ScriptBlockMark } from './scratch-block-diff-renderer.js';
|
|
3
|
+
import type { BlockPreviewView, ProjectBlockRenderer, ScriptBlockMark } from './scratch-block-diff-renderer.js';
|
|
4
|
+
import type { ProjectAssetLoader, ProjectVersion } from './project-assets.js';
|
|
4
5
|
export type LocationStatus = 'locating' | 'located' | 'missingLocation';
|
|
5
6
|
type PreviewLabels = Record<'absent' | 'loading' | 'unavailable', string> & Partial<Record<LocationStatus | 'locate', string>>;
|
|
6
|
-
|
|
7
|
+
/** Keeps the previews of one comparison at the same zoom, height and scroll position while they are linked. */
|
|
8
|
+
export interface PreviewSync {
|
|
9
|
+
add(view: BlockPreviewView): () => void;
|
|
10
|
+
setLinked(linked: boolean): void;
|
|
11
|
+
}
|
|
12
|
+
export declare function createPreviewSync(): PreviewSync;
|
|
13
|
+
/** One sync group per comparison, with the checkbox that links or unlinks its previews. */
|
|
14
|
+
export declare function usePreviewSync(locale: string): {
|
|
15
|
+
sync: PreviewSync;
|
|
16
|
+
toggle: React.JSX.Element;
|
|
17
|
+
};
|
|
18
|
+
export declare function BlockPreview({ side, title, parts, marks, renderBlock, withContext, labels, onLocate, locationStatus, version, loadAsset, sync }: {
|
|
7
19
|
side?: BlockSide;
|
|
8
20
|
title: string;
|
|
9
21
|
parts?: readonly BlockPart[];
|
|
@@ -13,5 +25,9 @@ export declare function BlockPreview({ side, title, parts, marks, renderBlock, w
|
|
|
13
25
|
labels: PreviewLabels;
|
|
14
26
|
onLocate?: () => void;
|
|
15
27
|
locationStatus?: LocationStatus;
|
|
28
|
+
/** Which version this preview shows, for its costume thumbnail. */
|
|
29
|
+
version?: ProjectVersion;
|
|
30
|
+
loadAsset?: ProjectAssetLoader;
|
|
31
|
+
sync?: PreviewSync;
|
|
16
32
|
}): React.JSX.Element;
|
|
17
33
|
export {};
|
package/dist/file-diff-view.d.ts
CHANGED
|
@@ -2,15 +2,17 @@ import React from 'react';
|
|
|
2
2
|
import type { FileChange, FileDiff } from './repository.js';
|
|
3
3
|
import type { labelsFor } from './messages.js';
|
|
4
4
|
import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
|
|
5
|
+
import type { ProjectAssetLoader } from './project-assets.js';
|
|
5
6
|
/** Project sources are the files the panel opens as projects. */
|
|
6
7
|
export declare const isProjectSource: (path: string) => boolean;
|
|
7
8
|
/**
|
|
8
9
|
* Shows one changed file. Project sources are compared as blocks by default, with their text diff one switch away.
|
|
9
10
|
*/
|
|
10
|
-
export declare function FileDiffView({ change, diff, labels, locale, renderBlock }: {
|
|
11
|
+
export declare function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset }: {
|
|
11
12
|
change: FileChange;
|
|
12
13
|
diff: FileDiff | null;
|
|
13
14
|
labels: ReturnType<typeof labelsFor>;
|
|
14
15
|
locale: string;
|
|
15
16
|
renderBlock?: ProjectBlockRenderer;
|
|
17
|
+
loadAsset?: ProjectAssetLoader;
|
|
16
18
|
}): React.JSX.Element;
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as gitHubRepositoryFromOrigin, i as createGitHubPullRequests, o as ProjectSourceComparison, r as PullRequestError, s as ProjectSourceReview, t as createScratchBlocksDiffRenderer } from "./scratch-block-diff-renderer-
|
|
1
|
+
import { a as gitHubRepositoryFromOrigin, i as createGitHubPullRequests, o as ProjectSourceComparison, r as PullRequestError, s as ProjectSourceReview, t as createScratchBlocksDiffRenderer } from "./scratch-block-diff-renderer-xS8OYsRl.js";
|
|
2
2
|
import { n as init_objectSpread2, r as __commonJSMin, t as _objectSpread2$1 } from "./objectSpread2-Dkd68nJH.js";
|
|
3
|
-
import { n as GitPanelError, r as createReadOnlyFs, t as openRepository } from "./repository-
|
|
3
|
+
import { n as GitPanelError, r as createReadOnlyFs, t as openRepository } from "./repository-BQkADJmK.js";
|
|
4
4
|
import * as m from "react";
|
|
5
5
|
import React, { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
6
6
|
import { render, unmountComponentAtNode } from "react-dom";
|
|
@@ -2684,7 +2684,7 @@ var isProjectSource = (path) => path.split("/").pop() === "project.source.json";
|
|
|
2684
2684
|
/**
|
|
2685
2685
|
* Shows one changed file. Project sources are compared as blocks by default, with their text diff one switch away.
|
|
2686
2686
|
*/
|
|
2687
|
-
function FileDiffView({ change, diff, labels, locale, renderBlock }) {
|
|
2687
|
+
function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset }) {
|
|
2688
2688
|
var _change$oldMode, _change$newMode;
|
|
2689
2689
|
const [view, setView] = useState("blocks");
|
|
2690
2690
|
const names = locale.startsWith("ja") ? ja$2 : en$2;
|
|
@@ -2712,6 +2712,7 @@ function FileDiffView({ change, diff, labels, locale, renderBlock }) {
|
|
|
2712
2712
|
renderBlock,
|
|
2713
2713
|
locale,
|
|
2714
2714
|
fallback: text,
|
|
2715
|
+
loadAsset,
|
|
2715
2716
|
before: change.before === null ? null : sources.before,
|
|
2716
2717
|
after: change.after === null ? null : sources.after
|
|
2717
2718
|
}) : text);
|
|
@@ -3438,6 +3439,11 @@ function GitPanel({ document, host, controller }) {
|
|
|
3438
3439
|
first === null || first === void 0 || first.focus();
|
|
3439
3440
|
}
|
|
3440
3441
|
};
|
|
3442
|
+
const loadAsset = useMemo(() => state.repository && state.snapshot && state.selected ? state.repository.assetLoader(state.snapshot, state.selected) : void 0, [
|
|
3443
|
+
state.repository,
|
|
3444
|
+
state.snapshot,
|
|
3445
|
+
state.selected
|
|
3446
|
+
]);
|
|
3441
3447
|
if (!visible) return null;
|
|
3442
3448
|
const groups = state.commit ? ["commit"] : [
|
|
3443
3449
|
"staged",
|
|
@@ -3513,7 +3519,8 @@ function GitPanel({ document, host, controller }) {
|
|
|
3513
3519
|
diff: state.diff,
|
|
3514
3520
|
labels,
|
|
3515
3521
|
locale,
|
|
3516
|
-
renderBlock: host.renderReviewBlock
|
|
3522
|
+
renderBlock: host.renderReviewBlock,
|
|
3523
|
+
loadAsset
|
|
3517
3524
|
}) : /* @__PURE__ */ React.createElement("p", null, labels.select))), /* @__PURE__ */ React.createElement("footer", null, labels.permissions)));
|
|
3518
3525
|
}
|
|
3519
3526
|
function mountGitPanel({ vm, document, host }) {
|
|
@@ -736,4 +736,26 @@ function splitLines(text) {
|
|
|
736
736
|
else result.push(result.pop().slice(0, -1));
|
|
737
737
|
return result;
|
|
738
738
|
}
|
|
739
|
-
|
|
739
|
+
// src/project-asset-paths.ts
|
|
740
|
+
/** The largest asset a thumbnail reads. */
|
|
741
|
+
var assetLimit = 10485760;
|
|
742
|
+
/** The largest sb3-source.json read to find the assets directory. */
|
|
743
|
+
var folderSettingsLimit = 1048576;
|
|
744
|
+
/** Asset names are plain `<md5>.<ext>` file names; anything else could address a path outside the assets directory. */
|
|
745
|
+
var isAssetName = (name) => typeof name === "string" && /^[0-9A-Za-z_-]{1,128}\.[0-9A-Za-z]{1,8}$/.test(name);
|
|
746
|
+
/**
|
|
747
|
+
* The assets directory named by one version of a project folder's `sb3-source.json`, as path segments relative to the
|
|
748
|
+
* folder. `assets` when that file or its `assetsDirectory` field is missing or unreadable. `null` when the field names
|
|
749
|
+
* anything other than a plain relative path inside the folder.
|
|
750
|
+
*/
|
|
751
|
+
function assetsDirectoryParts(settings) {
|
|
752
|
+
let value = "assets";
|
|
753
|
+
if (settings !== null) try {
|
|
754
|
+
const parsed = JSON.parse(settings);
|
|
755
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) && "assetsDirectory" in parsed) value = parsed.assetsDirectory;
|
|
756
|
+
} catch (_unused) {}
|
|
757
|
+
if (typeof value !== "string" || value.length > 1024) return null;
|
|
758
|
+
const parts = value.split("/");
|
|
759
|
+
return parts.every((part) => part && part !== "." && part !== ".." && part.toLowerCase() !== ".git" && !/[\\\0-\x1f\x7f]/.test(part)) ? parts : null;
|
|
760
|
+
}
|
|
761
|
+
export { createTwoFilesPatch as a, isAssetName as i, assetsDirectoryParts as n, hasOnlyUnixLineEndings as o, folderSettingsLimit as r, hasOnlyWinLineEndings as s, assetLimit as t };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** The largest asset a thumbnail reads. */
|
|
2
|
+
export declare const assetLimit: number;
|
|
3
|
+
/** The largest sb3-source.json read to find the assets directory. */
|
|
4
|
+
export declare const folderSettingsLimit: number;
|
|
5
|
+
/** Asset names are plain `<md5>.<ext>` file names; anything else could address a path outside the assets directory. */
|
|
6
|
+
export declare const isAssetName: (name: unknown) => name is string;
|
|
7
|
+
/**
|
|
8
|
+
* The assets directory named by one version of a project folder's `sb3-source.json`, as path segments relative to the
|
|
9
|
+
* folder. `assets` when that file or its `assetsDirectory` field is missing or unreadable. `null` when the field names
|
|
10
|
+
* anything other than a plain relative path inside the folder.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assetsDirectoryParts(settings: string | null): string[] | null;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ProjectTarget } from './project-source-patch.js';
|
|
2
|
+
export type ProjectVersion = 'before' | 'after';
|
|
3
|
+
/**
|
|
4
|
+
* Reads one asset of one version of a project file by its `md5ext` name (for example `0123…cdef.svg`), from the
|
|
5
|
+
* assets directory of that project. Resolves `null` when that version has no readable file of that name.
|
|
6
|
+
*/
|
|
7
|
+
export type ProjectAssetLoader = (request: {
|
|
8
|
+
version: ProjectVersion;
|
|
9
|
+
md5ext: string;
|
|
10
|
+
signal: AbortSignal;
|
|
11
|
+
}) => Promise<Blob | null>;
|
|
12
|
+
/** The costume a sprite shows, or the backdrop the stage shows, when the project data names an image file for it. */
|
|
13
|
+
export declare function currentCostume(target: ProjectTarget): {
|
|
14
|
+
name: string;
|
|
15
|
+
md5ext: string;
|
|
16
|
+
type: string;
|
|
17
|
+
} | null;
|
|
18
|
+
/** Loads the current costume of one version of a target as an object URL for an image, or `null` while it is unavailable. */
|
|
19
|
+
export declare function useCostumeImage(target: ProjectTarget | undefined, version: ProjectVersion, loadAsset?: ProjectAssetLoader): string | null;
|
|
@@ -9,8 +9,12 @@ export { createScratchBlocksDiffLocator } from './block-diff-location.js';
|
|
|
9
9
|
export type { ProjectBlockLocation, ProjectBlockLocator, ScratchBlockLocationWorkspace } from './block-diff-location.js';
|
|
10
10
|
export type { BlockPart } from './project-source-patch.js';
|
|
11
11
|
export { createProjectSourceDiff } from './project-source-patch.js';
|
|
12
|
+
export { currentCostume } from './project-assets.js';
|
|
13
|
+
export type { ProjectAssetLoader, ProjectVersion } from './project-assets.js';
|
|
12
14
|
export { createScratchBlocksDiffRenderer } from './scratch-block-diff-renderer.js';
|
|
13
|
-
export type { ProjectBlockRenderer, ScratchBlockDiffRendererOptions, ScratchBlocksPort } from './scratch-block-diff-renderer.js';
|
|
15
|
+
export type { BlockPreviewView, BlockPreviewViewport, ProjectBlockRenderer, ProjectBlockRendering, ScratchBlockDiffRendererOptions, ScratchBlocksPort } from './scratch-block-diff-renderer.js';
|
|
16
|
+
export { createPreviewSync } from './block-diff-preview.js';
|
|
17
|
+
export type { PreviewSync } from './block-diff-preview.js';
|
|
14
18
|
export interface ProjectSourceBlockDiffProps {
|
|
15
19
|
patch: string;
|
|
16
20
|
/** Supply the original JSON if the unified patch omits context. */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as gitHubRepositoryFromOrigin, c as
|
|
1
|
+
import { a as gitHubRepositoryFromOrigin, c as BlockPreview, d as currentCostume, f as compareProjectBlocks, i as createGitHubPullRequests, l as createPreviewSync, m as readProjectSourceDiff, n as createScratchBlocksDiffLocator, o as ProjectSourceComparison, p as createProjectSourceDiff, s as ProjectSourceReview, t as createScratchBlocksDiffRenderer, u as usePreviewSync } from "./scratch-block-diff-renderer-xS8OYsRl.js";
|
|
2
2
|
import { n as init_objectSpread2, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
|
|
3
3
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
// src/project-source-diff.tsx
|
|
@@ -19,13 +19,13 @@ var en = {
|
|
|
19
19
|
properties: "Other block properties",
|
|
20
20
|
absent: "No block on this side",
|
|
21
21
|
omitted: (count) => `${count} unchanged blocks omitted from the change list`,
|
|
22
|
-
context: "Highlight in the surrounding
|
|
22
|
+
context: "Highlight in the surrounding code",
|
|
23
23
|
more: "Show more changes",
|
|
24
24
|
noChanges: "No block changes.",
|
|
25
25
|
locate: "Highlight in Editor",
|
|
26
26
|
locating: "Finding the block in the Editor…",
|
|
27
27
|
located: "Highlighted in the Editor.",
|
|
28
|
-
missingLocation: "This version or block is unavailable in the Editor. Open its surrounding
|
|
28
|
+
missingLocation: "This version or block is unavailable in the Editor. Open its surrounding code here.",
|
|
29
29
|
partLegend: "Orange outlines mark changed inputs and fields.",
|
|
30
30
|
otherChanges: "The project also has changes outside its block code.",
|
|
31
31
|
invalid: "This diff could not be read. Use a full-context project diff, or provide the original project.",
|
|
@@ -48,13 +48,13 @@ var ja = {
|
|
|
48
48
|
properties: "その他のブロック属性",
|
|
49
49
|
absent: "この側にはブロックがありません",
|
|
50
50
|
omitted: (count) => `変更のないブロック ${count} 個を一覧から省略`,
|
|
51
|
-
context: "
|
|
51
|
+
context: "元のコード内で強調",
|
|
52
52
|
more: "続きを表示",
|
|
53
53
|
noChanges: "ブロックの変更はありません。",
|
|
54
54
|
locate: "編集画面で強調",
|
|
55
55
|
locating: "編集画面でブロックを検索中…",
|
|
56
56
|
located: "編集画面で強調しました。",
|
|
57
|
-
missingLocation: "
|
|
57
|
+
missingLocation: "編集画面に該当する版またはブロックがありません。元のコードをここで展開できます。",
|
|
58
58
|
partLegend: "オレンジの枠は、変更された入力欄やフィールドです。",
|
|
59
59
|
otherChanges: "ブロックのコード以外にも作品の変更があります。",
|
|
60
60
|
invalid: "差分を読み込めません。全体の文脈を含む差分を使うか、変更前の作品を指定してください。",
|
|
@@ -75,6 +75,7 @@ var propertyLabels = /* @__PURE__ */ new Map([
|
|
|
75
75
|
function ChangeCard({ change, renderBlock, labels, onLocate, location }) {
|
|
76
76
|
var _change$after;
|
|
77
77
|
const [withContext, setWithContext] = useState(false);
|
|
78
|
+
const { sync, toggle } = usePreviewSync(labels === en ? "en" : "ja");
|
|
78
79
|
const notes = [...new Set(change.fields.map((field) => {
|
|
79
80
|
var _propertyLabels$get;
|
|
80
81
|
return labels[(_propertyLabels$get = propertyLabels.get(field)) !== null && _propertyLabels$get !== void 0 ? _propertyLabels$get : "properties"];
|
|
@@ -89,8 +90,10 @@ function ChangeCard({ change, renderBlock, labels, onLocate, location }) {
|
|
|
89
90
|
labels,
|
|
90
91
|
parts: change.parts,
|
|
91
92
|
onLocate: onLocate && (() => onLocate(version)),
|
|
93
|
+
version,
|
|
94
|
+
sync,
|
|
92
95
|
locationStatus: (location === null || location === void 0 ? void 0 : location.version) === version ? location.status : void 0
|
|
93
|
-
}))), /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement("input", {
|
|
96
|
+
}))), toggle, /* @__PURE__ */ React.createElement("label", null, /* @__PURE__ */ React.createElement("input", {
|
|
94
97
|
type: "checkbox",
|
|
95
98
|
checked: withContext,
|
|
96
99
|
onChange: (event) => setWithContext(event.currentTarget.checked)
|
|
@@ -166,4 +169,4 @@ function ProjectSourceBlockDiff({ patch, beforeSource, renderBlock, locateBlock,
|
|
|
166
169
|
})
|
|
167
170
|
}, labels.more));
|
|
168
171
|
}
|
|
169
|
-
export { ProjectSourceBlockDiff, ProjectSourceComparison, ProjectSourceReview, createGitHubPullRequests, createProjectSourceDiff, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, gitHubRepositoryFromOrigin };
|
|
172
|
+
export { ProjectSourceBlockDiff, ProjectSourceComparison, ProjectSourceReview, createGitHubPullRequests, createPreviewSync, createProjectSourceDiff, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, currentCostume, gitHubRepositoryFromOrigin };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BlockSide, ProjectSource } from './project-source-patch.js';
|
|
1
|
+
import type { BlockSide, ProjectSource, ProjectTarget } from './project-source-patch.js';
|
|
2
2
|
import type { ScriptBlockMark } from './scratch-block-diff-renderer.js';
|
|
3
3
|
export interface ScriptReviewSide extends BlockSide {
|
|
4
4
|
marks: ScriptBlockMark[];
|
|
@@ -11,6 +11,9 @@ export interface ScriptReview {
|
|
|
11
11
|
export interface TargetReview {
|
|
12
12
|
name: string;
|
|
13
13
|
isStage: boolean;
|
|
14
|
+
/** The stage or sprite in each version, absent where it does not exist. */
|
|
15
|
+
before?: ProjectTarget;
|
|
16
|
+
after?: ProjectTarget;
|
|
14
17
|
scripts: ScriptReview[];
|
|
15
18
|
properties: string[];
|
|
16
19
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
|
|
3
|
+
import type { ProjectAssetLoader } from './project-assets.js';
|
|
3
4
|
export type ProjectSourceReviewFile = {
|
|
4
5
|
path: string;
|
|
5
6
|
summary?: string;
|
|
@@ -8,6 +9,7 @@ export type ProjectSourceReviewFile = {
|
|
|
8
9
|
} & ({
|
|
9
10
|
patch: string;
|
|
10
11
|
beforeSource?: string;
|
|
12
|
+
loadAsset?: ProjectAssetLoader;
|
|
11
13
|
} | {
|
|
12
14
|
status: 'added' | 'removed' | 'updated';
|
|
13
15
|
});
|
|
@@ -28,7 +30,9 @@ export interface ProjectSourceComparisonProps {
|
|
|
28
30
|
locale?: string;
|
|
29
31
|
/** Shown after an explanation when either version cannot be read as a project. Without it, only an error is shown. */
|
|
30
32
|
fallback?: React.ReactNode;
|
|
33
|
+
/** Reads the images of both versions for thumbnails. Without it, stages and sprites are shown by name. */
|
|
34
|
+
loadAsset?: ProjectAssetLoader;
|
|
31
35
|
}
|
|
32
|
-
/** Compares two versions of a project.source.json by stage, sprite and
|
|
33
|
-
export declare function ProjectSourceComparison({ before, after, renderBlock, locale, fallback }: ProjectSourceComparisonProps): React.JSX.Element;
|
|
36
|
+
/** Compares two versions of a project.source.json by stage, sprite and code, as the pull request review does. */
|
|
37
|
+
export declare function ProjectSourceComparison({ before, after, renderBlock, locale, fallback, loadAsset }: ProjectSourceComparisonProps): React.JSX.Element;
|
|
34
38
|
export declare function ProjectSourceReview({ title, description, verification, files, renderBlock, locale }: ProjectSourceReviewProps): React.JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, n as init_objectSpread2, r as __commonJSMin, t as _objectSpread2 } from "./objectSpread2-Dkd68nJH.js";
|
|
2
|
-
import {
|
|
2
|
+
import { a as createTwoFilesPatch, i as isAssetName, n as assetsDirectoryParts, r as folderSettingsLimit, t as assetLimit } from "./project-asset-paths-KV715791.js";
|
|
3
3
|
import { a as _OverloadYield, i as _wrapAsyncGenerator, o as _asyncIterator, r as createGitSmartHttp, t as GitSmartHttpError } from "./smart-http-BmHEkE7K.js";
|
|
4
4
|
// node_modules/inherits/inherits_browser.js
|
|
5
5
|
var require_inherits_browser = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
@@ -17055,7 +17055,8 @@ async function openRepository(root) {
|
|
|
17055
17055
|
after: (_b$oid = b === null || b === void 0 ? void 0 : b.oid) !== null && _b$oid !== void 0 ? _b$oid : null,
|
|
17056
17056
|
oldMode: (_a$mode = a === null || a === void 0 ? void 0 : a.mode) !== null && _a$mode !== void 0 ? _a$mode : null,
|
|
17057
17057
|
newMode: (_b$mode = b === null || b === void 0 ? void 0 : b.mode) !== null && _b$mode !== void 0 ? _b$mode : null,
|
|
17058
|
-
worktree: false
|
|
17058
|
+
worktree: false,
|
|
17059
|
+
commit: oid
|
|
17059
17060
|
});
|
|
17060
17061
|
}
|
|
17061
17062
|
await assertSnapshot(snapshot);
|
|
@@ -17127,6 +17128,66 @@ async function openRepository(root) {
|
|
|
17127
17128
|
text
|
|
17128
17129
|
});
|
|
17129
17130
|
};
|
|
17131
|
+
const sourceOf = async (snapshot, change, version) => {
|
|
17132
|
+
if (change[version] === null) return null;
|
|
17133
|
+
switch (change.group) {
|
|
17134
|
+
case "staged": return version === "after" ? { kind: "index" } : snapshot.head ? {
|
|
17135
|
+
kind: "tree",
|
|
17136
|
+
ref: snapshot.head
|
|
17137
|
+
} : null;
|
|
17138
|
+
case "unstaged": return version === "after" ? { kind: "worktree" } : { kind: "index" };
|
|
17139
|
+
case "untracked": return version === "after" ? { kind: "worktree" } : null;
|
|
17140
|
+
case "commit": {
|
|
17141
|
+
if (!change.commit) return null;
|
|
17142
|
+
if (version === "after") return {
|
|
17143
|
+
kind: "tree",
|
|
17144
|
+
ref: change.commit
|
|
17145
|
+
};
|
|
17146
|
+
const parent = (await readCommit(_objectSpread2(_objectSpread2({}, options), {}, {
|
|
17147
|
+
cache,
|
|
17148
|
+
oid: change.commit
|
|
17149
|
+
}))).commit.parent[0];
|
|
17150
|
+
return parent ? {
|
|
17151
|
+
kind: "tree",
|
|
17152
|
+
ref: parent
|
|
17153
|
+
} : null;
|
|
17154
|
+
}
|
|
17155
|
+
}
|
|
17156
|
+
};
|
|
17157
|
+
/** Reads one regular file of one version, or `null` when it is missing or larger than the limit. */
|
|
17158
|
+
const readVersion = async (source, path, limit) => {
|
|
17159
|
+
if (source.kind === "worktree") {
|
|
17160
|
+
const stat = await fs.promises.stat("/" + path).catch(() => null);
|
|
17161
|
+
if (!(stat === null || stat === void 0 ? void 0 : stat.isFile()) || stat.size > limit) return null;
|
|
17162
|
+
const bytes = await readOptional(fs, "/" + path);
|
|
17163
|
+
return bytes && bytes.byteLength <= limit ? bytes : null;
|
|
17164
|
+
}
|
|
17165
|
+
const entry = (await entries(source.kind === "tree" ? source.ref : null, path)).get(path);
|
|
17166
|
+
if (!entry || entry.mode !== 33188 && entry.mode !== 33261) return null;
|
|
17167
|
+
const { blob } = await readBlob(_objectSpread2(_objectSpread2({}, options), {}, {
|
|
17168
|
+
cache,
|
|
17169
|
+
oid: entry.oid
|
|
17170
|
+
}));
|
|
17171
|
+
return blob.byteLength <= limit ? blob : null;
|
|
17172
|
+
};
|
|
17173
|
+
const readAsset = async (snapshot, change, version, md5ext, signal) => {
|
|
17174
|
+
if (signal.aborted) return null;
|
|
17175
|
+
fs.clearReads();
|
|
17176
|
+
await assertSnapshot(snapshot);
|
|
17177
|
+
const source = await sourceOf(snapshot, change, version);
|
|
17178
|
+
if (!source) return null;
|
|
17179
|
+
const folder = pathParts(change.path).slice(0, -1);
|
|
17180
|
+
const settings = await readVersion(source, [...folder, "sb3-source.json"].join("/"), folderSettingsLimit);
|
|
17181
|
+
const directory = assetsDirectoryParts(settings && new TextDecoder().decode(settings));
|
|
17182
|
+
if (!directory || signal.aborted) return null;
|
|
17183
|
+
const bytes = await readVersion(source, [
|
|
17184
|
+
...folder,
|
|
17185
|
+
...directory,
|
|
17186
|
+
md5ext
|
|
17187
|
+
].join("/"), assetLimit);
|
|
17188
|
+
await fs.checkReads();
|
|
17189
|
+
return bytes && !signal.aborted ? new Blob([bytes]) : null;
|
|
17190
|
+
};
|
|
17130
17191
|
const directoryAt = async (path) => {
|
|
17131
17192
|
let directory = root;
|
|
17132
17193
|
for (const part of pathParts(path)) directory = await directory.getDirectoryHandle(part);
|
|
@@ -17206,7 +17267,14 @@ async function openRepository(root) {
|
|
|
17206
17267
|
}),
|
|
17207
17268
|
read: (scope) => serial(() => read(scope)),
|
|
17208
17269
|
readDiff: (snapshot, change) => serial(() => readDiff(snapshot, change)),
|
|
17209
|
-
historyChanges: (snapshot, oid) => serial(() => historyChanges(snapshot, oid))
|
|
17270
|
+
historyChanges: (snapshot, oid) => serial(() => historyChanges(snapshot, oid)),
|
|
17271
|
+
/**
|
|
17272
|
+
* Reads the assets of both versions of a selected project file, for thumbnails. Staged changes compare HEAD with
|
|
17273
|
+
* the index, unstaged ones the index with the working tree, untracked ones nothing with the working tree, and
|
|
17274
|
+
* commit changes the first parent with the commit. Each version's sb3-source.json names its assets directory.
|
|
17275
|
+
* Reads never write, and resolve `null` for anything missing, invalid, larger than 10 MiB or changed since the snapshot.
|
|
17276
|
+
*/
|
|
17277
|
+
assetLoader: (snapshot, change) => ({ version, md5ext, signal }) => !isAssetName(md5ext) || signal.aborted ? Promise.resolve(null) : serial(() => readAsset(snapshot, change, version, md5ext, signal)).catch(() => null)
|
|
17210
17278
|
};
|
|
17211
17279
|
}
|
|
17212
17280
|
export { GitPanelError as n, createReadOnlyFs as r, openRepository as t };
|
package/dist/repository.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GetRemoteSession, RemoteOperation } from './remote-operations.js';
|
|
2
|
+
import type { ProjectAssetLoader } from './project-assets.js';
|
|
2
3
|
export { createReadOnlyFs, GitPanelError } from './filesystem.js';
|
|
3
4
|
export type ChangeGroup = 'staged' | 'unstaged' | 'untracked' | 'commit';
|
|
4
5
|
export interface FileChange {
|
|
@@ -9,6 +10,8 @@ export interface FileChange {
|
|
|
9
10
|
worktree: boolean;
|
|
10
11
|
oldMode: number | null;
|
|
11
12
|
newMode: number | null;
|
|
13
|
+
/** For `commit` changes, the commit compared with its first parent. */
|
|
14
|
+
commit?: string;
|
|
12
15
|
}
|
|
13
16
|
export interface Commit {
|
|
14
17
|
oid: string;
|
|
@@ -57,5 +60,12 @@ export declare function openRepository(root: FileSystemDirectoryHandle): Promise
|
|
|
57
60
|
read: (scope?: string) => Promise<Snapshot>;
|
|
58
61
|
readDiff: (snapshot: Snapshot, change: FileChange) => Promise<FileDiff>;
|
|
59
62
|
historyChanges: (snapshot: Snapshot, oid: string) => Promise<FileChange[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Reads the assets of both versions of a selected project file, for thumbnails. Staged changes compare HEAD with
|
|
65
|
+
* the index, unstaged ones the index with the working tree, untracked ones nothing with the working tree, and
|
|
66
|
+
* commit changes the first parent with the commit. Each version's sb3-source.json names its assets directory.
|
|
67
|
+
* Reads never write, and resolve `null` for anything missing, invalid, larger than 10 MiB or changed since the snapshot.
|
|
68
|
+
*/
|
|
69
|
+
assetLoader: (snapshot: Snapshot, change: FileChange) => ProjectAssetLoader;
|
|
60
70
|
}>;
|
|
61
71
|
export type Repository = Awaited<ReturnType<typeof openRepository>>;
|
package/dist/repository.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { n as GitPanelError, r as createReadOnlyFs, t as openRepository } from "./repository-
|
|
1
|
+
export { n as GitPanelError, r as createReadOnlyFs, t as openRepository } from "./repository-BQkADJmK.js";
|