Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 41 additions & 1 deletion docs/MANUAL-TESTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ sections — the history is useful.

```
npm install
npm test # expect 198 passing, 0 failing
npm test # expect 207 passing, 0 failing
npm start
```

Expand Down Expand Up @@ -2876,3 +2876,43 @@ Two things Will found on beta.6:
- [x] Look at all of it in both light and dark themes.
- [x] Advanced Options' trim on the Convert view still drags, Shift-drags and
shows its two frames as before.

## Join says how far it is, and locks while it runs (2026-10-16)

Two things Will found on beta.7:

- **Join's bar had no words.** It moved, but nothing said how far. It now has
**Joining 42%** under it, left-aligned, and **Joining…** while the length
isn't known (the bar slides then).
- **Join's settings could be changed mid-join.** The clip list (order, trims,
Remove, Add More) and Join into, File name and Browse all stayed live while
a join ran, though nothing changed then would take effect. Now they're
greyed out and locked until it ends, however it ends. A file dropped on Join
during a run isn't added: a toast says to wait for the join to finish.

> **Status: not yet run by hand.** Driven once in the running app on
> 2026-10-16 (two clips needing a re-encode: the label read "Joining 48%",
> left-aligned; the list was inert and greyed, the side's fields disabled, a
> click on Remove did nothing; all of it came back after the join, with the
> first row's Move Up still disabled).

### Joining XX%

- [ ] In Join, add two videos of different sizes (so they re-encode, which
takes long enough to watch), choose a folder, press **Join**. Under the
bar, on the left, it reads **Joining 0%** and counts up to the end.
- [ ] When the join ends, the bar and its text go away.

### Locked while joining

- [ ] Open one clip's **Trim**, then press **Join**. While it runs, the list
is greyed out: Trim, the slider, Start and End, Move Up and Down, Remove
and **Add More** do nothing, and Tab doesn't reach them.
- [ ] **Join into**, **File name** and **Browse** are greyed out and can't be
changed. **Clear** is disabled; **Cancel** works.
- [ ] Drop a file on Join while it runs: it isn't added, and a toast says to
wait for the join to finish.
- [ ] When it finishes, everything works again, and the first clip's Move Up
and the last clip's Move Down are still disabled.
- [ ] Do it again and press **Cancel**: everything unlocks the same way.
- [ ] Look at it in both light and dark themes.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "diamond-file-converter",
"version": "2.0.0-beta.7",
"version": "2.0.0-beta.8",
"description": "An Electron-based desktop app for converting audio, video, and image files.",
"main": "src/main.js",
"funding": {
Expand Down
5 changes: 4 additions & 1 deletion src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,10 @@

<div id="join-errors" class="form-text text-danger"></div>

<div id="join-progress"></div>
<!-- The kit's bar goes in first (renderer.js, setupJoin). -->
<div id="join-progress" class="join-progress d-none">
<div id="join-progress-text" class="join-progress-text" aria-live="polite"></div>
</div>

<div class="join-actions">
<button id="btn-join-clear" class="btn btn-outline-secondary btn-sm">Clear</button>
Expand Down
37 changes: 32 additions & 5 deletions src/renderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -1645,6 +1645,11 @@ let joinTrimOpen = null; // the id of the clip whose trim is open, if any
*/
async function addJoinPaths(inputPaths) {
if (!inputPaths || inputPaths.length === 0) return;
// The list is what's being joined, so it holds still until the join ends.
if (joinRunning) {
kit.ui.toast("Wait for the join to finish before adding more files.", { type: "warning" });
return;
}
try {
const result = await api.scanPaths(inputPaths, buildScanOptions());
const added = await addJoinClips(result.files);
Expand Down Expand Up @@ -2052,7 +2057,30 @@ function renderJoinActions() {
$("btn-join-run").classList.toggle("d-none", Boolean(joinRunning));
$("btn-join-cancel").classList.toggle("d-none", !joinRunning);
$("btn-join-clear").disabled = Boolean(joinRunning);
joinProgress.element.classList.toggle("d-none", !joinRunning);
renderJoinLock();
$("join-progress").classList.toggle("d-none", !joinRunning);
}

/**
* While a join runs, everything that configures it is greyed out and locked:
* the clip list (order, trims, removing, Add More) and the side's fields. The
* list is made inert rather than each control disabled, so the rows keep their
* own disabled states (the first row's Move Up) for when it unlocks.
*/
function renderJoinLock() {
const locked = Boolean(joinRunning);
const scroll = document.querySelector("#join-body .join-scroll");
scroll.inert = locked;
scroll.classList.toggle("is-locked", locked);
for (const id of ["join-format", "join-name", "btn-join-browse"]) $(id).disabled = locked;
}

/** Join's progress: the bar, then "Joining 42%" under it, left-aligned. */
function setJoinProgress(percent) {
joinProgress.set(percent);
$("join-progress-text").textContent = typeof percent === "number"
? `Joining ${Math.round(Math.min(100, Math.max(0, percent)))}%`
: "Joining…";
}

async function runJoin() {
Expand All @@ -2078,7 +2106,7 @@ async function runJoin() {

joinRunning = generateId();
spec.id = joinRunning;
joinProgress.set(0);
setJoinProgress(0);
renderJoinActions();

// However it ends, as a conversion's.
Expand Down Expand Up @@ -2106,8 +2134,7 @@ let joinProgress = null;

function setupJoin() {
joinProgress = kit.ui.progress({ label: "Joining" });
joinProgress.element.classList.add("join-progress", "d-none");
$("join-progress").replaceWith(joinProgress.element);
$("join-progress").prepend(joinProgress.element);

// The same drop zone as Convert's, from the kit, so the two sections read
// as one app: only the icon and the label change. Files dropped anywhere
Expand Down Expand Up @@ -2367,7 +2394,7 @@ api.onJobProgress(({ jobId, percent }) => {
// A join is a job too, and it is the only one with no card to land on.
if (jobId === joinRunning) {
// null when the length isn't known: the bar slides instead of reading "null%".
joinProgress?.set(typeof percent === "number" ? percent : null);
if (joinProgress) setJoinProgress(typeof percent === "number" ? percent : null);
return;
}
const job = jobs.find(j => j.id === jobId);
Expand Down
4 changes: 4 additions & 0 deletions src/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -665,6 +665,10 @@ a {
.join-plan-why { margin: 0.35rem 0 0; opacity: 0.75; }

.join-progress { margin-top: 0.9rem; }
.join-progress-text { margin-top: 0.35rem; font-size: 0.8rem; text-align: left; opacity: 0.75; }

/* While a join runs the list can't change, and looks it. */
.join-scroll.is-locked { opacity: 0.5; transition: opacity var(--dur-state) var(--ease-state); }

.join-actions { margin-top: auto; padding-top: 0.9rem; display: flex; gap: 0.5rem; justify-content: flex-end; }

Expand Down
15 changes: 14 additions & 1 deletion test/kit.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -161,10 +161,23 @@ test("what Restart Now asks about is cleared however a conversion or a join ends
test("progress of null is \"not known\": a card's and Join's bars slide, never sit at 0% or read null%", () => {
const renderer = read("renderer.js");
assert.match(renderer, /progress\.set\(typeof job\.progress === "number" \? job\.progress : null\)/);
assert.match(renderer, /joinProgress\?\.set\(typeof percent === "number" \? percent : null\)/);
assert.match(renderer, /setJoinProgress\(typeof percent === "number" \? percent : null\)/);
assert.ok(!/style\.width = `\$\{percent\}%`/.test(renderer), "Join's bar used to read \"null%\"");
});

test("Join's bar has \"Joining XX%\" under it, and its settings lock while it runs", () => {
const renderer = read("renderer.js");
const html = read("index.html");
assert.match(html, /id="join-progress-text"/);
assert.match(renderer, /`Joining \$\{Math\.round\(/);
assert.match(renderer, /: "Joining…";/);
// The list goes inert and grey; the side's fields are disabled.
assert.match(renderer, /scroll\.inert = locked;/);
assert.match(renderer, /\["join-format", "join-name", "btn-join-browse"\]\) \$\(id\)\.disabled = locked;/);
// A drop during a join doesn't change what's being joined.
assert.match(renderer, /if \(joinRunning\) \{\r?\n\s+kit\.ui\.toast\("Wait for the join to finish/);
});

test("every icon is hidden from screen readers, and every icon-only button has a name", () => {
const html = read("index.html");
const spans = html.match(/<span class="material-icons-round[^>]*>/g) ?? [];
Expand Down
Loading