@kubohiroya/turbowarp-plus-editor-git 0.8.0 → 0.9.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/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-xS8OYsRl.js";
1
+ import { a as gitHubRepositoryFromOrigin, c as projectPropertyLabel, g as useCostumeImage, h as useAssetImage, i as createGitHubPullRequests, l as ProjectSourceComparison, r as PullRequestError, s as organizeProjectChanges, t as createScratchBlocksDiffRenderer, u as ProjectSourceReview, y as parseProjectSource } from "./scratch-block-diff-renderer-DZR8ep-T.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-BQkADJmK.js";
3
+ import { n as GitPanelError, r as createReadOnlyFs, t as openRepository } from "./repository-VBp-UNWc.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";
@@ -2669,12 +2669,12 @@ diffMatchPatch.DIFF_EQUAL;
2669
2669
  diffMatchPatch.DIFF_DELETE;
2670
2670
  diffMatchPatch.DIFF_INSERT;
2671
2671
  // src/file-diff-view.tsx
2672
- var en$2 = {
2672
+ var en$3 = {
2673
2673
  view: "Show changes as",
2674
2674
  blocks: "Blocks",
2675
2675
  text: "Text"
2676
2676
  };
2677
- var ja$2 = {
2677
+ var ja$3 = {
2678
2678
  view: "変更の表示",
2679
2679
  blocks: "ブロック",
2680
2680
  text: "テキスト"
@@ -2687,7 +2687,7 @@ var isProjectSource = (path) => path.split("/").pop() === "project.source.json";
2687
2687
  function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset }) {
2688
2688
  var _change$oldMode, _change$newMode;
2689
2689
  const [view, setView] = useState("blocks");
2690
- const names = locale.startsWith("ja") ? ja$2 : en$2;
2690
+ const names = locale.startsWith("ja") ? ja$3 : en$3;
2691
2691
  const parsed = (diff === null || diff === void 0 ? void 0 : diff.patch) ? parseDiff(diff.patch)[0] : null;
2692
2692
  const text = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("p", null, labels.mode, " · ", ((_change$oldMode = change.oldMode) === null || _change$oldMode === void 0 ? void 0 : _change$oldMode.toString(8)) || "—", " → ", ((_change$newMode = change.newMode) === null || _change$newMode === void 0 ? void 0 : _change$newMode.toString(8)) || "—"), diff && /* @__PURE__ */ React.createElement("p", null, diff.beforeSize, " → ", diff.afterSize, " ", labels.bytes), (diff === null || diff === void 0 ? void 0 : diff.binary) && /* @__PURE__ */ React.createElement("p", null, labels.binary), (diff === null || diff === void 0 ? void 0 : diff.tooLarge) && /* @__PURE__ */ React.createElement("p", null, labels.large), parsed && /* @__PURE__ */ React.createElement(index, {
2693
2693
  viewType: "unified",
@@ -2717,6 +2717,306 @@ function FileDiffView({ change, diff, labels, locale, renderBlock, loadAsset })
2717
2717
  after: change.after === null ? null : sources.after
2718
2718
  }) : text);
2719
2719
  }
2720
+ // src/project-change-list.tsx
2721
+ var en$2 = {
2722
+ title: "Changes by project element",
2723
+ stage: "Stage",
2724
+ code: (count) => `Code · ${count} ${count === 1 ? "change" : "changes"}`,
2725
+ costumes: "Costume",
2726
+ backdrops: "Backdrop",
2727
+ sounds: "Sound",
2728
+ data: "Variables and messages",
2729
+ settings: "Other settings",
2730
+ project: "Whole project",
2731
+ other: "Files outside the project",
2732
+ added: "Added",
2733
+ removed: "Removed",
2734
+ updated: "Changed",
2735
+ files: "Files and Git groups",
2736
+ before: "Before",
2737
+ after: "After",
2738
+ absent: "Not in this version",
2739
+ loading: "Reading the project changes…",
2740
+ unreadable: "The project data could not be read, so the changes are listed by file.",
2741
+ fileList: "List by file",
2742
+ property: "Item",
2743
+ format: "Format",
2744
+ rate: "Sample rate",
2745
+ samples: "Samples",
2746
+ file: "File name",
2747
+ noImage: "No image",
2748
+ groups: {
2749
+ staged: "Staged",
2750
+ unstaged: "Unstaged",
2751
+ untracked: "Untracked",
2752
+ commit: "Commit"
2753
+ }
2754
+ };
2755
+ var ja$2 = {
2756
+ title: "作品の要素ごとの変更",
2757
+ stage: "ステージ",
2758
+ code: (count) => `コード · ${count} か所`,
2759
+ costumes: "コスチューム",
2760
+ backdrops: "背景",
2761
+ sounds: "音",
2762
+ data: "変数・リスト・メッセージ",
2763
+ settings: "そのほかの設定",
2764
+ project: "作品全体",
2765
+ other: "作品に属さないファイル",
2766
+ added: "追加",
2767
+ removed: "削除",
2768
+ updated: "変更",
2769
+ files: "ファイルと Git の区分",
2770
+ before: "変更前",
2771
+ after: "変更後",
2772
+ absent: "この版にはありません",
2773
+ loading: "作品の変更を読み込み中…",
2774
+ unreadable: "作品データを読み込めないため、ファイル単位で一覧にします。",
2775
+ fileList: "ファイル単位の一覧",
2776
+ property: "項目",
2777
+ format: "形式",
2778
+ rate: "サンプリング周波数",
2779
+ samples: "サンプル数",
2780
+ file: "ファイル名",
2781
+ noImage: "画像なし",
2782
+ groups: {
2783
+ staged: "ステージ済み",
2784
+ unstaged: "未ステージ",
2785
+ untracked: "未追跡",
2786
+ commit: "コミット"
2787
+ }
2788
+ };
2789
+ var projectChangeLabels = (locale) => locale.startsWith("ja") ? ja$2 : en$2;
2790
+ /**
2791
+ * Reads the selected project folder in the last commit and the working tree (or a commit and its parent) and arranges
2792
+ * the changed files by stage, sprite and element. `undefined` while there is no project; `'loading'` and `'unreadable'`
2793
+ * tell the panel to keep the list by file.
2794
+ */
2795
+ function useProjectChanges(repository, snapshot, project, commit, changes) {
2796
+ const [result, setResult] = useState();
2797
+ const active = !!repository && !!snapshot && project !== null && changes.length > 0;
2798
+ useEffect(() => {
2799
+ if (!repository || !snapshot || project === null || !changes.length) return;
2800
+ let current = true;
2801
+ const comparison = commit ? {
2802
+ kind: "commit",
2803
+ commit
2804
+ } : { kind: "worktree" };
2805
+ repository.readProject(snapshot, project, comparison).then((read) => {
2806
+ if (!current) return;
2807
+ let list;
2808
+ try {
2809
+ list = organizeProjectChanges({
2810
+ folder: project,
2811
+ before: read.before === null ? null : parseProjectSource(read.before),
2812
+ after: read.after === null ? null : parseProjectSource(read.after),
2813
+ assetsDirectory: read.assetsDirectory,
2814
+ files: changes.map(({ path, group }) => ({
2815
+ path,
2816
+ group
2817
+ }))
2818
+ });
2819
+ } catch (_unused) {
2820
+ setResult({
2821
+ changes,
2822
+ value: "unreadable"
2823
+ });
2824
+ return;
2825
+ }
2826
+ setResult({
2827
+ changes,
2828
+ value: {
2829
+ list,
2830
+ sources: {
2831
+ before: read.before,
2832
+ after: read.after
2833
+ },
2834
+ loadAsset: repository.projectAssetLoader(snapshot, project, comparison)
2835
+ }
2836
+ });
2837
+ }, () => {
2838
+ if (current) setResult({
2839
+ changes,
2840
+ value: "unreadable"
2841
+ });
2842
+ });
2843
+ return () => {
2844
+ current = false;
2845
+ };
2846
+ }, [
2847
+ repository,
2848
+ snapshot,
2849
+ project,
2850
+ commit,
2851
+ changes
2852
+ ]);
2853
+ if (!active) return void 0;
2854
+ if ((result === null || result === void 0 ? void 0 : result.changes) === changes || result && typeof result.value === "object") return result.value;
2855
+ return "loading";
2856
+ }
2857
+ function TargetHeading({ target, labels, loadAsset }) {
2858
+ const version = target.after ? "after" : "before";
2859
+ const image = useCostumeImage(target[version], version, loadAsset);
2860
+ return /* @__PURE__ */ React.createElement("h4", { className: "twgit-element-target" }, image ? /* @__PURE__ */ React.createElement("img", {
2861
+ src: image,
2862
+ alt: ""
2863
+ }) : /* @__PURE__ */ React.createElement("span", { className: "twgit-element-thumbnail" }), /* @__PURE__ */ React.createElement("span", null, target.isStage ? labels.stage : target.name), target.status !== "updated" && /* @__PURE__ */ React.createElement("small", { className: "twgit-review-status" }, labels[target.status]));
2864
+ }
2865
+ function elementTitle(element, target, labels, locale) {
2866
+ switch (element.kind) {
2867
+ case "code":
2868
+ var _element$scripts;
2869
+ return labels.code((_element$scripts = element.scripts) !== null && _element$scripts !== void 0 ? _element$scripts : 0);
2870
+ case "costumes":
2871
+ var _element$name;
2872
+ return `${(target === null || target === void 0 ? void 0 : target.isStage) ? labels.backdrops : labels.costumes} · ${(_element$name = element.name) !== null && _element$name !== void 0 ? _element$name : ""}`;
2873
+ case "sounds":
2874
+ var _element$name2;
2875
+ return `${labels.sounds} · ${(_element$name2 = element.name) !== null && _element$name2 !== void 0 ? _element$name2 : ""}`;
2876
+ default: {
2877
+ var _element$properties;
2878
+ const names = ((_element$properties = element.properties) !== null && _element$properties !== void 0 ? _element$properties : []).map((key) => projectPropertyLabel(key, locale)).join("・");
2879
+ return target ? `${element.kind === "data" ? labels.data : labels.settings} · ${names}` : names;
2880
+ }
2881
+ }
2882
+ }
2883
+ function ElementRow({ element, target, labels, locale, loadAsset, selected, busy, onSelect }) {
2884
+ var _element$md5ext, _element$md5ext2;
2885
+ const version = ((_element$md5ext = element.md5ext) === null || _element$md5ext === void 0 ? void 0 : _element$md5ext.after) ? "after" : "before";
2886
+ const image = useAssetImage(element.kind === "costumes" ? (_element$md5ext2 = element.md5ext) === null || _element$md5ext2 === void 0 ? void 0 : _element$md5ext2[version] : void 0, version, loadAsset);
2887
+ return /* @__PURE__ */ React.createElement("li", null, /* @__PURE__ */ React.createElement("button", {
2888
+ className: "twgit-file twgit-element",
2889
+ "data-git-element": element.kind,
2890
+ disabled: busy,
2891
+ "aria-pressed": selected,
2892
+ onClick: onSelect
2893
+ }, element.kind === "costumes" && (image ? /* @__PURE__ */ React.createElement("img", {
2894
+ src: image,
2895
+ alt: ""
2896
+ }) : /* @__PURE__ */ React.createElement("span", { className: "twgit-element-thumbnail" })), /* @__PURE__ */ React.createElement("span", { className: "twgit-element-name" }, elementTitle(element, target, labels, locale)), element.status !== "updated" && /* @__PURE__ */ React.createElement("small", { className: "twgit-review-status" }, labels[element.status])));
2897
+ }
2898
+ /** The change list arranged by the project's stage, sprites and elements, with files outside the project after them. */
2899
+ function ProjectChangeListView({ changes, selected, locale, busy, onSelect, onSelectFile, fileList, fileListOpen, setFileListOpen }) {
2900
+ const labels = projectChangeLabels(locale);
2901
+ const { list, loadAsset } = changes;
2902
+ return /* @__PURE__ */ React.createElement("section", {
2903
+ className: "twgit-element-list",
2904
+ "aria-label": labels.title
2905
+ }, list.targets.map((target) => /* @__PURE__ */ React.createElement("section", { key: target.key }, /* @__PURE__ */ React.createElement(TargetHeading, {
2906
+ target,
2907
+ labels,
2908
+ loadAsset
2909
+ }), /* @__PURE__ */ React.createElement("ul", null, target.elements.map((element) => /* @__PURE__ */ React.createElement(ElementRow, {
2910
+ key: element.key,
2911
+ element,
2912
+ target,
2913
+ labels,
2914
+ locale,
2915
+ loadAsset,
2916
+ selected: selected === element.key,
2917
+ busy,
2918
+ onSelect: () => onSelect(element.key)
2919
+ }))))), list.project.length > 0 && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h4", { className: "twgit-element-target" }, /* @__PURE__ */ React.createElement("span", null, labels.project)), /* @__PURE__ */ React.createElement("ul", null, list.project.map((element) => /* @__PURE__ */ React.createElement(ElementRow, {
2920
+ key: element.key,
2921
+ element,
2922
+ target: null,
2923
+ labels,
2924
+ locale,
2925
+ loadAsset,
2926
+ selected: selected === element.key,
2927
+ busy,
2928
+ onSelect: () => onSelect(element.key)
2929
+ })))), list.other.length > 0 && /* @__PURE__ */ React.createElement("section", null, /* @__PURE__ */ React.createElement("h4", { className: "twgit-element-target" }, /* @__PURE__ */ React.createElement("span", null, labels.other)), /* @__PURE__ */ React.createElement("ul", null, list.other.map((file) => /* @__PURE__ */ React.createElement("li", { key: file.group + ":" + file.path }, /* @__PURE__ */ React.createElement("button", {
2930
+ className: "twgit-file",
2931
+ "data-git-other": file.group,
2932
+ disabled: busy,
2933
+ onClick: () => onSelectFile(file)
2934
+ }, file.path, " ", /* @__PURE__ */ React.createElement("small", null, labels.groups[file.group])))))), /* @__PURE__ */ React.createElement("details", {
2935
+ className: "twgit-file-list",
2936
+ open: fileListOpen,
2937
+ onToggle: (event) => setFileListOpen(event.currentTarget.open)
2938
+ }, /* @__PURE__ */ React.createElement("summary", null, labels.fileList), fileList));
2939
+ }
2940
+ function findElement(list, key) {
2941
+ if (key === null) return null;
2942
+ for (const target of list.targets) {
2943
+ const element = target.elements.find((item) => item.key === key);
2944
+ if (element) return {
2945
+ element,
2946
+ target
2947
+ };
2948
+ }
2949
+ const element = list.project.find((item) => item.key === key);
2950
+ return element ? {
2951
+ element,
2952
+ target: null
2953
+ } : null;
2954
+ }
2955
+ function Picture({ md5ext, version, loadAsset, labels, title }) {
2956
+ const image = useAssetImage(md5ext, version, loadAsset);
2957
+ return /* @__PURE__ */ React.createElement("figure", { className: "twgit-element-picture" }, /* @__PURE__ */ React.createElement("figcaption", null, title), !md5ext ? /* @__PURE__ */ React.createElement("p", null, labels.absent) : image ? /* @__PURE__ */ React.createElement("img", {
2958
+ src: image,
2959
+ alt: title
2960
+ }) : /* @__PURE__ */ React.createElement("p", null, labels.noImage));
2961
+ }
2962
+ var show = (value) => {
2963
+ if (value === void 0) return "—";
2964
+ const text = typeof value === "string" ? value : JSON.stringify(value);
2965
+ return text.length > 400 ? text.slice(0, 400) + "…" : text;
2966
+ };
2967
+ /** The details of one selected row: its code, pictures or values in both versions, and the files behind it. */
2968
+ function ProjectElementView({ changes, selection, locale, renderBlock, onSelectFile }) {
2969
+ var _element$properties2;
2970
+ const labels = projectChangeLabels(locale);
2971
+ const { element, target } = selection;
2972
+ const record = (version) => {
2973
+ if (element.kind === "costumes" || element.kind === "sounds") return element[version];
2974
+ if (!target) {
2975
+ const source = changes.sources[version];
2976
+ try {
2977
+ return source === null ? void 0 : parseProjectSource(source);
2978
+ } catch (_unused2) {
2979
+ return;
2980
+ }
2981
+ }
2982
+ return target[version];
2983
+ };
2984
+ const rows = element.kind === "sounds" ? [
2985
+ ["name", "name"],
2986
+ ["dataFormat", labels.format],
2987
+ ["rate", labels.rate],
2988
+ ["sampleCount", labels.samples],
2989
+ ["md5ext", labels.file]
2990
+ ] : ((_element$properties2 = element.properties) !== null && _element$properties2 !== void 0 ? _element$properties2 : []).map((key) => [key, projectPropertyLabel(key, locale)]);
2991
+ return /* @__PURE__ */ React.createElement("section", {
2992
+ className: "twgit-element-view",
2993
+ "aria-label": elementTitle(element, target, labels, locale)
2994
+ }, /* @__PURE__ */ React.createElement("h3", null, target && /* @__PURE__ */ React.createElement("span", null, target.isStage ? labels.stage : target.name, " · "), elementTitle(element, target, labels, locale)), element.kind === "code" && target && renderBlock && /* @__PURE__ */ React.createElement(ProjectSourceComparison, {
2995
+ before: changes.sources.before,
2996
+ after: changes.sources.after,
2997
+ renderBlock,
2998
+ locale,
2999
+ loadAsset: changes.loadAsset,
3000
+ target: {
3001
+ isStage: target.isStage,
3002
+ name: target.name
3003
+ }
3004
+ }), element.kind === "costumes" && /* @__PURE__ */ React.createElement("div", { className: "twgit-element-pictures" }, ["before", "after"].map((version) => {
3005
+ var _element$md5ext3, _element$version;
3006
+ return /* @__PURE__ */ React.createElement(Picture, {
3007
+ key: version,
3008
+ md5ext: (_element$md5ext3 = element.md5ext) === null || _element$md5ext3 === void 0 ? void 0 : _element$md5ext3[version],
3009
+ version,
3010
+ loadAsset: changes.loadAsset,
3011
+ labels,
3012
+ title: ((_element$version = element[version]) === null || _element$version === void 0 ? void 0 : _element$version.name) === void 0 ? labels[version] : `${labels[version]} · ${String(element[version].name)}`
3013
+ });
3014
+ })), element.kind !== "code" && element.kind !== "costumes" && /* @__PURE__ */ React.createElement("table", { className: "twgit-element-values" }, /* @__PURE__ */ React.createElement("thead", null, /* @__PURE__ */ React.createElement("tr", null, /* @__PURE__ */ React.createElement("th", null, labels.property), /* @__PURE__ */ React.createElement("th", null, labels.before), /* @__PURE__ */ React.createElement("th", null, labels.after))), /* @__PURE__ */ React.createElement("tbody", null, rows.map(([key, name]) => /* @__PURE__ */ React.createElement("tr", { key }, /* @__PURE__ */ React.createElement("th", { scope: "row" }, name), /* @__PURE__ */ React.createElement("td", null, record("before") ? show(record("before")[key]) : labels.absent), /* @__PURE__ */ React.createElement("td", null, record("after") ? show(record("after")[key]) : labels.absent))))), /* @__PURE__ */ React.createElement("details", { className: "twgit-element-files" }, /* @__PURE__ */ React.createElement("summary", null, labels.files), /* @__PURE__ */ React.createElement("ul", null, element.files.map((file) => /* @__PURE__ */ React.createElement("li", { key: file.group + ":" + file.path }, /* @__PURE__ */ React.createElement("button", {
3015
+ className: "twgit-file",
3016
+ type: "button",
3017
+ onClick: () => onSelectFile(file)
3018
+ }, file.path, " ", /* @__PURE__ */ React.createElement("small", null, labels.groups[file.group])))))));
3019
+ }
2720
3020
  // src/messages.ts
2721
3021
  var messages = {
2722
3022
  en: {
@@ -3385,6 +3685,22 @@ function GitPanel({ document, host, controller }) {
3385
3685
  selected,
3386
3686
  diff: await state.repository.readDiff(state.snapshot, selected)
3387
3687
  }));
3688
+ const [element, setElement] = useState(null);
3689
+ const [fileListOpen, setFileListOpen] = useState(false);
3690
+ const projectChanges = useProjectChanges(state.repository, state.snapshot, state.project, state.commit, state.changes);
3691
+ const openFile = (file) => {
3692
+ const change = state.changes.find((item) => item.path === file.path && item.group === file.group);
3693
+ if (!change) return;
3694
+ setElement(null);
3695
+ selectFile(change);
3696
+ };
3697
+ const openElement = (key) => {
3698
+ setElement(key);
3699
+ setState((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
3700
+ selected: null,
3701
+ diff: null
3702
+ }));
3703
+ };
3388
3704
  const selectCommit = (commit) => run(async () => ({
3389
3705
  commit,
3390
3706
  changes: await state.repository.historyChanges(state.snapshot, commit),
@@ -3450,6 +3766,8 @@ function GitPanel({ document, host, controller }) {
3450
3766
  "unstaged",
3451
3767
  "untracked"
3452
3768
  ];
3769
+ const projectList = typeof projectChanges === "object" ? projectChanges : null;
3770
+ const selection = projectList && !state.selected ? findElement(projectList.list, element) : null;
3453
3771
  return /* @__PURE__ */ React.createElement("div", { className: "twgit-backdrop" }, /* @__PURE__ */ React.createElement("div", {
3454
3772
  className: "twgit-panel",
3455
3773
  ref: dialog,
@@ -3503,18 +3821,37 @@ function GitPanel({ document, host, controller }) {
3503
3821
  }, name, "/"))))), /* @__PURE__ */ React.createElement("h3", null, /* @__PURE__ */ React.createElement("button", {
3504
3822
  disabled: state.busy,
3505
3823
  onClick: () => void controller.refresh()
3506
- }, labels.worktree)), groups.map((group) => /* @__PURE__ */ React.createElement("section", { key: group }, /* @__PURE__ */ React.createElement("h3", null, labels[group], " · ", state.changes.filter((change) => change.group === group).length), /* @__PURE__ */ React.createElement("ul", null, state.changes.filter((change) => change.group === group).map((change) => /* @__PURE__ */ React.createElement("li", { key: change.path }, /* @__PURE__ */ React.createElement("button", {
3507
- className: "twgit-file",
3508
- "data-git-group": group,
3509
- disabled: state.busy,
3510
- "aria-pressed": state.selected === change,
3511
- onClick: () => void selectFile(change)
3512
- }, /* @__PURE__ */ React.createElement("span", null, !change.after ? "D" : !change.before ? "A" : "M"), " ", change.path)))))), !state.changes.length && /* @__PURE__ */ React.createElement("p", null, labels.clean), /* @__PURE__ */ React.createElement("details", null, /* @__PURE__ */ React.createElement("summary", null, labels.branches), /* @__PURE__ */ React.createElement("ul", null, (_state$snapshot4 = state.snapshot) === null || _state$snapshot4 === void 0 ? void 0 : _state$snapshot4.branches.map((branch) => /* @__PURE__ */ React.createElement("li", { key: branch }, branch)))), /* @__PURE__ */ React.createElement("h3", null, labels.history), /* @__PURE__ */ React.createElement("ol", { className: "twgit-history" }, (_state$snapshot5 = state.snapshot) === null || _state$snapshot5 === void 0 ? void 0 : _state$snapshot5.commits.map((commit) => /* @__PURE__ */ React.createElement("li", { key: commit.oid }, /* @__PURE__ */ React.createElement("button", {
3824
+ }, labels.worktree)), projectChanges === "loading" && /* @__PURE__ */ React.createElement("p", { role: "status" }, projectChangeLabels(locale).loading), projectChanges === "unreadable" && /* @__PURE__ */ React.createElement("p", null, projectChangeLabels(locale).unreadable), (() => {
3825
+ const fileList = groups.map((group) => /* @__PURE__ */ React.createElement("section", { key: group }, /* @__PURE__ */ React.createElement("h3", null, labels[group], " · ", state.changes.filter((change) => change.group === group).length), /* @__PURE__ */ React.createElement("ul", null, state.changes.filter((change) => change.group === group).map((change) => /* @__PURE__ */ React.createElement("li", { key: change.path }, /* @__PURE__ */ React.createElement("button", {
3826
+ className: "twgit-file",
3827
+ "data-git-group": group,
3828
+ disabled: state.busy,
3829
+ "aria-pressed": state.selected === change,
3830
+ onClick: () => openFile(change)
3831
+ }, /* @__PURE__ */ React.createElement("span", null, !change.after ? "D" : !change.before ? "A" : "M"), " ", change.path))))));
3832
+ return projectList ? /* @__PURE__ */ React.createElement(ProjectChangeListView, {
3833
+ changes: projectList,
3834
+ selected: selection ? element : null,
3835
+ locale,
3836
+ busy: state.busy,
3837
+ onSelect: openElement,
3838
+ onSelectFile: openFile,
3839
+ fileList,
3840
+ fileListOpen,
3841
+ setFileListOpen
3842
+ }) : fileList;
3843
+ })(), !state.changes.length && /* @__PURE__ */ React.createElement("p", null, labels.clean), /* @__PURE__ */ React.createElement("details", null, /* @__PURE__ */ React.createElement("summary", null, labels.branches), /* @__PURE__ */ React.createElement("ul", null, (_state$snapshot4 = state.snapshot) === null || _state$snapshot4 === void 0 ? void 0 : _state$snapshot4.branches.map((branch) => /* @__PURE__ */ React.createElement("li", { key: branch }, branch)))), /* @__PURE__ */ React.createElement("h3", null, labels.history), /* @__PURE__ */ React.createElement("ol", { className: "twgit-history" }, (_state$snapshot5 = state.snapshot) === null || _state$snapshot5 === void 0 ? void 0 : _state$snapshot5.commits.map((commit) => /* @__PURE__ */ React.createElement("li", { key: commit.oid }, /* @__PURE__ */ React.createElement("button", {
3513
3844
  disabled: state.busy,
3514
3845
  "aria-pressed": state.commit === commit.oid,
3515
3846
  title: commit.oid,
3516
3847
  onClick: () => void selectCommit(commit.oid)
3517
- }, commit.message.split("\n")[0]), /* @__PURE__ */ React.createElement("small", null, /* @__PURE__ */ React.createElement("code", null, commit.oid.slice(0, 8)), " · ", commit.author, " · ", (/* @__PURE__ */ new Date(commit.timestamp * 1e3)).toLocaleString(locale)))))), /* @__PURE__ */ React.createElement("main", { className: "twgit-diff" }, state.selected ? /* @__PURE__ */ React.createElement(FileDiffView, {
3848
+ }, commit.message.split("\n")[0]), /* @__PURE__ */ React.createElement("small", null, /* @__PURE__ */ React.createElement("code", null, commit.oid.slice(0, 8)), " · ", commit.author, " · ", (/* @__PURE__ */ new Date(commit.timestamp * 1e3)).toLocaleString(locale)))))), /* @__PURE__ */ React.createElement("main", { className: "twgit-diff" }, selection && projectList ? /* @__PURE__ */ React.createElement(ProjectElementView, {
3849
+ changes: projectList,
3850
+ selection,
3851
+ locale,
3852
+ renderBlock: host.renderReviewBlock,
3853
+ onSelectFile: openFile
3854
+ }) : state.selected ? /* @__PURE__ */ React.createElement(FileDiffView, {
3518
3855
  change: state.selected,
3519
3856
  diff: state.diff,
3520
3857
  labels,
@@ -15,5 +15,9 @@ export declare function currentCostume(target: ProjectTarget): {
15
15
  md5ext: string;
16
16
  type: string;
17
17
  } | null;
18
+ /** The image type of an asset file name, or `undefined` for sounds and anything that is not an image. */
19
+ export declare function assetImageType(md5ext: string): string | undefined;
20
+ /** Loads one image asset of one version as an object URL, or `null` while it is unavailable or not an image. */
21
+ export declare function useAssetImage(md5ext: string | undefined, version: ProjectVersion, loadAsset?: ProjectAssetLoader): string | null;
18
22
  /** Loads the current costume of one version of a target as an object URL for an image, or `null` while it is unavailable. */
19
23
  export declare function useCostumeImage(target: ProjectTarget | undefined, version: ProjectVersion, loadAsset?: ProjectAssetLoader): string | null;
@@ -0,0 +1,81 @@
1
+ import React from 'react';
2
+ import type { ChangedFile, ProjectChangeList, ProjectElementChange, ProjectTargetChanges } from './project-change-model.js';
3
+ import type { ProjectAssetLoader } from './project-assets.js';
4
+ import type { ChangeGroup, FileChange, Repository, Snapshot } from './repository.js';
5
+ import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
6
+ declare const en: {
7
+ title: string;
8
+ stage: string;
9
+ code: (count: number) => string;
10
+ costumes: string;
11
+ backdrops: string;
12
+ sounds: string;
13
+ data: string;
14
+ settings: string;
15
+ project: string;
16
+ other: string;
17
+ added: string;
18
+ removed: string;
19
+ updated: string;
20
+ files: string;
21
+ before: string;
22
+ after: string;
23
+ absent: string;
24
+ loading: string;
25
+ unreadable: string;
26
+ fileList: string;
27
+ property: string;
28
+ format: string;
29
+ rate: string;
30
+ samples: string;
31
+ file: string;
32
+ noImage: string;
33
+ groups: Record<ChangeGroup, string>;
34
+ };
35
+ type Labels = typeof en;
36
+ export declare const projectChangeLabels: (locale: string) => Labels;
37
+ export interface ProjectChanges {
38
+ list: ProjectChangeList;
39
+ sources: {
40
+ before: string | null;
41
+ after: string | null;
42
+ };
43
+ loadAsset: ProjectAssetLoader;
44
+ }
45
+ /**
46
+ * Reads the selected project folder in the last commit and the working tree (or a commit and its parent) and arranges
47
+ * the changed files by stage, sprite and element. `undefined` while there is no project; `'loading'` and `'unreadable'`
48
+ * tell the panel to keep the list by file.
49
+ */
50
+ export declare function useProjectChanges(repository: Repository | null, snapshot: Snapshot | null, project: string | null, commit: string | null, changes: readonly FileChange[]): ProjectChanges | 'loading' | 'unreadable' | undefined;
51
+ /** The change list arranged by the project's stage, sprites and elements, with files outside the project after them. */
52
+ export declare function ProjectChangeListView({ changes, selected, locale, busy, onSelect, onSelectFile, fileList, fileListOpen, setFileListOpen }: {
53
+ changes: ProjectChanges;
54
+ selected: string | null;
55
+ locale: string;
56
+ busy: boolean;
57
+ onSelect(key: string): void;
58
+ onSelectFile(file: ChangedFile): void;
59
+ fileList: React.ReactNode;
60
+ fileListOpen: boolean;
61
+ setFileListOpen(open: boolean): void;
62
+ }): React.JSX.Element;
63
+ export declare function findElement(list: ProjectChangeList, key: string | null): {
64
+ element: ProjectElementChange;
65
+ target: ProjectTargetChanges;
66
+ } | {
67
+ element: ProjectElementChange;
68
+ target: null;
69
+ } | null;
70
+ /** The details of one selected row: its code, pictures or values in both versions, and the files behind it. */
71
+ export declare function ProjectElementView({ changes, selection, locale, renderBlock, onSelectFile }: {
72
+ changes: ProjectChanges;
73
+ selection: {
74
+ element: ProjectElementChange;
75
+ target: ProjectTargetChanges | null;
76
+ };
77
+ locale: string;
78
+ renderBlock?: ProjectBlockRenderer;
79
+ onSelectFile(file: ChangedFile): void;
80
+ }): React.JSX.Element;
81
+ export {};
@@ -0,0 +1,73 @@
1
+ import type { ChangeGroup } from './repository.js';
2
+ import type { ProjectSource, ProjectTarget } from './project-source-patch.js';
3
+ /** One changed file of the repository, with the Git group it was listed under. A path can appear in several groups. */
4
+ export interface ChangedFile {
5
+ path: string;
6
+ group: ChangeGroup;
7
+ }
8
+ export type ProjectElementKind = 'code' | 'costumes' | 'sounds' | 'data' | 'settings';
9
+ export type ProjectChangeStatus = 'added' | 'removed' | 'updated';
10
+ /** One row of the project-shaped change list: something a person recognizes in the Editor. */
11
+ export interface ProjectElementChange {
12
+ /** Stable within one list, for React keys and selection. */
13
+ key: string;
14
+ kind: ProjectElementKind;
15
+ status: ProjectChangeStatus;
16
+ /** Costume or sound name; absent for code, data and settings rows. */
17
+ name?: string;
18
+ /** The costume or sound entry in each version, for costume and sound rows. */
19
+ before?: Record<string, unknown>;
20
+ after?: Record<string, unknown>;
21
+ /** Asset file names (`md5ext`) of each version, for costume and sound rows. */
22
+ md5ext?: {
23
+ before?: string;
24
+ after?: string;
25
+ };
26
+ /**
27
+ * Changed property keys, for data and settings rows (for example `variables`, `x`, `visible`), and for
28
+ * project-level rows (`monitors`, `extensions`, `meta`).
29
+ */
30
+ properties?: string[];
31
+ /** Number of changed code items (as counted by the review comparison), for code rows. */
32
+ scripts?: number;
33
+ /** The changed repository files behind this row, so the details view can show them with their Git groups. */
34
+ files: ChangedFile[];
35
+ }
36
+ export interface ProjectTargetChanges {
37
+ key: string;
38
+ name: string;
39
+ isStage: boolean;
40
+ status: ProjectChangeStatus;
41
+ before?: ProjectTarget;
42
+ after?: ProjectTarget;
43
+ /** In the order code, costumes, sounds, data, settings; costumes and sounds in their order in the target. */
44
+ elements: ProjectElementChange[];
45
+ }
46
+ export interface ProjectChangeList {
47
+ /** The stage first, then sprites in project order; removed targets after the existing ones. */
48
+ targets: ProjectTargetChanges[];
49
+ /** Project-level changes outside the targets (monitors, extensions, meta and other top-level keys). */
50
+ project: ProjectElementChange[];
51
+ /** Changed files that belong to no element: sb3-source.json, embedded-extensions.json, unreferenced assets and the rest. */
52
+ other: ChangedFile[];
53
+ }
54
+ export interface OrganizeProjectChangesInput {
55
+ /** The project folder relative to the repository root, `''` for the root. */
56
+ folder: string;
57
+ /** The project data of each version, `null` where the version has no project file. */
58
+ before: ProjectSource | null;
59
+ after: ProjectSource | null;
60
+ /** The assets directory of each version, relative to the folder (from sb3-source.json; `assets` by default). */
61
+ assetsDirectory: {
62
+ before: string;
63
+ after: string;
64
+ };
65
+ /** Every changed file in the folder, from all Git groups. */
66
+ files: readonly ChangedFile[];
67
+ }
68
+ /** The Japanese or English name of a project or target property key; unknown keys are returned as they are. */
69
+ export declare function projectPropertyLabel(key: string, locale?: string): string;
70
+ /** The asset file name of a costume or sound entry: `md5ext`, or `assetId.dataFormat`; `''` when it has neither. */
71
+ export declare function assetFileName(entry: Record<string, unknown>): string;
72
+ /** Arranges the changed files of one project folder by stage, sprite and element. Pure; reads nothing. */
73
+ export declare function organizeProjectChanges(input: OrganizeProjectChangesInput): ProjectChangeList;
@@ -10,6 +10,8 @@ export type { ProjectBlockLocation, ProjectBlockLocator, ScratchBlockLocationWor
10
10
  export type { BlockPart } from './project-source-patch.js';
11
11
  export { createProjectSourceDiff } from './project-source-patch.js';
12
12
  export { currentCostume } from './project-assets.js';
13
+ export { assetFileName, organizeProjectChanges, projectPropertyLabel } from './project-change-model.js';
14
+ export type { ChangedFile, OrganizeProjectChangesInput, ProjectChangeList, ProjectChangeStatus, ProjectElementChange, ProjectElementKind, ProjectTargetChanges } from './project-change-model.js';
13
15
  export type { ProjectAssetLoader, ProjectVersion } from './project-assets.js';
14
16
  export { createScratchBlocksDiffRenderer } from './scratch-block-diff-renderer.js';
15
17
  export type { BlockPreviewView, BlockPreviewViewport, ProjectBlockRenderer, ProjectBlockRendering, ScratchBlockDiffRendererOptions, ScratchBlocksPort } from './scratch-block-diff-renderer.js';
@@ -1,4 +1,4 @@
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";
1
+ import { _ as compareProjectBlocks, a as gitHubRepositoryFromOrigin, b as readProjectSourceDiff, c as projectPropertyLabel, d as BlockPreview, f as createPreviewSync, i as createGitHubPullRequests, l as ProjectSourceComparison, m as currentCostume, n as createScratchBlocksDiffLocator, o as assetFileName, p as usePreviewSync, s as organizeProjectChanges, t as createScratchBlocksDiffRenderer, u as ProjectSourceReview, v as createProjectSourceDiff } from "./scratch-block-diff-renderer-DZR8ep-T.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
@@ -169,4 +169,4 @@ function ProjectSourceBlockDiff({ patch, beforeSource, renderBlock, locateBlock,
169
169
  })
170
170
  }, labels.more));
171
171
  }
172
- export { ProjectSourceBlockDiff, ProjectSourceComparison, ProjectSourceReview, createGitHubPullRequests, createPreviewSync, createProjectSourceDiff, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, currentCostume, gitHubRepositoryFromOrigin };
172
+ export { ProjectSourceBlockDiff, ProjectSourceComparison, ProjectSourceReview, assetFileName, createGitHubPullRequests, createPreviewSync, createProjectSourceDiff, createScratchBlocksDiffLocator, createScratchBlocksDiffRenderer, currentCostume, gitHubRepositoryFromOrigin, organizeProjectChanges, projectPropertyLabel };
@@ -32,7 +32,12 @@ export interface ProjectSourceComparisonProps {
32
32
  fallback?: React.ReactNode;
33
33
  /** Reads the images of both versions for thumbnails. Without it, stages and sprites are shown by name. */
34
34
  loadAsset?: ProjectAssetLoader;
35
+ /** Shows only the code of this stage or sprite, without the overview and the changes outside the code. */
36
+ target?: {
37
+ isStage: boolean;
38
+ name: string;
39
+ };
35
40
  }
36
41
  /** 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;
42
+ export declare function ProjectSourceComparison({ before, after, renderBlock, locale, fallback, loadAsset, target }: ProjectSourceComparisonProps): React.JSX.Element;
38
43
  export declare function ProjectSourceReview({ title, description, verification, files, renderBlock, locale }: ProjectSourceReviewProps): React.JSX.Element;