@effing/create 0.14.1 → 0.15.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
|
@@ -61,8 +61,8 @@ Creating a new Effing project in ${root}...
|
|
|
61
61
|
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
62
62
|
console.log("Done! To get started:\n");
|
|
63
63
|
console.log(` cd ${projectName}`);
|
|
64
|
-
console.log(" npm install");
|
|
65
|
-
console.log(" npm run dev\n");
|
|
64
|
+
console.log(" npm install # or: pnpm install");
|
|
65
|
+
console.log(" npm run dev # or: pnpm run dev\n");
|
|
66
66
|
console.log("Then open http://localhost:3839 to see your project.\n");
|
|
67
67
|
}
|
|
68
68
|
main().catch((err) => {
|
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
FROM node:22-alpine AS base
|
|
2
|
+
RUN corepack enable
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Stage: install all deps and build
|
|
6
|
+
FROM base AS build
|
|
7
|
+
COPY package.json pnpm-lock.yaml* package-lock.json* ./
|
|
8
|
+
RUN if [ -f pnpm-lock.yaml ]; then \
|
|
9
|
+
pnpm install --frozen-lockfile; \
|
|
10
|
+
elif [ -f package-lock.json ]; then \
|
|
11
|
+
npm ci; \
|
|
12
|
+
else \
|
|
13
|
+
npm install; \
|
|
14
|
+
fi
|
|
15
|
+
COPY . .
|
|
16
|
+
RUN npm run build
|
|
17
|
+
|
|
18
|
+
# Stage: production deps only
|
|
19
|
+
FROM base AS prod-deps
|
|
20
|
+
COPY package.json pnpm-lock.yaml* package-lock.json* ./
|
|
21
|
+
RUN if [ -f pnpm-lock.yaml ]; then \
|
|
22
|
+
pnpm install --frozen-lockfile --prod; \
|
|
23
|
+
elif [ -f package-lock.json ]; then \
|
|
24
|
+
npm ci --omit=dev; \
|
|
25
|
+
else \
|
|
26
|
+
npm install --omit=dev; \
|
|
27
|
+
fi
|
|
28
|
+
|
|
29
|
+
# Stage: final runtime image
|
|
30
|
+
FROM node:22-alpine
|
|
31
|
+
WORKDIR /app
|
|
32
|
+
ENV NODE_ENV=production
|
|
33
|
+
ENV PORT=8080
|
|
34
|
+
COPY --from=prod-deps /app/node_modules ./node_modules
|
|
35
|
+
COPY --from=build /app/package.json ./package.json
|
|
36
|
+
COPY --from=build /app/build ./build
|
|
37
|
+
EXPOSE $PORT
|
|
38
|
+
CMD ["./node_modules/.bin/react-router-serve", "./build/server/index.js"]
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
useNavigation,
|
|
6
6
|
type ShouldRevalidateFunctionArgs,
|
|
7
7
|
} from "react-router";
|
|
8
|
-
import { useEffect, useReducer, useState } from "react";
|
|
8
|
+
import { useEffect, useReducer, useRef, useState } from "react";
|
|
9
9
|
import invariant from "tiny-invariant";
|
|
10
10
|
import { serialize } from "@effing/serde";
|
|
11
11
|
import {
|
|
@@ -59,20 +59,28 @@ type ActionResult =
|
|
|
59
59
|
type RenderState =
|
|
60
60
|
| { step: "idle" }
|
|
61
61
|
| { step: "started"; startedAt: number }
|
|
62
|
-
| {
|
|
62
|
+
| {
|
|
63
|
+
step: "ready";
|
|
64
|
+
startedAt: number;
|
|
65
|
+
videoUrl: string;
|
|
66
|
+
scale: number;
|
|
67
|
+
downloadUrl?: string;
|
|
68
|
+
}
|
|
63
69
|
| {
|
|
64
70
|
step: "done";
|
|
65
71
|
startedAt: number;
|
|
66
72
|
videoUrl: string;
|
|
67
73
|
scale: number;
|
|
68
74
|
playbackAt: number;
|
|
75
|
+
downloadUrl?: string;
|
|
69
76
|
};
|
|
70
77
|
|
|
71
78
|
type RenderAction =
|
|
72
79
|
| { type: "start" }
|
|
73
80
|
| { type: "ready"; videoUrl: string; scale: number }
|
|
74
81
|
| { type: "play" }
|
|
75
|
-
| { type: "error" }
|
|
82
|
+
| { type: "error" }
|
|
83
|
+
| { type: "buffered"; downloadUrl: string };
|
|
76
84
|
|
|
77
85
|
function renderReducer(state: RenderState, action: RenderAction): RenderState {
|
|
78
86
|
switch (action.type) {
|
|
@@ -89,6 +97,10 @@ function renderReducer(state: RenderState, action: RenderAction): RenderState {
|
|
|
89
97
|
case "play":
|
|
90
98
|
if (state.step !== "ready") return state;
|
|
91
99
|
return { ...state, step: "done", playbackAt: Date.now() };
|
|
100
|
+
case "buffered":
|
|
101
|
+
if (state.step === "ready" || state.step === "done")
|
|
102
|
+
return { ...state, downloadUrl: action.downloadUrl };
|
|
103
|
+
return state;
|
|
92
104
|
case "error":
|
|
93
105
|
return { step: "idle" };
|
|
94
106
|
}
|
|
@@ -286,6 +298,7 @@ export default function EffiePreviewPage() {
|
|
|
286
298
|
|
|
287
299
|
const [render, dispatch] = useReducer(renderReducer, { step: "idle" });
|
|
288
300
|
const [elapsedToPlay, setElapsedToPlay] = useState<number | null>(null);
|
|
301
|
+
const prevDownloadUrlRef = useRef<string | null>(null);
|
|
289
302
|
|
|
290
303
|
// Update elapsed time while rendering is in progress
|
|
291
304
|
useEffect(() => {
|
|
@@ -381,6 +394,19 @@ export default function EffiePreviewPage() {
|
|
|
381
394
|
|
|
382
395
|
const handleRenderSubmit = () => {
|
|
383
396
|
dispatch({ type: "start" });
|
|
397
|
+
if (prevDownloadUrlRef.current) {
|
|
398
|
+
URL.revokeObjectURL(prevDownloadUrlRef.current);
|
|
399
|
+
prevDownloadUrlRef.current = null;
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
const handleFullyBuffered = (blob: Blob) => {
|
|
404
|
+
if (prevDownloadUrlRef.current) {
|
|
405
|
+
URL.revokeObjectURL(prevDownloadUrlRef.current);
|
|
406
|
+
}
|
|
407
|
+
const downloadUrl = URL.createObjectURL(blob);
|
|
408
|
+
prevDownloadUrlRef.current = downloadUrl;
|
|
409
|
+
dispatch({ type: "buffered", downloadUrl });
|
|
384
410
|
};
|
|
385
411
|
|
|
386
412
|
const formatSourceUrl = (url: string, maxLen = 70) => {
|
|
@@ -461,6 +487,7 @@ export default function EffiePreviewPage() {
|
|
|
461
487
|
resolution={coverResolution}
|
|
462
488
|
video={"videoUrl" in render ? render.videoUrl : null}
|
|
463
489
|
onPlay={handleVideoPlay}
|
|
490
|
+
onFullyBuffered={handleFullyBuffered}
|
|
464
491
|
style={{
|
|
465
492
|
border: "1px solid black",
|
|
466
493
|
backgroundColor: "#eee",
|
|
@@ -594,13 +621,50 @@ export default function EffiePreviewPage() {
|
|
|
594
621
|
/>
|
|
595
622
|
)}
|
|
596
623
|
|
|
624
|
+
{/* Render Progress */}
|
|
625
|
+
{(render.step === "started" || render.step === "ready") &&
|
|
626
|
+
elapsedToPlay !== null && (
|
|
627
|
+
<div style={{ color: "#4CAE4C" }}>
|
|
628
|
+
Rendering... {elapsedToPlay.toFixed(1)}s
|
|
629
|
+
</div>
|
|
630
|
+
)}
|
|
631
|
+
|
|
597
632
|
{/* Render Success */}
|
|
598
|
-
{render.step === "
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
633
|
+
{(render.step === "ready" || render.step === "done") &&
|
|
634
|
+
elapsedToPlay !== null && (
|
|
635
|
+
<div
|
|
636
|
+
style={{
|
|
637
|
+
display: "flex",
|
|
638
|
+
flexDirection: "column",
|
|
639
|
+
alignItems: "flex-start",
|
|
640
|
+
gap: "1rem",
|
|
641
|
+
}}
|
|
642
|
+
>
|
|
643
|
+
{render.step === "done" && (
|
|
644
|
+
<span style={{ color: "#4CAE4C" }}>
|
|
645
|
+
Started playing after {elapsedToPlay.toFixed(1)}s (at{" "}
|
|
646
|
+
{Math.round(render.scale * 100)}%)
|
|
647
|
+
</span>
|
|
648
|
+
)}
|
|
649
|
+
{render.downloadUrl && (
|
|
650
|
+
<a
|
|
651
|
+
href={render.downloadUrl}
|
|
652
|
+
download={`${effieId}-${width}x${height}.mp4`}
|
|
653
|
+
style={{
|
|
654
|
+
padding: "0.4rem 0.75rem",
|
|
655
|
+
backgroundColor: "#fff",
|
|
656
|
+
color: "#4CAE4C",
|
|
657
|
+
border: "1px solid #4CAE4C",
|
|
658
|
+
borderRadius: 4,
|
|
659
|
+
fontSize: "14px",
|
|
660
|
+
textDecoration: "none",
|
|
661
|
+
}}
|
|
662
|
+
>
|
|
663
|
+
Download video
|
|
664
|
+
</a>
|
|
665
|
+
)}
|
|
666
|
+
</div>
|
|
667
|
+
)}
|
|
604
668
|
|
|
605
669
|
{/* Downloading Status */}
|
|
606
670
|
{warmupDownloadingItems.length > 0 && (
|
package/template/package.json
CHANGED
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
"typecheck": "react-router typegen && tsc"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@effing/annie": "^0.
|
|
15
|
-
"@effing/annie-player": "^0.
|
|
16
|
-
"@effing/effie": "^0.
|
|
17
|
-
"@effing/effie-preview": "^0.
|
|
18
|
-
"@effing/ffs": "^0.
|
|
19
|
-
"@effing/satori": "^0.
|
|
14
|
+
"@effing/annie": "^0.15.0",
|
|
15
|
+
"@effing/annie-player": "^0.15.0",
|
|
16
|
+
"@effing/effie": "^0.15.0",
|
|
17
|
+
"@effing/effie-preview": "^0.15.0",
|
|
18
|
+
"@effing/ffs": "^0.15.0",
|
|
19
|
+
"@effing/satori": "^0.15.0",
|
|
20
20
|
"@resvg/resvg-js": "^2.6.2",
|
|
21
|
-
"@effing/serde": "^0.
|
|
22
|
-
"@effing/tween": "^0.
|
|
21
|
+
"@effing/serde": "^0.15.0",
|
|
22
|
+
"@effing/tween": "^0.15.0",
|
|
23
23
|
"@react-router/node": "^7.0.0",
|
|
24
24
|
"@react-router/serve": "^7.0.0",
|
|
25
25
|
"cross-env": "^7.0.3",
|
package/template/vite.config.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { reactRouter } from "@react-router/dev/vite";
|
|
2
|
-
import { satoriPoolPlugin } from "@effing/satori/vite";
|
|
3
2
|
import { defineConfig } from "vite";
|
|
4
3
|
import tsconfigPaths from "vite-tsconfig-paths";
|
|
5
4
|
|
|
6
5
|
export default defineConfig({
|
|
7
6
|
server: { port: 3839 }, // 3839 = 0xEFF, how effing cool is that? ʘ‿ʘ
|
|
8
|
-
plugins: [reactRouter(), tsconfigPaths()
|
|
7
|
+
plugins: [reactRouter(), tsconfigPaths()],
|
|
9
8
|
ssr: {
|
|
10
9
|
noExternal: ["yoga-wasm-web", "yoga-wasm-web/asm", "satori", "emoji-regex"],
|
|
11
10
|
external: ["@resvg/resvg-js"],
|