@kubohiroya/turbowarp-plus-editor-git 0.8.0 → 0.10.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 { i as createReadOnlyFs, r as GitPanelError, t as openRepository } from "./repository-CtvWYPQ8.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,9 +2687,9 @@ 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
- 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, {
2692
+ const text = /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("details", { className: "twgit-file-details" }, /* @__PURE__ */ React.createElement("summary", null, labels.details), /* @__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",
2694
2694
  diffType: parsed.type,
2695
2695
  hunks: parsed.hunks
@@ -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: "in the next record",
2750
+ unstaged: "not yet recorded",
2751
+ untracked: "new file",
2752
+ commit: "in this record"
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: {
@@ -2739,15 +3039,15 @@ var messages = {
2739
3039
  replace: "Replace the open project and discard its unsaved changes?",
2740
3040
  dirty: "The Editor has unsaved changes.",
2741
3041
  saved: "The Editor has no unsaved changes.",
2742
- staged: "Staged",
2743
- unstaged: "Unstaged",
2744
- untracked: "Untracked",
2745
- commit: "Commit changes",
2746
- history: "History",
2747
- worktree: "Working tree",
2748
- clean: "No changes in the selected folder.",
3042
+ staged: "Changes in the next record",
3043
+ unstaged: "Saved, not yet recorded",
3044
+ untracked: "New files never recorded",
3045
+ commit: "Changes in this record",
3046
+ history: "Records (commits)",
3047
+ worktree: "Saved files",
3048
+ clean: "No saved changes in the selected folder.",
2749
3049
  branches: "Local branches",
2750
- unborn: "No commits yet",
3050
+ unborn: "No records yet",
2751
3051
  detached: "Detached HEAD",
2752
3052
  select: "Select a file to view its diff.",
2753
3053
  binary: "Binary asset",
@@ -2761,16 +3061,33 @@ var messages = {
2761
3061
  changed: "Files or Git state changed while reading. Refresh to read the current state.",
2762
3062
  outdated: "The repository changed outside the Editor. Refresh to read the current state.",
2763
3063
  unsupported: "This repository contains an entry the browser cannot represent.",
2764
- pullUnsafe: "Stop the project and clear unsaved or local Git changes before pulling.",
2765
- diverged: "Pull requires a fast-forward. Your local branch and files are intact.",
3064
+ pullUnsafe: "Stop the project, and save or undo unsaved edits and unrecorded changes, before getting changes from GitHub (pull).",
3065
+ diverged: "This branch and GitHub's have both gained records, so they cannot be combined here (pull only fast-forwards). Your branch and files are unchanged.",
2766
3066
  session: "Reconnect GitHub for this repository and requested permission.",
2767
- pushUnknown: "The push result is unknown. Fetch and check the remote branch before retrying.",
2768
- pushRefresh: "The push succeeded. Fetch to refresh the local tracking branch.",
2769
- rollbackFailed: "Git could not restore all local changes. Check the working tree and Git status.",
2770
- remoteFailed: "The Git operation failed. Check the selected connection and retry.",
2771
- remoteDone: "Git operation completed.",
2772
- pulledProject: "Pull completed. Use Reload Project to open the updated files.",
2773
- confirmPush: "Push the current branch to its GitHub origin?",
3067
+ pushUnknown: "It is unknown whether GitHub received the records. Check GitHub (fetch) before sending again.",
3068
+ pushRefresh: "GitHub received the records. Check GitHub (fetch) to update the status.",
3069
+ rollbackFailed: "Some local changes could not be restored. Check the files and the Git details.",
3070
+ remoteFailed: "Could not complete the GitHub operation. Check the connection and try again.",
3071
+ remoteDone: "The GitHub operation finished.",
3072
+ pulledProject: "Got the changes from GitHub. Use Reload Project to open the updated files.",
3073
+ confirmPush: "Send the records on this branch to GitHub (push)?",
3074
+ details: "Git details",
3075
+ branch: "Branch",
3076
+ head: "HEAD",
3077
+ groups: "Git groups",
3078
+ added: "Added",
3079
+ modified: "Changed",
3080
+ deleted: "Deleted",
3081
+ gitStaged: "Staged",
3082
+ gitUnstaged: "Unstaged",
3083
+ gitUntracked: "Untracked",
3084
+ upstreamSame: "Same as GitHub",
3085
+ upstreamAhead: "Ahead of GitHub",
3086
+ upstreamBehind: "Behind GitHub",
3087
+ upstreamDiverged: "Diverged from GitHub",
3088
+ upstreamUnknown: "Not checked with GitHub yet",
3089
+ records: "records",
3090
+ checkedAt: "as of the last check",
2774
3091
  failed: "Could not read the repository. Check folder access and refresh."
2775
3092
  },
2776
3093
  ja: {
@@ -2790,18 +3107,18 @@ var messages = {
2790
3107
  reload: "作品を再読込",
2791
3108
  up: "上のフォルダ",
2792
3109
  browse: "リポジトリ内の作品を選択",
2793
- replace: "未保存の変更を破棄して、別の作品を開きますか?",
2794
- dirty: "Editor に未保存の変更があります。",
2795
- saved: "Editor に未保存の変更はありません。",
2796
- staged: "ステージ済み",
2797
- unstaged: "未ステージ",
2798
- untracked: "未追跡",
2799
- commit: "コミットの変更",
2800
- history: "履歴",
2801
- worktree: "作業ツリー",
2802
- clean: "対象フォルダに変更はありません。",
3110
+ replace: "保存していない編集を破棄して、別の作品を開きますか?",
3111
+ dirty: "Editor に保存していない編集があります。",
3112
+ saved: "Editor に保存していない編集はありません。",
3113
+ staged: "次の記録に入る変更",
3114
+ unstaged: "保存したが、まだ記録していない変更",
3115
+ untracked: "まだ記録したことのない新しいファイル",
3116
+ commit: "この記録の変更",
3117
+ history: "記録(コミット)の履歴",
3118
+ worktree: "保存したファイル",
3119
+ clean: "対象フォルダに、保存した変更はありません。",
2803
3120
  branches: "ローカルブランチ",
2804
- unborn: "コミットはまだありません",
3121
+ unborn: "まだ記録がありません",
2805
3122
  detached: "ブランチ未選択の HEAD",
2806
3123
  select: "ファイルを選ぶと差分を表示します。",
2807
3124
  binary: "バイナリアセット",
@@ -2815,16 +3132,33 @@ var messages = {
2815
3132
  changed: "読込中にファイルまたは Git の状態が変わりました。再取得してください。",
2816
3133
  outdated: "Editor の外でリポジトリが変更されました。再取得すると最新の状態を読み込みます。",
2817
3134
  unsupported: "ブラウザーで扱えない種類の Git エントリーがあります。",
2818
- pullUnsafe: "pull の前に実行を止め、未保存編集とローカルの Git 変更を解消してください。",
2819
- diverged: "fast-forward できません。ローカルブランチとファイルは保持されています。",
3135
+ pullUnsafe: "GitHub から取り込む前に、実行を止め、保存していない編集とまだ記録していない変更をなくしてください。",
3136
+ diverged: "このブランチと GitHub の両方に新しい記録があるため、ここでは取り込めません(pull は fast-forward のみ)。ブランチとファイルはそのままです。",
2820
3137
  session: "このリポジトリと操作に必要な権限で GitHub に接続してください。",
2821
- pushUnknown: "push の結果が不明です。再試行する前に fetch でリモートブランチを確認してください。",
2822
- pushRefresh: "push は成功しました。fetch でローカルの追跡ブランチを更新してください。",
2823
- rollbackFailed: "Git がローカルの変更をすべて復元できませんでした。作業ツリーと Git の状態を確認してください。",
2824
- remoteFailed: "Git 操作に失敗しました。選択中の接続を確認して再試行してください。",
2825
- remoteDone: "Git 操作が完了しました。",
2826
- pulledProject: "pull が完了しました。「作品を再読込」で更新済みのファイルを開けます。",
2827
- confirmPush: "現在のブランチを GitHub の origin に push しますか?",
3138
+ pushUnknown: "GitHub が記録を受け取ったか分かりません。送り直す前に「GitHub と確かめる」を使ってください。",
3139
+ pushRefresh: "GitHub が記録を受け取りました。「GitHub と確かめる」で状態を更新してください。",
3140
+ rollbackFailed: "一部のローカルの変更を元に戻せませんでした。ファイルと Git の詳細を確認してください。",
3141
+ remoteFailed: "GitHub との操作を完了できませんでした。接続を確認して、もう一度試してください。",
3142
+ remoteDone: "GitHub との操作が終わりました。",
3143
+ pulledProject: "GitHub から取り込みました。「作品を再読込」で更新したファイルを開けます。",
3144
+ confirmPush: "このブランチの記録を GitHub に送りますか(push)?",
3145
+ details: "Git の詳細",
3146
+ branch: "ブランチ",
3147
+ head: "HEAD",
3148
+ groups: "Git の区分",
3149
+ added: "追加",
3150
+ modified: "変更",
3151
+ deleted: "削除",
3152
+ gitStaged: "ステージ済み (staged)",
3153
+ gitUnstaged: "未ステージ (unstaged)",
3154
+ gitUntracked: "未追跡 (untracked)",
3155
+ upstreamSame: "GitHub と同じ",
3156
+ upstreamAhead: "GitHub より進んでいる",
3157
+ upstreamBehind: "GitHub より遅れている",
3158
+ upstreamDiverged: "GitHub と分かれている",
3159
+ upstreamUnknown: "まだ GitHub と確かめていない",
3160
+ records: "件",
3161
+ checkedAt: "最後に確かめた時点",
2828
3162
  failed: "リポジトリを読み込めませんでした。フォルダへのアクセスを確認し、再取得してください。"
2829
3163
  }
2830
3164
  };
@@ -2851,10 +3185,10 @@ var en$1 = {
2851
3185
  changed: "Switching disconnects the current session.",
2852
3186
  error: "GitHub connection failed. Retry using the selected connection.",
2853
3187
  invalid: "The selected repository needs a GitHub HTTPS origin.",
2854
- allowPush: "Authorize push",
2855
- fetch: "Fetch",
2856
- pull: "Pull (fast-forward only)",
2857
- push: "Push"
3188
+ allowPush: "Allow sending to GitHub (push)",
3189
+ fetch: "Check GitHub (fetch)",
3190
+ pull: "Get from GitHub (pull)",
3191
+ push: "Send to GitHub (push)"
2858
3192
  };
2859
3193
  var ja$1 = {
2860
3194
  title: "GitHub 接続",
@@ -2877,10 +3211,10 @@ var ja$1 = {
2877
3211
  changed: "方式を切り替えると現在のセッションを切断します。",
2878
3212
  error: "GitHub 接続に失敗しました。選択中の方式で再試行してください。",
2879
3213
  invalid: "選択したリポジトリには GitHub の HTTPS リモート origin が必要です。",
2880
- allowPush: "push を許可する",
2881
- fetch: "Fetch",
2882
- pull: "Pull(fast-forward のみ)",
2883
- push: "Push"
3214
+ allowPush: "GitHub に送ることを許可する(push)",
3215
+ fetch: "GitHub と確かめる(fetch)",
3216
+ pull: "GitHub から取り込む(pull)",
3217
+ push: "GitHub に送る(push)"
2884
3218
  };
2885
3219
  function ConnectionSettings({ settings, locale, readOrigin, runOperation }) {
2886
3220
  const labels = locale.startsWith("ja") ? ja$1 : en$1;
@@ -2916,7 +3250,16 @@ function ConnectionSettings({ settings, locale, readOrigin, runOperation }) {
2916
3250
  if (!remote) throw new Error("INVALID_REMOTE");
2917
3251
  await settings.connect(remote, allowPush);
2918
3252
  });
2919
- return /* @__PURE__ */ React.createElement("details", { className: "twgit-connection" }, /* @__PURE__ */ React.createElement("summary", null, labels.title), !settings.modes.length ? /* @__PURE__ */ React.createElement("p", null, labels.unavailable) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, labels.mode, " ", /* @__PURE__ */ React.createElement("select", {
3253
+ const connected = !!mode && state.status === "connected";
3254
+ return /* @__PURE__ */ React.createElement(React.Fragment, null, runOperation && connected && /* @__PURE__ */ React.createElement("span", { className: "twgit-sync" }, [
3255
+ "fetch",
3256
+ "pull",
3257
+ "push"
3258
+ ].map((operation) => /* @__PURE__ */ React.createElement("button", {
3259
+ key: operation,
3260
+ disabled: busy || !readOrigin || operation === "push" && !write,
3261
+ onClick: () => void run(() => runOperation(operation))
3262
+ }, labels[operation]))), /* @__PURE__ */ React.createElement("details", { className: "twgit-connection" }, /* @__PURE__ */ React.createElement("summary", null, labels.title), /* @__PURE__ */ React.createElement("div", { className: "twgit-connection-body" }, !settings.modes.length ? /* @__PURE__ */ React.createElement("p", null, labels.unavailable) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("label", null, labels.mode, " ", /* @__PURE__ */ React.createElement("select", {
2920
3263
  value: mode !== null && mode !== void 0 ? mode : "",
2921
3264
  disabled: busy,
2922
3265
  onChange: (event) => {
@@ -2950,18 +3293,10 @@ function ConnectionSettings({ settings, locale, readOrigin, runOperation }) {
2950
3293
  }, labels.check), /* @__PURE__ */ React.createElement("button", {
2951
3294
  disabled: busy,
2952
3295
  onClick: () => void run(settings.disconnect)
2953
- }, labels.disconnect), runOperation && state.status === "connected" && [
2954
- "fetch",
2955
- "pull",
2956
- "push"
2957
- ].map((operation) => /* @__PURE__ */ React.createElement("button", {
2958
- key: operation,
2959
- disabled: busy || !readOrigin || operation === "push" && !write,
2960
- onClick: () => void run(() => runOperation(operation))
2961
- }, labels[operation]))), error && /* @__PURE__ */ React.createElement("p", {
3296
+ }, labels.disconnect))))), error && /* @__PURE__ */ React.createElement("p", {
2962
3297
  className: "twgit-error",
2963
3298
  role: "alert"
2964
- }, labels[error])));
3299
+ }, labels[error]));
2965
3300
  }
2966
3301
  // src/pull-request-panel.tsx
2967
3302
  var en = {
@@ -3156,7 +3491,7 @@ var initial = {
3156
3491
  var controllers = /* @__PURE__ */ new WeakMap();
3157
3492
  var watchInterval = 2e3;
3158
3493
  function GitPanel({ document, host, controller }) {
3159
- var _state$repository, _state$snapshot, _state$snapshot2, _state$snapshot3, _state$snapshot4, _state$snapshot5;
3494
+ var _state$repository, _state$snapshot$head$, _state$snapshot$head, _state$snapshot;
3160
3495
  const [visible, setVisible] = useState(false);
3161
3496
  const [review, setReview] = useState(false);
3162
3497
  const [locale, setLocale] = useState(document.documentElement.lang || "en");
@@ -3385,6 +3720,22 @@ function GitPanel({ document, host, controller }) {
3385
3720
  selected,
3386
3721
  diff: await state.repository.readDiff(state.snapshot, selected)
3387
3722
  }));
3723
+ const [element, setElement] = useState(null);
3724
+ const [fileListOpen, setFileListOpen] = useState(false);
3725
+ const projectChanges = useProjectChanges(state.repository, state.snapshot, state.project, state.commit, state.changes);
3726
+ const openFile = (file) => {
3727
+ const change = state.changes.find((item) => item.path === file.path && item.group === file.group);
3728
+ if (!change) return;
3729
+ setElement(null);
3730
+ selectFile(change);
3731
+ };
3732
+ const openElement = (key) => {
3733
+ setElement(key);
3734
+ setState((previous) => _objectSpread2$1(_objectSpread2$1({}, previous), {}, {
3735
+ selected: null,
3736
+ diff: null
3737
+ }));
3738
+ };
3388
3739
  const selectCommit = (commit) => run(async () => ({
3389
3740
  commit,
3390
3741
  changes: await state.repository.historyChanges(state.snapshot, commit),
@@ -3450,6 +3801,8 @@ function GitPanel({ document, host, controller }) {
3450
3801
  "unstaged",
3451
3802
  "untracked"
3452
3803
  ];
3804
+ const projectList = typeof projectChanges === "object" ? projectChanges : null;
3805
+ const selection = projectList && !state.selected ? findElement(projectList.list, element) : null;
3453
3806
  return /* @__PURE__ */ React.createElement("div", { className: "twgit-backdrop" }, /* @__PURE__ */ React.createElement("div", {
3454
3807
  className: "twgit-panel",
3455
3808
  ref: dialog,
@@ -3471,7 +3824,10 @@ function GitPanel({ document, host, controller }) {
3471
3824
  type: "button",
3472
3825
  disabled: state.busy,
3473
3826
  onClick: () => setReview(!review)
3474
- }, review ? labels.localChanges : labels.pullRequests), /* @__PURE__ */ React.createElement("button", { onClick: () => setVisible(false) }, labels.close)), /* @__PURE__ */ React.createElement("div", { className: "twgit-context" }, /* @__PURE__ */ React.createElement("span", null, labels.repository, " · ", ((_state$repository = state.repository) === null || _state$repository === void 0 ? void 0 : _state$repository.root.name) || "—"), /* @__PURE__ */ React.createElement("span", null, labels.project, " · ", state.project === null ? labels.none : state.project || labels.root), /* @__PURE__ */ React.createElement("strong", null, ((_state$snapshot = state.snapshot) === null || _state$snapshot === void 0 ? void 0 : _state$snapshot.branch) || (((_state$snapshot2 = state.snapshot) === null || _state$snapshot2 === void 0 ? void 0 : _state$snapshot2.head) ? labels.detached : labels.unborn)), /* @__PURE__ */ React.createElement("code", null, (_state$snapshot3 = state.snapshot) === null || _state$snapshot3 === void 0 || (_state$snapshot3 = _state$snapshot3.head) === null || _state$snapshot3 === void 0 ? void 0 : _state$snapshot3.slice(0, 12)), /* @__PURE__ */ React.createElement("span", { "data-git-dirty": "" }, dirty ? labels.dirty : labels.saved)), !review && host.connectionSettings && /* @__PURE__ */ React.createElement(ConnectionSettings, {
3827
+ }, review ? labels.localChanges : labels.pullRequests), /* @__PURE__ */ React.createElement("button", { onClick: () => setVisible(false) }, labels.close)), /* @__PURE__ */ React.createElement("div", { className: "twgit-context" }, /* @__PURE__ */ React.createElement("span", null, labels.repository, " · ", ((_state$repository = state.repository) === null || _state$repository === void 0 ? void 0 : _state$repository.root.name) || "—"), /* @__PURE__ */ React.createElement("span", null, labels.project, " · ", state.project === null ? labels.none : state.project || labels.root), /* @__PURE__ */ React.createElement("span", { "data-git-dirty": "" }, dirty ? labels.dirty : labels.saved), state.snapshot && /* @__PURE__ */ React.createElement("span", {
3828
+ "data-git-upstream": state.snapshot.upstream.state,
3829
+ title: labels.checkedAt
3830
+ }, upstreamText(state.snapshot, labels)), !review && host.connectionSettings && /* @__PURE__ */ React.createElement(ConnectionSettings, {
3475
3831
  settings: host.connectionSettings,
3476
3832
  locale,
3477
3833
  runOperation: host.connectionSettings.getSession ? remoteOperation : void 0,
@@ -3480,7 +3836,7 @@ function GitPanel({ document, host, controller }) {
3480
3836
  const remote = await state.repository.readOrigin();
3481
3837
  return epoch.current === ticket ? remote : void 0;
3482
3838
  } : void 0
3483
- }), state.error && /* @__PURE__ */ React.createElement("p", {
3839
+ }), state.snapshot && /* @__PURE__ */ React.createElement("details", { className: "twgit-git-details" }, /* @__PURE__ */ React.createElement("summary", null, labels.details), /* @__PURE__ */ React.createElement("div", { className: "twgit-connection-body" }, /* @__PURE__ */ React.createElement("p", null, labels.branch, " · ", /* @__PURE__ */ React.createElement("strong", null, state.snapshot.branch || (state.snapshot.head ? labels.detached : labels.unborn))), /* @__PURE__ */ React.createElement("p", null, labels.head, " · ", /* @__PURE__ */ React.createElement("code", null, (_state$snapshot$head$ = (_state$snapshot$head = state.snapshot.head) === null || _state$snapshot$head === void 0 ? void 0 : _state$snapshot$head.slice(0, 12)) !== null && _state$snapshot$head$ !== void 0 ? _state$snapshot$head$ : "—")), /* @__PURE__ */ React.createElement("p", null, labels.branches, " · ", state.snapshot.branches.join(", ") || "—"), /* @__PURE__ */ React.createElement("p", null, labels.groups, " · ", labels.gitStaged, " = ", labels.staged, " / ", labels.gitUnstaged, " = ", labels.unstaged, " / ", labels.gitUntracked, " = ", labels.untracked), /* @__PURE__ */ React.createElement("p", null, labels.permissions)))), state.error && /* @__PURE__ */ React.createElement("p", {
3484
3840
  className: "twgit-error",
3485
3841
  role: "alert"
3486
3842
  }, state.error), state.notice && /* @__PURE__ */ React.createElement("p", {
@@ -3503,25 +3859,54 @@ function GitPanel({ document, host, controller }) {
3503
3859
  }, name, "/"))))), /* @__PURE__ */ React.createElement("h3", null, /* @__PURE__ */ React.createElement("button", {
3504
3860
  disabled: state.busy,
3505
3861
  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", {
3862
+ }, labels.worktree)), projectChanges === "loading" && /* @__PURE__ */ React.createElement("p", { role: "status" }, projectChangeLabels(locale).loading), projectChanges === "unreadable" && /* @__PURE__ */ React.createElement("p", null, projectChangeLabels(locale).unreadable), (() => {
3863
+ 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", {
3864
+ className: "twgit-file",
3865
+ "data-git-group": group,
3866
+ disabled: state.busy,
3867
+ "aria-pressed": state.selected === change,
3868
+ onClick: () => openFile(change)
3869
+ }, /* @__PURE__ */ React.createElement("small", { className: "twgit-change-kind" }, !change.after ? labels.deleted : !change.before ? labels.added : labels.modified), " ", change.path))))));
3870
+ return projectList ? /* @__PURE__ */ React.createElement(ProjectChangeListView, {
3871
+ changes: projectList,
3872
+ selected: selection ? element : null,
3873
+ locale,
3874
+ busy: state.busy,
3875
+ onSelect: openElement,
3876
+ onSelectFile: openFile,
3877
+ fileList,
3878
+ fileListOpen,
3879
+ setFileListOpen
3880
+ }) : fileList;
3881
+ })(), !state.changes.length && /* @__PURE__ */ React.createElement("p", null, labels.clean), /* @__PURE__ */ React.createElement("h3", null, labels.history), /* @__PURE__ */ React.createElement("ol", { className: "twgit-history" }, (_state$snapshot = state.snapshot) === null || _state$snapshot === void 0 ? void 0 : _state$snapshot.commits.map((commit) => /* @__PURE__ */ React.createElement("li", { key: commit.oid }, /* @__PURE__ */ React.createElement("button", {
3513
3882
  disabled: state.busy,
3514
3883
  "aria-pressed": state.commit === commit.oid,
3515
3884
  title: commit.oid,
3516
3885
  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, {
3886
+ }, commit.message.split("\n")[0]), /* @__PURE__ */ React.createElement("small", null, commit.author, " · ", (/* @__PURE__ */ new Date(commit.timestamp * 1e3)).toLocaleString(locale)))))), /* @__PURE__ */ React.createElement("main", { className: "twgit-diff" }, selection && projectList ? /* @__PURE__ */ React.createElement(ProjectElementView, {
3887
+ changes: projectList,
3888
+ selection,
3889
+ locale,
3890
+ renderBlock: host.renderReviewBlock,
3891
+ onSelectFile: openFile
3892
+ }) : state.selected ? /* @__PURE__ */ React.createElement(FileDiffView, {
3518
3893
  change: state.selected,
3519
3894
  diff: state.diff,
3520
3895
  labels,
3521
3896
  locale,
3522
3897
  renderBlock: host.renderReviewBlock,
3523
3898
  loadAsset
3524
- }) : /* @__PURE__ */ React.createElement("p", null, labels.select))), /* @__PURE__ */ React.createElement("footer", null, labels.permissions)));
3899
+ }) : /* @__PURE__ */ React.createElement("p", null, labels.select)))));
3900
+ }
3901
+ /** The GitHub relation in plain words, with the number of records where it helps. */
3902
+ function upstreamText({ upstream }, labels) {
3903
+ switch (upstream.state) {
3904
+ case "same": return labels.upstreamSame;
3905
+ case "ahead": return `${labels.upstreamAhead} · ${upstream.ahead} ${labels.records}`;
3906
+ case "behind": return `${labels.upstreamBehind} · ${upstream.behind} ${labels.records}`;
3907
+ case "diverged": return `${labels.upstreamDiverged} · +${upstream.ahead} / −${upstream.behind}`;
3908
+ default: return labels.upstreamUnknown;
3909
+ }
3525
3910
  }
3526
3911
  function mountGitPanel({ vm, document, host }) {
3527
3912
  var _document$defaultView2;