@gentbajko/slopify 3.7.0 → 3.7.1
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/edge/http/studio.js +48 -1
- package/dist/extension/slopify-studio-chrome.zip +0 -0
- package/dist/extension/slopify-studio-firefox.zip +0 -0
- package/dist/patch-notes/3.7.1.md +16 -0
- package/dist/patch-notes/index.json +6 -0
- package/dist/slices/studio/releases.js +12 -0
- package/dist/web/assets/{index-CDNk-_g2.css → index-CC9XywV_.css} +1 -1
- package/dist/web/assets/{index-CfRLi2K6.js → index-glbbg3ZG.js} +132 -132
- package/dist/web/assets/{pdf-CWEGa9YC.js → pdf-B6O50xdO.js} +1 -1
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
package/dist/edge/http/studio.js
CHANGED
|
@@ -5,6 +5,7 @@ import { zValidator } from "@hono/zod-validator";
|
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { bodyLimit } from "hono/body-limit";
|
|
7
7
|
import { z } from "zod";
|
|
8
|
+
import { transact } from "../../kernel/db/tx.js";
|
|
8
9
|
import { derive } from "../../kernel/runner/graph.js";
|
|
9
10
|
import { listProjects, projectById, stagesOf } from "../../slices/admission/repo.js";
|
|
10
11
|
import { channelById } from "../../slices/channels/repo.js";
|
|
@@ -19,7 +20,7 @@ import { writeUploadPick } from "../../slices/studio/pick.js";
|
|
|
19
20
|
import { postingPlanSchema, readPlan, writePlan } from "../../slices/studio/plan.js";
|
|
20
21
|
import { leadHoursMax, seriesOf } from "../../slices/studio/plan-model.js";
|
|
21
22
|
import { enqueueFill, fillNow, readFillQueue, removeFill, } from "../../slices/studio/queue.js";
|
|
22
|
-
import { allReleases, freeSlots, planReleases, readLeadHours, releasesOf, setRelease, writeLeadHours, } from "../../slices/studio/releases.js";
|
|
23
|
+
import { allReleases, freeSlots, planReleases, readLeadHours, releasesOf, setRelease, swapReleases, writeLeadHours, } from "../../slices/studio/releases.js";
|
|
23
24
|
import { parseStudioExport, readReport, saveReport } from "../../slices/studio/report.js";
|
|
24
25
|
import { autoCommentKey, bearerToken, isExtensionOrigin, pairStudioExtension, readChannelPlaylists, readStudioPlaylists, resetStudioPairing, saveProjectPlaylists, saveRealFootage, saveStudioPlaylists, studioPairing, studioPlaylistsMax, studioPlaylistsProblem, studioRequestAllowed, } from "../../slices/studio/settings.js";
|
|
25
26
|
import { abResults, abVariantSchema, projectStats, saveAbResult, saveStats, } from "../../slices/studio/stats.js";
|
|
@@ -254,6 +255,52 @@ export function studioRoutes(deps) {
|
|
|
254
255
|
weeks: c.req.valid("query").weeks ?? 2,
|
|
255
256
|
candidates: finished.map((project) => project.id),
|
|
256
257
|
}));
|
|
258
|
+
})
|
|
259
|
+
// One project's release times (its long video and each short, with their upload-by), the
|
|
260
|
+
// plan's free times for its series, for the project page's Release block.
|
|
261
|
+
.get("/releases/:projectId", zValidator("param", projectParam, onInvalid), (c) => {
|
|
262
|
+
const { projectId } = c.req.valid("param");
|
|
263
|
+
planned(projectId);
|
|
264
|
+
const pack = uploadPack(deps, projectId);
|
|
265
|
+
if (!pack.ok)
|
|
266
|
+
return unknownProject(c);
|
|
267
|
+
const leadMs = readLeadHours(deps.db) * 3_600_000;
|
|
268
|
+
const releases = releasesOf(deps.db, projectId);
|
|
269
|
+
return c.json({
|
|
270
|
+
leadHours: readLeadHours(deps.db),
|
|
271
|
+
items: pack.pack.items
|
|
272
|
+
.filter((item) => item.video !== null || item.kind === "video")
|
|
273
|
+
.map((item) => {
|
|
274
|
+
const short = item.short ?? 0;
|
|
275
|
+
const at = releases.find((release) => release.short === short)?.at ?? "";
|
|
276
|
+
return {
|
|
277
|
+
short,
|
|
278
|
+
title: item.title,
|
|
279
|
+
at: at === "" ? null : at,
|
|
280
|
+
uploadBy: at === "" ? null : new Date(Date.parse(at) - leadMs).toISOString(),
|
|
281
|
+
onYoutube: videoOf(deps.db, projectId, item.short ?? null)?.uploadState === "done",
|
|
282
|
+
};
|
|
283
|
+
}),
|
|
284
|
+
free: slotChoices(projectId, pack.pack.series),
|
|
285
|
+
});
|
|
286
|
+
})
|
|
287
|
+
// Swap with…: two videos trade their release times; their shorts follow each.
|
|
288
|
+
.post("/releases/:projectId/swap", zValidator("param", projectParam, onInvalid), zValidator("json", z.object({ with: id }).strict(), onInvalid), (c) => {
|
|
289
|
+
const denied = samePage(c);
|
|
290
|
+
if (denied !== undefined)
|
|
291
|
+
return denied;
|
|
292
|
+
const { projectId } = c.req.valid("param");
|
|
293
|
+
const other = c.req.valid("json").with;
|
|
294
|
+
const swapped = transact(deps.db, () => swapReleases(deps.db, projectId, other, deps.clock.now()));
|
|
295
|
+
if (!swapped)
|
|
296
|
+
return problem(c, {
|
|
297
|
+
status: 409,
|
|
298
|
+
title: titleOf(409),
|
|
299
|
+
detail: "Both videos need a release time to swap. Give this one a time first, or reload the page and pick another video.",
|
|
300
|
+
});
|
|
301
|
+
planned(projectId);
|
|
302
|
+
planned(other);
|
|
303
|
+
return c.json({ releases: releasesOf(deps.db, projectId) });
|
|
257
304
|
})
|
|
258
305
|
// Moves one release (a time), sets it to "not scheduled" (null), or puts a project into a
|
|
259
306
|
// free time of the plan (short 0 with its line).
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Slopify 3.7.1
|
|
2
|
+
|
|
3
|
+
Released 4 October 2026.
|
|
4
|
+
|
|
5
|
+
## Release times on the project
|
|
6
|
+
|
|
7
|
+
- **Set release dates in the project itself.** The Video section has a Release block: the long video's and each short's release time, editable, with the time each must be uploaded and scheduled by. The long video can take one of the posting plan's free times, or be set to Not scheduled. They are the same times Calendar → Releases, Prepare upload and the Studio extension use.
|
|
8
|
+
- **Swap with…** in the Release block: two videos trade their release times, and each one's shorts move with it.
|
|
9
|
+
|
|
10
|
+
## Project page
|
|
11
|
+
|
|
12
|
+
- **A run clock you can see.** While a project runs, its header shows "Working · 1:03:27", counting every second and checked against the server every 20 seconds. A stopped run shows its working time so far.
|
|
13
|
+
|
|
14
|
+
## YouTube Studio extension (1.1.3)
|
|
15
|
+
|
|
16
|
+
- **One click uploads one video.** Studio rebuilds its file picker while an upload starts; the extension took each rebuild for a new upload dialog and added the same short again (and Upload all seemed to start every short at once). Now each dialog takes one video, and the same upload is never added twice within a minute and a half. Download the extension again from Settings → YouTube Studio and reload it.
|
|
@@ -123,6 +123,18 @@ export function setRelease(db, projectId, short, at, line, now) {
|
|
|
123
123
|
if (short === 0)
|
|
124
124
|
db.prepare("DELETE FROM releases WHERE project_id=? AND short>0 AND by='plan'").run(projectId);
|
|
125
125
|
}
|
|
126
|
+
// Two projects trade their long videos' release times (and lines); each one's plan-placed
|
|
127
|
+
// shorts are dropped, to be placed again after its new time. False when either has no time.
|
|
128
|
+
export function swapReleases(db, first, second, now) {
|
|
129
|
+
const longOf = (projectId) => releasesOf(db, projectId).find((release) => release.short === 0 && release.at !== "");
|
|
130
|
+
const a = longOf(first);
|
|
131
|
+
const b = longOf(second);
|
|
132
|
+
if (a === undefined || b === undefined || first === second)
|
|
133
|
+
return false;
|
|
134
|
+
setRelease(db, first, 0, b.at, b.line, now);
|
|
135
|
+
setRelease(db, second, 0, a.at, a.line, now);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
126
138
|
export function scheduleOf(db, projectId) {
|
|
127
139
|
const releases = releasesOf(db, projectId);
|
|
128
140
|
const long = releases.find((release) => release.short === 0);
|