@kubohiroya/turbowarp-plus-editor-git 0.9.0 → 0.11.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/editor-block-locator.d.ts +27 -0
- package/dist/file-diff-view.d.ts +3 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.js +243 -101
- package/dist/messages.d.ts +34 -0
- package/dist/project-change-list.d.ts +3 -1
- package/dist/project-source-diff.js +1 -1
- package/dist/project-source-review.d.ts +6 -1
- package/dist/pull-request-panel.d.ts +4 -1
- package/dist/{repository-VBp-UNWc.js → repository-CtvWYPQ8.js} +120 -4
- package/dist/repository.d.ts +17 -0
- package/dist/repository.js +1 -1
- package/dist/{scratch-block-diff-renderer-DZR8ep-T.js → scratch-block-diff-renderer-CgAGfmAc.js} +65 -9
- package/dist/style.css +15 -2
- package/package.json +1 -1
- package/src/connection-settings.tsx +14 -8
- package/src/editor-block-locator.ts +55 -0
- package/src/file-diff-view.tsx +8 -5
- package/src/index.tsx +105 -29
- package/src/messages.ts +34 -26
- package/src/project-change-list.tsx +6 -5
- package/src/project-source-review.tsx +57 -11
- package/src/pull-request-panel.tsx +9 -4
- package/src/repository.ts +76 -3
- package/src/style.css +15 -2
package/dist/messages.d.ts
CHANGED
|
@@ -51,6 +51,23 @@ export declare const messages: {
|
|
|
51
51
|
remoteDone: string;
|
|
52
52
|
pulledProject: string;
|
|
53
53
|
confirmPush: string;
|
|
54
|
+
details: string;
|
|
55
|
+
branch: string;
|
|
56
|
+
head: string;
|
|
57
|
+
groups: string;
|
|
58
|
+
added: string;
|
|
59
|
+
modified: string;
|
|
60
|
+
deleted: string;
|
|
61
|
+
gitStaged: string;
|
|
62
|
+
gitUnstaged: string;
|
|
63
|
+
gitUntracked: string;
|
|
64
|
+
upstreamSame: string;
|
|
65
|
+
upstreamAhead: string;
|
|
66
|
+
upstreamBehind: string;
|
|
67
|
+
upstreamDiverged: string;
|
|
68
|
+
upstreamUnknown: string;
|
|
69
|
+
records: string;
|
|
70
|
+
checkedAt: string;
|
|
54
71
|
failed: string;
|
|
55
72
|
};
|
|
56
73
|
ja: {
|
|
@@ -105,6 +122,23 @@ export declare const messages: {
|
|
|
105
122
|
remoteDone: string;
|
|
106
123
|
pulledProject: string;
|
|
107
124
|
confirmPush: string;
|
|
125
|
+
details: string;
|
|
126
|
+
branch: string;
|
|
127
|
+
head: string;
|
|
128
|
+
groups: string;
|
|
129
|
+
added: string;
|
|
130
|
+
modified: string;
|
|
131
|
+
deleted: string;
|
|
132
|
+
gitStaged: string;
|
|
133
|
+
gitUnstaged: string;
|
|
134
|
+
gitUntracked: string;
|
|
135
|
+
upstreamSame: string;
|
|
136
|
+
upstreamAhead: string;
|
|
137
|
+
upstreamBehind: string;
|
|
138
|
+
upstreamDiverged: string;
|
|
139
|
+
upstreamUnknown: string;
|
|
140
|
+
records: string;
|
|
141
|
+
checkedAt: string;
|
|
108
142
|
failed: string;
|
|
109
143
|
};
|
|
110
144
|
};
|
|
@@ -3,6 +3,7 @@ import type { ChangedFile, ProjectChangeList, ProjectElementChange, ProjectTarge
|
|
|
3
3
|
import type { ProjectAssetLoader } from './project-assets.js';
|
|
4
4
|
import type { ChangeGroup, FileChange, Repository, Snapshot } from './repository.js';
|
|
5
5
|
import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
|
|
6
|
+
import type { ProjectBlockLocator } from './block-diff-location.js';
|
|
6
7
|
declare const en: {
|
|
7
8
|
title: string;
|
|
8
9
|
stage: string;
|
|
@@ -68,7 +69,7 @@ export declare function findElement(list: ProjectChangeList, key: string | null)
|
|
|
68
69
|
target: null;
|
|
69
70
|
} | null;
|
|
70
71
|
/** 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
|
+
export declare function ProjectElementView({ changes, selection, locale, renderBlock, onSelectFile, locateBlock }: {
|
|
72
73
|
changes: ProjectChanges;
|
|
73
74
|
selection: {
|
|
74
75
|
element: ProjectElementChange;
|
|
@@ -77,5 +78,6 @@ export declare function ProjectElementView({ changes, selection, locale, renderB
|
|
|
77
78
|
locale: string;
|
|
78
79
|
renderBlock?: ProjectBlockRenderer;
|
|
79
80
|
onSelectFile(file: ChangedFile): void;
|
|
81
|
+
locateBlock?: ProjectBlockLocator;
|
|
80
82
|
}): React.JSX.Element;
|
|
81
83
|
export {};
|
|
@@ -1,4 +1,4 @@
|
|
|
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-
|
|
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-CgAGfmAc.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
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
|
|
3
|
+
import type { ProjectBlockLocator } from './block-diff-location.js';
|
|
3
4
|
import type { ProjectAssetLoader } from './project-assets.js';
|
|
4
5
|
export type ProjectSourceReviewFile = {
|
|
5
6
|
path: string;
|
|
@@ -10,6 +11,8 @@ export type ProjectSourceReviewFile = {
|
|
|
10
11
|
patch: string;
|
|
11
12
|
beforeSource?: string;
|
|
12
13
|
loadAsset?: ProjectAssetLoader;
|
|
14
|
+
/** Offers "Highlight in Editor" on this file's after version, which the caller has matched with the open project. */
|
|
15
|
+
locateBlock?: ProjectBlockLocator;
|
|
13
16
|
} | {
|
|
14
17
|
status: 'added' | 'removed' | 'updated';
|
|
15
18
|
});
|
|
@@ -32,6 +35,8 @@ export interface ProjectSourceComparisonProps {
|
|
|
32
35
|
fallback?: React.ReactNode;
|
|
33
36
|
/** Reads the images of both versions for thumbnails. Without it, stages and sprites are shown by name. */
|
|
34
37
|
loadAsset?: ProjectAssetLoader;
|
|
38
|
+
/** Offers "Highlight in Editor" on the after version, which the caller has matched with the open project. */
|
|
39
|
+
locateBlock?: ProjectBlockLocator;
|
|
35
40
|
/** Shows only the code of this stage or sprite, without the overview and the changes outside the code. */
|
|
36
41
|
target?: {
|
|
37
42
|
isStage: boolean;
|
|
@@ -39,5 +44,5 @@ export interface ProjectSourceComparisonProps {
|
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
/** Compares two versions of a project.source.json by stage, sprite and code, as the pull request review does. */
|
|
42
|
-
export declare function ProjectSourceComparison({ before, after, renderBlock, locale, fallback, loadAsset, target }: ProjectSourceComparisonProps): React.JSX.Element;
|
|
47
|
+
export declare function ProjectSourceComparison({ before, after, renderBlock, locale, fallback, loadAsset, locateBlock, target }: ProjectSourceComparisonProps): React.JSX.Element;
|
|
43
48
|
export declare function ProjectSourceReview({ title, description, verification, files, renderBlock, locale }: ProjectSourceReviewProps): React.JSX.Element;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { ProjectBlockRenderer } from './scratch-block-diff-renderer.js';
|
|
3
3
|
import type { Repository } from './repository.js';
|
|
4
|
-
|
|
4
|
+
import type { ProjectBlockLocator } from './block-diff-location.js';
|
|
5
|
+
export declare function PullRequestReviewPanel({ repository, locale, renderBlock, locateFor }: {
|
|
5
6
|
repository: Repository;
|
|
6
7
|
locale: string;
|
|
7
8
|
renderBlock: ProjectBlockRenderer;
|
|
9
|
+
/** Returns a locator for a project file whose PR head matches the open project, or `undefined`. */
|
|
10
|
+
locateFor?(path: string, headSha: string): ProjectBlockLocator | undefined;
|
|
8
11
|
}): React.JSX.Element;
|
|
@@ -14562,6 +14562,37 @@ async function fetch({ fs, http, onProgress, onMessage, onAuth, onAuthSuccess, o
|
|
|
14562
14562
|
}
|
|
14563
14563
|
}
|
|
14564
14564
|
/**
|
|
14565
|
+
* Find the merge base for a set of commits
|
|
14566
|
+
*
|
|
14567
|
+
* @param {object} args
|
|
14568
|
+
* @param {FsClient} args.fs - a file system client
|
|
14569
|
+
* @param {string} [args.dir] - The [working tree](dir-vs-gitdir.md) directory path
|
|
14570
|
+
* @param {string} [args.gitdir=join(dir,'.git')] - [required] The [git directory](dir-vs-gitdir.md) path
|
|
14571
|
+
* @param {string[]} args.oids - Which commits
|
|
14572
|
+
* @param {object} [args.cache] - a [cache](cache.md) object
|
|
14573
|
+
*
|
|
14574
|
+
*/
|
|
14575
|
+
async function findMergeBase({ fs, dir, gitdir = join(dir, ".git"), oids, cache = {} }) {
|
|
14576
|
+
try {
|
|
14577
|
+
assertParameter("fs", fs);
|
|
14578
|
+
assertParameter("gitdir", gitdir);
|
|
14579
|
+
assertParameter("oids", oids);
|
|
14580
|
+
const fsp = new FileSystem(fs);
|
|
14581
|
+
return await _findMergeBase({
|
|
14582
|
+
fs: fsp,
|
|
14583
|
+
cache,
|
|
14584
|
+
gitdir: await discoverGitdir({
|
|
14585
|
+
fsp,
|
|
14586
|
+
dotgit: gitdir
|
|
14587
|
+
}),
|
|
14588
|
+
oids
|
|
14589
|
+
});
|
|
14590
|
+
} catch (err) {
|
|
14591
|
+
err.caller = "git.findMergeBase";
|
|
14592
|
+
throw err;
|
|
14593
|
+
}
|
|
14594
|
+
}
|
|
14595
|
+
/**
|
|
14565
14596
|
* Read an entry from the git config files.
|
|
14566
14597
|
*
|
|
14567
14598
|
* *Caveats:*
|
|
@@ -16800,6 +16831,9 @@ var emptyTree = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
|
16800
16831
|
var textLimit = 1048576;
|
|
16801
16832
|
var inScope = (path, scope) => !scope || path === scope || path.startsWith(scope + "/");
|
|
16802
16833
|
var probeLimit = 5e3;
|
|
16834
|
+
/** Most commits read to count how far a branch is ahead of or behind its tracking branch. */
|
|
16835
|
+
var upstreamWalkLimit = 1e3;
|
|
16836
|
+
var trackingRef = (branchRef) => "refs/remotes/origin/" + branchRef.slice(11);
|
|
16803
16837
|
var sameMetadata = (a, b) => a.size === b.size && [...a].every(([path, bytes]) => {
|
|
16804
16838
|
var _b$get;
|
|
16805
16839
|
return sameBytes(bytes, (_b$get = b.get(path)) !== null && _b$get !== void 0 ? _b$get : null);
|
|
@@ -16839,7 +16873,7 @@ async function openRepository(root) {
|
|
|
16839
16873
|
if (text.startsWith("ref: refs/heads/")) {
|
|
16840
16874
|
const ref = text.slice(5);
|
|
16841
16875
|
pathParts(ref);
|
|
16842
|
-
paths.push("/.git/" + ref);
|
|
16876
|
+
paths.push("/.git/" + ref, "/.git/" + trackingRef(ref));
|
|
16843
16877
|
} else if (!/^[0-9a-f]{40}$/.test(text)) throw new GitPanelError("invalid");
|
|
16844
16878
|
const result = /* @__PURE__ */ new Map();
|
|
16845
16879
|
for (const path of paths) {
|
|
@@ -16885,7 +16919,7 @@ async function openRepository(root) {
|
|
|
16885
16919
|
} catch (_unused) {
|
|
16886
16920
|
return "locked";
|
|
16887
16921
|
}
|
|
16888
|
-
paths.push(".git/" + head.slice(5));
|
|
16922
|
+
paths.push(".git/" + head.slice(5), ".git/" + trackingRef(head.slice(5)));
|
|
16889
16923
|
}
|
|
16890
16924
|
const lines = [];
|
|
16891
16925
|
for (const path of paths) {
|
|
@@ -16957,6 +16991,86 @@ async function openRepository(root) {
|
|
|
16957
16991
|
mode
|
|
16958
16992
|
}]));
|
|
16959
16993
|
};
|
|
16994
|
+
/**
|
|
16995
|
+
* Compares `refs/heads/<branch>` with `refs/remotes/origin/<branch>` from local refs only. Counts by painting
|
|
16996
|
+
* commits from both tips in committer-time order until every queued commit is reachable from both, like
|
|
16997
|
+
* `git rev-list --left-right --count`, reading at most `upstreamWalkLimit` commits.
|
|
16998
|
+
*/
|
|
16999
|
+
const upstreamRelation = async (branch, head) => {
|
|
17000
|
+
const unknown = {
|
|
17001
|
+
state: "unknown",
|
|
17002
|
+
ahead: 0,
|
|
17003
|
+
behind: 0
|
|
17004
|
+
};
|
|
17005
|
+
if (!branch || !head) return unknown;
|
|
17006
|
+
pathParts(trackingRef("refs/heads/" + branch));
|
|
17007
|
+
try {
|
|
17008
|
+
const remote = await resolveRef(_objectSpread2(_objectSpread2({}, options), {}, { ref: trackingRef("refs/heads/" + branch) }));
|
|
17009
|
+
if (remote === head) return {
|
|
17010
|
+
state: "same",
|
|
17011
|
+
ahead: 0,
|
|
17012
|
+
behind: 0
|
|
17013
|
+
};
|
|
17014
|
+
const bases = await findMergeBase(_objectSpread2(_objectSpread2({}, options), {}, {
|
|
17015
|
+
cache,
|
|
17016
|
+
oids: [head, remote]
|
|
17017
|
+
}));
|
|
17018
|
+
const hasAhead = !bases.includes(head), hasBehind = !bases.includes(remote);
|
|
17019
|
+
const local = 1, tracking = 2, both = 3;
|
|
17020
|
+
const flags = /* @__PURE__ */ new Map([[head, local], [remote, tracking]]);
|
|
17021
|
+
const time = async (oid) => (await readCommit(_objectSpread2(_objectSpread2({}, options), {}, {
|
|
17022
|
+
cache,
|
|
17023
|
+
oid
|
|
17024
|
+
}))).commit;
|
|
17025
|
+
const queue = [];
|
|
17026
|
+
const enqueue = async (oid) => {
|
|
17027
|
+
const commit = await time(oid);
|
|
17028
|
+
queue.push({
|
|
17029
|
+
oid,
|
|
17030
|
+
time: commit.committer.timestamp,
|
|
17031
|
+
parents: commit.parent
|
|
17032
|
+
});
|
|
17033
|
+
};
|
|
17034
|
+
await enqueue(head);
|
|
17035
|
+
await enqueue(remote);
|
|
17036
|
+
let read = 2;
|
|
17037
|
+
let complete = true;
|
|
17038
|
+
while (queue.some((item) => flags.get(item.oid) !== both)) {
|
|
17039
|
+
if (read >= 1e3) {
|
|
17040
|
+
complete = false;
|
|
17041
|
+
break;
|
|
17042
|
+
}
|
|
17043
|
+
queue.sort((a, b) => b.time - a.time);
|
|
17044
|
+
const item = queue.shift();
|
|
17045
|
+
const flag = flags.get(item.oid);
|
|
17046
|
+
for (const parent of item.parents) {
|
|
17047
|
+
var _flags$get;
|
|
17048
|
+
const previous = (_flags$get = flags.get(parent)) !== null && _flags$get !== void 0 ? _flags$get : 0;
|
|
17049
|
+
if ((previous | flag) === previous) continue;
|
|
17050
|
+
flags.set(parent, previous | flag);
|
|
17051
|
+
if (!queue.some((entry) => entry.oid === parent)) {
|
|
17052
|
+
await enqueue(parent);
|
|
17053
|
+
read++;
|
|
17054
|
+
}
|
|
17055
|
+
}
|
|
17056
|
+
}
|
|
17057
|
+
let ahead = 0, behind = 0;
|
|
17058
|
+
for (const flag of flags.values()) if (flag === local) ahead++;
|
|
17059
|
+
else if (flag === tracking) behind++;
|
|
17060
|
+
if (!complete) {
|
|
17061
|
+
ahead = hasAhead ? upstreamWalkLimit : 0;
|
|
17062
|
+
behind = hasBehind ? upstreamWalkLimit : 0;
|
|
17063
|
+
}
|
|
17064
|
+
return {
|
|
17065
|
+
state: hasAhead && hasBehind ? "diverged" : hasAhead ? "ahead" : hasBehind ? "behind" : "same",
|
|
17066
|
+
ahead: hasAhead ? Math.max(ahead, 1) : 0,
|
|
17067
|
+
behind: hasBehind ? Math.max(behind, 1) : 0
|
|
17068
|
+
};
|
|
17069
|
+
} catch (error) {
|
|
17070
|
+
if (errorCode(error) === "NotFoundError") return unknown;
|
|
17071
|
+
throw error;
|
|
17072
|
+
}
|
|
17073
|
+
};
|
|
16960
17074
|
const assertSnapshot = async (snapshot) => {
|
|
16961
17075
|
if (!sameMetadata(snapshot.metadata, await metadata())) throw new GitPanelError("changed");
|
|
16962
17076
|
};
|
|
@@ -16985,6 +17099,7 @@ async function openRepository(root) {
|
|
|
16985
17099
|
author: commit.author.name,
|
|
16986
17100
|
timestamp: commit.author.timestamp
|
|
16987
17101
|
})) : [];
|
|
17102
|
+
const upstream = await upstreamRelation(branch || void 0, head);
|
|
16988
17103
|
const matrix = await statusMatrix(_objectSpread2(_objectSpread2({}, options), {}, {
|
|
16989
17104
|
cache,
|
|
16990
17105
|
refresh: false,
|
|
@@ -17033,7 +17148,8 @@ async function openRepository(root) {
|
|
|
17033
17148
|
changes,
|
|
17034
17149
|
scope,
|
|
17035
17150
|
metadata: before,
|
|
17036
|
-
probe: fingerprint
|
|
17151
|
+
probe: fingerprint,
|
|
17152
|
+
upstream
|
|
17037
17153
|
};
|
|
17038
17154
|
};
|
|
17039
17155
|
const historyChanges = async (snapshot, oid) => {
|
|
@@ -17340,4 +17456,4 @@ async function openRepository(root) {
|
|
|
17340
17456
|
projectAssetLoader: (snapshot, folder, comparison) => ({ version, md5ext, signal }) => !isAssetName(md5ext) || signal.aborted ? Promise.resolve(null) : serial(() => readFolderAsset(snapshot, pathParts(folder), () => comparisonSource(snapshot, comparison, version), md5ext, signal)).catch(() => null)
|
|
17341
17457
|
};
|
|
17342
17458
|
}
|
|
17343
|
-
export {
|
|
17459
|
+
export { createReadOnlyFs as i, upstreamWalkLimit as n, GitPanelError as r, openRepository as t };
|
package/dist/repository.d.ts
CHANGED
|
@@ -40,6 +40,21 @@ export interface Snapshot {
|
|
|
40
40
|
metadata: Metadata;
|
|
41
41
|
/** Size and time fingerprint taken before the read. Compare it with `probe(scope)` to see later changes. */
|
|
42
42
|
probe: string;
|
|
43
|
+
/**
|
|
44
|
+
* How the current branch stands against `refs/remotes/origin/<branch>` as of the last fetch, from local refs only.
|
|
45
|
+
* `unknown` when there is no current branch or no tracking ref yet.
|
|
46
|
+
*/
|
|
47
|
+
upstream: UpstreamRelation;
|
|
48
|
+
}
|
|
49
|
+
export interface UpstreamRelation {
|
|
50
|
+
state: 'same' | 'ahead' | 'behind' | 'diverged' | 'unknown';
|
|
51
|
+
/**
|
|
52
|
+
* Commits on the local branch that the tracking branch lacks, and the reverse. Zero when unknown. Counting reads at
|
|
53
|
+
* most `upstreamWalkLimit` (1000) commits; past that, a side that has commits of its own reports exactly that limit,
|
|
54
|
+
* meaning "this many or more". The state itself comes from the merge base and stays exact.
|
|
55
|
+
*/
|
|
56
|
+
ahead: number;
|
|
57
|
+
behind: number;
|
|
43
58
|
}
|
|
44
59
|
export interface FileDiff {
|
|
45
60
|
binary: boolean;
|
|
@@ -56,6 +71,8 @@ export interface FileDiff {
|
|
|
56
71
|
afterSize: number;
|
|
57
72
|
tooLarge: boolean;
|
|
58
73
|
}
|
|
74
|
+
/** Most commits read to count how far a branch is ahead of or behind its tracking branch. */
|
|
75
|
+
export declare const upstreamWalkLimit = 1000;
|
|
59
76
|
export declare function openRepository(root: FileSystemDirectoryHandle): Promise<{
|
|
60
77
|
root: FileSystemDirectoryHandle;
|
|
61
78
|
openProject: (path: string) => Promise<FileSystemDirectoryHandle>;
|
package/dist/repository.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { r as GitPanelError, i as createReadOnlyFs, t as openRepository, n as upstreamWalkLimit } from "./repository-CtvWYPQ8.js";
|
package/dist/{scratch-block-diff-renderer-DZR8ep-T.js → scratch-block-diff-renderer-CgAGfmAc.js}
RENAMED
|
@@ -1007,7 +1007,11 @@ var ja = {
|
|
|
1007
1007
|
script: "コード",
|
|
1008
1008
|
noChanges: "作品に内容の変更はありません。",
|
|
1009
1009
|
comparison: "作品の変更",
|
|
1010
|
-
unreadable: "作品データとして読み込めないため、テキストの差分を表示します。"
|
|
1010
|
+
unreadable: "作品データとして読み込めないため、テキストの差分を表示します。",
|
|
1011
|
+
locate: "編集画面で強調",
|
|
1012
|
+
locating: "編集画面でブロックを検索中…",
|
|
1013
|
+
located: "編集画面で強調しました。",
|
|
1014
|
+
missingLocation: "編集画面に該当する版またはブロックがありません。"
|
|
1011
1015
|
};
|
|
1012
1016
|
var en = {
|
|
1013
1017
|
review: "Change review",
|
|
@@ -1037,7 +1041,11 @@ var en = {
|
|
|
1037
1041
|
script: "Code",
|
|
1038
1042
|
noChanges: "No changes to the project contents.",
|
|
1039
1043
|
comparison: "Project changes",
|
|
1040
|
-
unreadable: "This file could not be read as project data, so its text diff is shown."
|
|
1044
|
+
unreadable: "This file could not be read as project data, so its text diff is shown.",
|
|
1045
|
+
locate: "Highlight in Editor",
|
|
1046
|
+
locating: "Finding the block in the Editor…",
|
|
1047
|
+
located: "Highlighted in the Editor.",
|
|
1048
|
+
missingLocation: "This version or block is unavailable in the Editor."
|
|
1041
1049
|
};
|
|
1042
1050
|
var properties = /* @__PURE__ */ new Map([
|
|
1043
1051
|
["variables", ["変数", "Variables"]],
|
|
@@ -1068,7 +1076,49 @@ function scriptTitle(side, labels) {
|
|
|
1068
1076
|
}
|
|
1069
1077
|
return typeof block.opcode === "string" ? block.opcode : labels.script;
|
|
1070
1078
|
}
|
|
1071
|
-
|
|
1079
|
+
var sideKey = (side) => JSON.stringify([
|
|
1080
|
+
side.target.isStage,
|
|
1081
|
+
side.target.name,
|
|
1082
|
+
side.id
|
|
1083
|
+
]);
|
|
1084
|
+
/** Highlights the after version of a script in the Editor, one request per comparison, and cancels it on unmount. */
|
|
1085
|
+
function useBlockLocation(comparison, locateBlock) {
|
|
1086
|
+
const request = useRef();
|
|
1087
|
+
const [location, setLocation] = useState();
|
|
1088
|
+
useEffect(() => () => {
|
|
1089
|
+
var _request$current;
|
|
1090
|
+
return (_request$current = request.current) === null || _request$current === void 0 ? void 0 : _request$current.abort();
|
|
1091
|
+
}, [comparison, locateBlock]);
|
|
1092
|
+
const locate = async (side) => {
|
|
1093
|
+
var _request$current2;
|
|
1094
|
+
if (!locateBlock) return;
|
|
1095
|
+
(_request$current2 = request.current) === null || _request$current2 === void 0 || _request$current2.abort();
|
|
1096
|
+
const controller = new AbortController();
|
|
1097
|
+
request.current = controller;
|
|
1098
|
+
const selected = {
|
|
1099
|
+
comparison,
|
|
1100
|
+
key: sideKey(side),
|
|
1101
|
+
locator: locateBlock
|
|
1102
|
+
};
|
|
1103
|
+
setLocation(_objectSpread2(_objectSpread2({}, selected), {}, { status: "locating" }));
|
|
1104
|
+
let found = false;
|
|
1105
|
+
try {
|
|
1106
|
+
found = await locateBlock({
|
|
1107
|
+
target: side.target,
|
|
1108
|
+
id: side.id,
|
|
1109
|
+
version: "after",
|
|
1110
|
+
parts: [],
|
|
1111
|
+
signal: controller.signal
|
|
1112
|
+
});
|
|
1113
|
+
} catch (_unused) {}
|
|
1114
|
+
if (!controller.signal.aborted) setLocation(_objectSpread2(_objectSpread2({}, selected), {}, { status: found ? "located" : "missingLocation" }));
|
|
1115
|
+
};
|
|
1116
|
+
return {
|
|
1117
|
+
locate: locateBlock && ((side) => void locate(side)),
|
|
1118
|
+
status: (side) => (location === null || location === void 0 ? void 0 : location.comparison) === comparison && location.locator === locateBlock && location.key === sideKey(side) ? location.status : void 0
|
|
1119
|
+
};
|
|
1120
|
+
}
|
|
1121
|
+
function ScriptComparison({ script, labels, reviewed = false, setReviewed, renderBlock, loadAsset, location }) {
|
|
1072
1122
|
const [open, setOpen] = useState(false);
|
|
1073
1123
|
const { sync, toggle } = usePreviewSync(labels === ja ? "ja" : "en");
|
|
1074
1124
|
const status = !script.before.length ? "added" : !script.after.length ? "removed" : script.before.length !== 1 || script.after.length !== 1 ? "reorganized" : "updated";
|
|
@@ -1088,7 +1138,9 @@ function ScriptComparison({ script, labels, reviewed = false, setReviewed, rende
|
|
|
1088
1138
|
labels,
|
|
1089
1139
|
version,
|
|
1090
1140
|
loadAsset,
|
|
1091
|
-
sync
|
|
1141
|
+
sync,
|
|
1142
|
+
onLocate: version === "after" && (location === null || location === void 0 ? void 0 : location.locate) ? () => location.locate(side) : void 0,
|
|
1143
|
+
locationStatus: version === "after" ? location === null || location === void 0 ? void 0 : location.status(side) : void 0
|
|
1092
1144
|
})) : /* @__PURE__ */ React.createElement(BlockPreview, {
|
|
1093
1145
|
title: labels[version],
|
|
1094
1146
|
renderBlock,
|
|
@@ -1132,7 +1184,8 @@ function TargetThumbnails({ target, labels, loadAsset }) {
|
|
|
1132
1184
|
}));
|
|
1133
1185
|
}
|
|
1134
1186
|
/** Lists the changed stages and sprites of one project file, each with its code comparisons. */
|
|
1135
|
-
function ComparisonTargets({ comparison, labels, renderBlock, loadAsset, review }) {
|
|
1187
|
+
function ComparisonTargets({ comparison, labels, renderBlock, loadAsset, locateBlock, review }) {
|
|
1188
|
+
const location = useBlockLocation(comparison, locateBlock);
|
|
1136
1189
|
return /* @__PURE__ */ React.createElement(React.Fragment, null, comparison.properties.length > 0 && /* @__PURE__ */ React.createElement("p", null, labels.properties, " · ", propertyNames(comparison.properties, labels)), !comparison.targets.length && !comparison.properties.length && /* @__PURE__ */ React.createElement("p", null, labels.noChanges), comparison.targets.map((target) => /* @__PURE__ */ React.createElement("section", {
|
|
1137
1190
|
className: "twgit-review-target",
|
|
1138
1191
|
key: JSON.stringify([target.isStage, target.name])
|
|
@@ -1153,6 +1206,7 @@ function ComparisonTargets({ comparison, labels, renderBlock, loadAsset, review
|
|
|
1153
1206
|
labels,
|
|
1154
1207
|
renderBlock,
|
|
1155
1208
|
loadAsset,
|
|
1209
|
+
location,
|
|
1156
1210
|
reviewed: review === null || review === void 0 ? void 0 : review.keys.has(key),
|
|
1157
1211
|
setReviewed: review && ((value) => review.set(key, value))
|
|
1158
1212
|
});
|
|
@@ -1160,14 +1214,14 @@ function ComparisonTargets({ comparison, labels, renderBlock, loadAsset, review
|
|
|
1160
1214
|
}
|
|
1161
1215
|
var countScripts = (comparison) => comparison.targets.reduce((count, target) => count + target.scripts.length, 0);
|
|
1162
1216
|
/** Compares two versions of a project.source.json by stage, sprite and code, as the pull request review does. */
|
|
1163
|
-
function ProjectSourceComparison({ before, after, renderBlock, locale = "en", fallback, loadAsset, target }) {
|
|
1217
|
+
function ProjectSourceComparison({ before, after, renderBlock, locale = "en", fallback, loadAsset, locateBlock, target }) {
|
|
1164
1218
|
const labels = locale.startsWith("ja") ? ja : en;
|
|
1165
1219
|
const only = target && JSON.stringify([target.isStage, target.isStage || target.name]);
|
|
1166
1220
|
const comparison = useMemo(() => {
|
|
1167
1221
|
let result;
|
|
1168
1222
|
try {
|
|
1169
1223
|
result = compareProjectForReview(parseProjectSource(before), parseProjectSource(after));
|
|
1170
|
-
} catch (
|
|
1224
|
+
} catch (_unused2) {
|
|
1171
1225
|
return null;
|
|
1172
1226
|
}
|
|
1173
1227
|
if (!only) return result;
|
|
@@ -1191,7 +1245,8 @@ function ProjectSourceComparison({ before, after, renderBlock, locale = "en", fa
|
|
|
1191
1245
|
comparison,
|
|
1192
1246
|
labels,
|
|
1193
1247
|
renderBlock,
|
|
1194
|
-
loadAsset
|
|
1248
|
+
loadAsset,
|
|
1249
|
+
locateBlock
|
|
1195
1250
|
}));
|
|
1196
1251
|
}
|
|
1197
1252
|
function ProjectSourceReview({ title, description, verification, files, renderBlock, locale = "en" }) {
|
|
@@ -1207,7 +1262,7 @@ function ProjectSourceReview({ title, description, verification, files, renderBl
|
|
|
1207
1262
|
properties: []
|
|
1208
1263
|
}
|
|
1209
1264
|
};
|
|
1210
|
-
} catch (
|
|
1265
|
+
} catch (_unused3) {
|
|
1211
1266
|
return {
|
|
1212
1267
|
file,
|
|
1213
1268
|
invalid: true
|
|
@@ -1244,6 +1299,7 @@ function ProjectSourceReview({ title, description, verification, files, renderBl
|
|
|
1244
1299
|
labels,
|
|
1245
1300
|
renderBlock,
|
|
1246
1301
|
loadAsset: "loadAsset" in file ? file.loadAsset : void 0,
|
|
1302
|
+
locateBlock: "locateBlock" in file ? file.locateBlock : void 0,
|
|
1247
1303
|
review: {
|
|
1248
1304
|
file: file.path,
|
|
1249
1305
|
keys: reviewed,
|
package/dist/style.css
CHANGED
|
@@ -116,14 +116,24 @@
|
|
|
116
116
|
.twgit-panel button:focus-visible, .twgit-panel summary:focus-visible { outline: 2px solid Highlight; outline-offset: 2px; }
|
|
117
117
|
.twgit-panel button[aria-pressed="true"] { color: HighlightText; background: Highlight; }
|
|
118
118
|
.twgit-context { display: flex; gap: 8px 20px; flex-wrap: wrap; padding: 8px 16px; }
|
|
119
|
-
.twgit-
|
|
119
|
+
.twgit-context { align-items: center; }
|
|
120
|
+
.twgit-connection, .twgit-git-details { position: relative; }
|
|
121
|
+
.twgit-connection > summary, .twgit-git-details > summary { cursor: pointer; }
|
|
122
|
+
/* Settings and Git details open over the list, so the list and the diff stay where they are. */
|
|
123
|
+
.twgit-connection-body { position: absolute; z-index: 5; top: calc(100% + 4px); left: 0; width: min(440px, 80vw); max-height: 60vh;
|
|
124
|
+
overflow: auto; padding: 10px 12px; background: Canvas; color: CanvasText; border: 1px solid GrayText; border-radius: 6px;
|
|
125
|
+
box-shadow: 0 8px 24px #0004; }
|
|
126
|
+
.twgit-connection-body > p { margin: 4px 0; overflow-wrap: anywhere; }
|
|
127
|
+
.twgit-sync { display: inline-flex; flex-wrap: wrap; gap: 4px; }
|
|
128
|
+
.twgit-change-kind { display: inline-block; min-width: 2.6em; font-size: 11px; opacity: .8; }
|
|
129
|
+
.twgit-file-details { font-size: 12px; margin-bottom: 6px; }
|
|
130
|
+
.twgit-file-details > p { margin: 2px 0; }
|
|
120
131
|
.twgit-content { display: grid; grid-template-columns: minmax(230px, 30%) minmax(0, 1fr); flex: 1; min-height: 0; }
|
|
121
132
|
.twgit-panel aside { padding: 0 14px 14px; overflow: auto; border-right: 1px solid GrayText; }
|
|
122
133
|
.twgit-panel aside ul { list-style: none; padding: 0; margin: 4px 0; }
|
|
123
134
|
.twgit-panel aside li { overflow-wrap: anywhere; }
|
|
124
135
|
.twgit-panel summary { cursor: pointer; }
|
|
125
136
|
.twgit-panel .twgit-file { width: 100%; border: 0; margin: 1px 0; }
|
|
126
|
-
.twgit-file span { display: inline-block; width: 1.4em; font-family: monospace; }
|
|
127
137
|
.twgit-diff { padding: 0 16px 16px; min-width: 0; overflow: auto; }
|
|
128
138
|
.twgit-diff .diff { font-size: 12px; }
|
|
129
139
|
.twgit-history { list-style: none; margin: 0; padding: 0; }
|
|
@@ -152,4 +162,7 @@
|
|
|
152
162
|
.twgit-element-values td { font-family: ui-monospace, monospace; }
|
|
153
163
|
.twgit-element-files { margin-top: 12px; }
|
|
154
164
|
.twgit-panel .twgit-element span.twgit-element-name { width: auto; font-family: inherit; }
|
|
165
|
+
.twgit-git-details > .twgit-connection-body { left: auto; right: 0; }
|
|
166
|
+
/* In a layout tab the panel fills the tab and leaves the Editor beside it. */
|
|
167
|
+
.twgit-panel.twgit-panel-tab { width: 100%; height: 100%; border: 0; border-radius: 0; box-shadow: none; }
|
|
155
168
|
/*$vite$:1*/
|
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@ const en = {
|
|
|
28
28
|
unavailable: 'No GitHub connection is configured.', changed: 'Switching disconnects the current session.',
|
|
29
29
|
error: 'GitHub connection failed. Retry using the selected connection.',
|
|
30
30
|
invalid: 'The selected repository needs a GitHub HTTPS origin.',
|
|
31
|
-
allowPush: '
|
|
31
|
+
allowPush: 'Allow sending to GitHub (push)', fetch: 'Check GitHub (fetch)', pull: 'Get from GitHub (pull)', push: 'Send to GitHub (push)'
|
|
32
32
|
};
|
|
33
33
|
const ja: typeof en = {
|
|
34
34
|
title: 'GitHub 接続', mode: '接続方式', select: '接続方式を選択',
|
|
@@ -39,7 +39,7 @@ const ja: typeof en = {
|
|
|
39
39
|
unavailable: 'GitHub 接続先が設定されていません。', changed: '方式を切り替えると現在のセッションを切断します。',
|
|
40
40
|
error: 'GitHub 接続に失敗しました。選択中の方式で再試行してください。',
|
|
41
41
|
invalid: '選択したリポジトリには GitHub の HTTPS リモート origin が必要です。',
|
|
42
|
-
allowPush: 'push
|
|
42
|
+
allowPush: 'GitHub に送ることを許可する(push)', fetch: 'GitHub と確かめる(fetch)', pull: 'GitHub から取り込む(pull)', push: 'GitHub に送る(push)'
|
|
43
43
|
};
|
|
44
44
|
|
|
45
45
|
export function ConnectionSettings({settings, locale, readOrigin, runOperation}: {
|
|
@@ -69,8 +69,15 @@ export function ConnectionSettings({settings, locale, readOrigin, runOperation}:
|
|
|
69
69
|
if (!remote) throw new Error('INVALID_REMOTE');
|
|
70
70
|
await settings.connect(remote, allowPush);
|
|
71
71
|
});
|
|
72
|
-
|
|
72
|
+
const connected = !!mode && state.status === 'connected';
|
|
73
|
+
// Sync sits beside the GitHub status; the settings open over the list instead of pushing it down.
|
|
74
|
+
return <>
|
|
75
|
+
{runOperation && connected && <span className="twgit-sync">{(['fetch', 'pull', 'push'] as const).map(operation =>
|
|
76
|
+
<button key={operation} disabled={busy || !readOrigin || (operation === 'push' && !write)}
|
|
77
|
+
onClick={() => void run(() => runOperation(operation))}>{labels[operation]}</button>)}</span>}
|
|
78
|
+
<details className="twgit-connection">
|
|
73
79
|
<summary>{labels.title}</summary>
|
|
80
|
+
<div className="twgit-connection-body">
|
|
74
81
|
{!settings.modes.length ? <p>{labels.unavailable}</p> : <>
|
|
75
82
|
<label>{labels.mode} <select value={mode ?? ''} disabled={busy}
|
|
76
83
|
onChange={event => {const choice = event.currentTarget.value as Mode; void run(() => settings.select(choice));}}>
|
|
@@ -93,11 +100,10 @@ export function ConnectionSettings({settings, locale, readOrigin, runOperation}:
|
|
|
93
100
|
{state.status !== 'disconnected' && <button disabled={busy}
|
|
94
101
|
onClick={() => void run(settings.refresh)}>{labels.check}</button>}
|
|
95
102
|
<button disabled={busy} onClick={() => void run(settings.disconnect)}>{labels.disconnect}</button>
|
|
96
|
-
{runOperation && state.status === 'connected' && (['fetch', 'pull', 'push'] as const).map(operation =>
|
|
97
|
-
<button key={operation} disabled={busy || !readOrigin || (operation === 'push' && !write)}
|
|
98
|
-
onClick={() => void run(() => runOperation(operation))}>{labels[operation]}</button>)}
|
|
99
103
|
</>}
|
|
100
|
-
{error && <p className="twgit-error" role="alert">{labels[error]}</p>}
|
|
101
104
|
</>}
|
|
102
|
-
|
|
105
|
+
</div>
|
|
106
|
+
</details>
|
|
107
|
+
{error && <p className="twgit-error" role="alert">{labels[error]}</p>}
|
|
108
|
+
</>;
|
|
103
109
|
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import {createScratchBlocksDiffLocator} from './block-diff-location.js';
|
|
2
|
+
import type {ProjectBlockLocator, ScratchBlockLocationWorkspace} from './block-diff-location.js';
|
|
3
|
+
|
|
4
|
+
/** The parts of a Scratch VM the locator reads to find a stage or sprite. */
|
|
5
|
+
export interface LocatorVM {
|
|
6
|
+
runtime: {targets: readonly {id: string; isOriginal?: boolean; isStage: boolean; getName(): string}[]};
|
|
7
|
+
editingTarget?: {id: string} | null;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface EditorBlockLocatorOptions {
|
|
11
|
+
vm: LocatorVM;
|
|
12
|
+
/** Opens the code of one stage or sprite in the Editor, as project search does. */
|
|
13
|
+
openTargetCode(targetId: string): void | Promise<void>;
|
|
14
|
+
/** The Editor's code workspace as it is now, or `null` while none is shown. */
|
|
15
|
+
getWorkspace(): ScratchBlockLocationWorkspace | null | Promise<ScratchBlockLocationWorkspace | null>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Finds the stage or sprite of a compared version in the open project, opens its code and waits for the block,
|
|
20
|
+
* then centers and outlines it. The caller decides which version matches the open project.
|
|
21
|
+
*/
|
|
22
|
+
export function createEditorBlockLocator({vm, openTargetCode, getWorkspace}: EditorBlockLocatorOptions): ProjectBlockLocator {
|
|
23
|
+
return createScratchBlocksDiffLocator(async ({target, id: blockId, signal}) => {
|
|
24
|
+
const found = vm.runtime.targets.find(item => target.isStage ? item.isStage :
|
|
25
|
+
!item.isStage && item.isOriginal !== false && item.getName() === target.name);
|
|
26
|
+
if (!found || signal.aborted) return null;
|
|
27
|
+
await openTargetCode(found.id);
|
|
28
|
+
// The Editor switches targets and fills its workspace over the next frames.
|
|
29
|
+
const deadline = Date.now() + waitLimit;
|
|
30
|
+
while (!signal.aborted) {
|
|
31
|
+
if (vm.editingTarget?.id === found.id) {
|
|
32
|
+
const workspace = await getWorkspace();
|
|
33
|
+
if (signal.aborted) break;
|
|
34
|
+
if (workspace?.getBlockById(blockId)) return workspace;
|
|
35
|
+
}
|
|
36
|
+
if (Date.now() >= deadline) break;
|
|
37
|
+
await nextFrame(signal);
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const waitLimit = 2000;
|
|
44
|
+
|
|
45
|
+
function nextFrame(signal: AbortSignal) {
|
|
46
|
+
return new Promise<void>(resolve => {
|
|
47
|
+
const done = () => {
|
|
48
|
+
signal.removeEventListener('abort', done);
|
|
49
|
+
resolve();
|
|
50
|
+
};
|
|
51
|
+
signal.addEventListener('abort', done, {once: true});
|
|
52
|
+
if (typeof requestAnimationFrame === 'function') requestAnimationFrame(done);
|
|
53
|
+
else setTimeout(done, 16);
|
|
54
|
+
});
|
|
55
|
+
}
|