mirror of
https://github.com/motion-canvas/motion-canvas.git
synced 2026-01-12 23:38:02 -05:00
1
.github/workflows/docs.yml
vendored
1
.github/workflows/docs.yml
vendored
@@ -16,6 +16,7 @@ jobs:
|
||||
node-version: 18
|
||||
cache: npm
|
||||
- run: npm ci
|
||||
- run: npm run examples:build
|
||||
- run: npm run docs:build
|
||||
- uses: peaceiris/actions-gh-pages@v3
|
||||
with:
|
||||
|
||||
2
.github/workflows/verify.yml
vendored
2
.github/workflows/verify.yml
vendored
@@ -29,3 +29,5 @@ jobs:
|
||||
run: npx lerna run test
|
||||
- name: Build
|
||||
run: npx lerna run build
|
||||
- name: E2E
|
||||
run: npm run e2e:test
|
||||
|
||||
@@ -10,6 +10,7 @@ module.exports = {
|
||||
'core',
|
||||
'create',
|
||||
'docs',
|
||||
'e2e',
|
||||
'examples',
|
||||
'legacy',
|
||||
'player',
|
||||
|
||||
3648
package-lock.json
generated
3648
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -23,6 +23,7 @@
|
||||
"docs:build": "npm run build -w packages/docs",
|
||||
"vite-plugin:build": "npm run build -w packages/vite-plugin",
|
||||
"vite-plugin:watch": "npm run watch -w packages/vite-plugin",
|
||||
"e2e:test": "npm run test -w packages/e2e",
|
||||
"eslint": "eslint --fix \"**/*.ts?(x)\"",
|
||||
"prettier": "prettier --write ."
|
||||
},
|
||||
|
||||
@@ -363,6 +363,12 @@ export class Project {
|
||||
}
|
||||
}
|
||||
|
||||
public async finishExport() {
|
||||
while (this.exportCounter > 0) {
|
||||
await new Promise(resolve => setTimeout(resolve, EXPORT_RETRY_DELAY));
|
||||
}
|
||||
}
|
||||
|
||||
public syncAudio(frameOffset = 0) {
|
||||
this.audio.setTime(this.framesToSeconds(this.frame + frameOffset));
|
||||
}
|
||||
|
||||
@@ -293,6 +293,7 @@ export class Player {
|
||||
}
|
||||
|
||||
if (state.finished || this.project.frame >= state.endFrame) {
|
||||
await this.project.finishExport();
|
||||
this.toggleRendering(false);
|
||||
}
|
||||
|
||||
|
||||
2
packages/e2e/.gitignore
vendored
Normal file
2
packages/e2e/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
output
|
||||
src/__image_snapshots__/__diff_output__
|
||||
22
packages/e2e/package.json
Normal file
22
packages/e2e/package.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "@motion-canvas/e2e",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "vitest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@motion-canvas/2d": "*",
|
||||
"@motion-canvas/core": "*",
|
||||
"jest-image-snapshot": "^6.1.0",
|
||||
"puppeteer": "^19.3.0",
|
||||
"vitest": "^0.25.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
"@motion-canvas/vite-plugin": "*",
|
||||
"@types/puppeteer": "^7.0.4",
|
||||
"vite": "^3.0.5"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 47 KiB |
34
packages/e2e/src/app.ts
Normal file
34
packages/e2e/src/app.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import * as path from 'path';
|
||||
import puppeteer, {Page} from 'puppeteer';
|
||||
import {fileURLToPath} from 'url';
|
||||
import {createServer} from 'vite';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export interface App {
|
||||
page: Page;
|
||||
stop: () => Promise<void>;
|
||||
}
|
||||
|
||||
export async function start(): Promise<App> {
|
||||
const [browser, server] = await Promise.all([
|
||||
puppeteer.launch(),
|
||||
createServer({
|
||||
root: __dirname,
|
||||
configFile: path.resolve(__dirname, '../vite.config.ts'),
|
||||
server: {
|
||||
port: 9000,
|
||||
},
|
||||
}).then(server => server.listen()),
|
||||
]);
|
||||
|
||||
const page = await browser.newPage();
|
||||
await page.goto('http://localhost:9000');
|
||||
|
||||
return {
|
||||
page,
|
||||
async stop() {
|
||||
await Promise.all([browser.close(), server.close()]);
|
||||
},
|
||||
};
|
||||
}
|
||||
10
packages/e2e/src/global.d.ts
vendored
Normal file
10
packages/e2e/src/global.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
declare namespace jest {
|
||||
interface Matchers<R> {
|
||||
toMatchImageSnapshot(): R;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'jest-image-snapshot' {
|
||||
const toMatchImageSnapshot: any;
|
||||
export {toMatchImageSnapshot};
|
||||
}
|
||||
26
packages/e2e/src/rendering.test.ts
Normal file
26
packages/e2e/src/rendering.test.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import {afterAll, beforeAll, describe, expect, test} from 'vitest';
|
||||
import {toMatchImageSnapshot} from 'jest-image-snapshot';
|
||||
import {App, start} from './app';
|
||||
import {readFileSync} from 'fs';
|
||||
|
||||
expect.extend({toMatchImageSnapshot});
|
||||
|
||||
describe('Rendering', () => {
|
||||
let app: App;
|
||||
|
||||
beforeAll(async () => {
|
||||
app = await start();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await app.stop();
|
||||
});
|
||||
|
||||
test('Animation renders correctly', async () => {
|
||||
await app.page.click('#rendering-tab');
|
||||
await app.page.click('#render');
|
||||
await app.page.waitForSelector('#render:not([data-rendering])');
|
||||
|
||||
expect(readFileSync('./output/project/000300.png')).toMatchImageSnapshot();
|
||||
});
|
||||
});
|
||||
7
packages/e2e/tsconfig.json
Normal file
7
packages/e2e/tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "@motion-canvas/2d/tsconfig.project.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "src"
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
15
packages/e2e/vite.config.ts
Normal file
15
packages/e2e/vite.config.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/// <reference types="vitest" />
|
||||
|
||||
import {defineConfig} from 'vite';
|
||||
import motionCanvas from '@motion-canvas/vite-plugin';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
motionCanvas.default({
|
||||
project: ['@motion-canvas/template/src/project.ts'],
|
||||
}),
|
||||
],
|
||||
test: {
|
||||
testTimeout: 60000,
|
||||
},
|
||||
});
|
||||
@@ -7,8 +7,8 @@
|
||||
"build": "tsc && vite build"
|
||||
},
|
||||
"dependencies": {
|
||||
"@motion-canvas/core": "*",
|
||||
"@motion-canvas/2d": "*"
|
||||
"@motion-canvas/2d": "*",
|
||||
"@motion-canvas/core": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@motion-canvas/ui": "*",
|
||||
|
||||
@@ -21,7 +21,7 @@ export function Console() {
|
||||
const [filters, setFilters] = useState(LOG_LEVELS);
|
||||
|
||||
return (
|
||||
<Pane title="Console">
|
||||
<Pane title="Console" id="console">
|
||||
<div className={styles.navbar}>
|
||||
<div className={styles.pills}>
|
||||
{Object.keys(LOG_LEVELS).map(level => {
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Properties() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Pane title="Properties">
|
||||
<Pane title="Properties" id="properties-pane">
|
||||
{attributes
|
||||
? Object.entries(attributes).map(([key, value]) => (
|
||||
<AutoField label={key} value={value} />
|
||||
|
||||
@@ -46,7 +46,7 @@ export function Rendering() {
|
||||
];
|
||||
|
||||
return (
|
||||
<Pane title="Rendering">
|
||||
<Pane title="Rendering" id="rendering-pane">
|
||||
<Group>
|
||||
<Label>range</Label>
|
||||
<Input
|
||||
@@ -148,7 +148,12 @@ export function Rendering() {
|
||||
)}
|
||||
<Group>
|
||||
<Label />
|
||||
<Button main onClick={() => player.toggleRendering()}>
|
||||
<Button
|
||||
main
|
||||
id="render"
|
||||
data-rendering={state.render}
|
||||
onClick={() => player.toggleRendering()}
|
||||
>
|
||||
{state.render ? 'STOP RENDERING' : 'RENDER'}
|
||||
</Button>
|
||||
</Group>
|
||||
|
||||
@@ -47,6 +47,7 @@ export function Sidebar({setOpen}: SidebarProps) {
|
||||
>
|
||||
{{
|
||||
title: 'Project Selection',
|
||||
id: 'project-selection-link',
|
||||
type: TabType.Link,
|
||||
icon: IconType.motionCanvas,
|
||||
url: window.location.pathname === '/' ? undefined : '/',
|
||||
@@ -56,24 +57,28 @@ export function Sidebar({setOpen}: SidebarProps) {
|
||||
}}
|
||||
{{
|
||||
title: 'Inspector',
|
||||
id: 'inspector-tab',
|
||||
type: TabType.Pane,
|
||||
icon: IconType.tune,
|
||||
pane: <Properties />,
|
||||
}}
|
||||
{{
|
||||
title: 'Video Settings',
|
||||
id: 'rendering-tab',
|
||||
type: TabType.Pane,
|
||||
icon: IconType.videoSettings,
|
||||
pane: <Rendering />,
|
||||
}}
|
||||
{{
|
||||
title: 'Thread Debugger',
|
||||
id: 'threads-tab',
|
||||
type: TabType.Pane,
|
||||
icon: IconType.schedule,
|
||||
pane: <Threads />,
|
||||
}}
|
||||
{{
|
||||
title: errorCount > 0 ? `Console (${errorCount})` : 'Console',
|
||||
id: 'console-tab',
|
||||
type: TabType.Pane,
|
||||
icon: IconType.bug,
|
||||
pane: <Console />,
|
||||
|
||||
@@ -18,7 +18,7 @@ export function Threads() {
|
||||
);
|
||||
|
||||
return (
|
||||
<Pane title="Threads">
|
||||
<Pane title="Threads" id="threads-pane">
|
||||
{thread ? (
|
||||
<ThreadView thread={thread} />
|
||||
) : (
|
||||
|
||||
@@ -4,12 +4,13 @@ import {ComponentChildren} from 'preact';
|
||||
|
||||
export interface PaneProps {
|
||||
title: string;
|
||||
id?: string;
|
||||
children: ComponentChildren;
|
||||
}
|
||||
|
||||
export function Pane({title, children}: PaneProps) {
|
||||
export function Pane({title, id, children}: PaneProps) {
|
||||
return (
|
||||
<div className={styles.pane}>
|
||||
<div className={styles.pane} id={id}>
|
||||
<div className={styles.header}>{title}</div>
|
||||
{children}
|
||||
</div>
|
||||
|
||||
@@ -18,12 +18,14 @@ type Tab =
|
||||
pane: ComponentChildren;
|
||||
badge?: ComponentChildren;
|
||||
title?: string;
|
||||
id?: string;
|
||||
}
|
||||
| {
|
||||
type: TabType.Link;
|
||||
icon: IconType;
|
||||
url?: string;
|
||||
title?: string;
|
||||
id?: string;
|
||||
}
|
||||
| {
|
||||
type: TabType.Space;
|
||||
@@ -62,12 +64,14 @@ export function Tabs({children, tab, onToggle}: TabsProps) {
|
||||
title={data.title}
|
||||
type={data.icon}
|
||||
href={data.url}
|
||||
id={data.id}
|
||||
className={classes(styles.tab, [styles.disabled, !data.url])}
|
||||
/>
|
||||
) : data.type === TabType.Pane ? (
|
||||
<Icon
|
||||
type={data.icon}
|
||||
title={data.title}
|
||||
id={data.id}
|
||||
onClick={() => toggleTab(index)}
|
||||
className={classes(styles.tab, [styles.active, tab === index])}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user