Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

boxel-motion improvements #1105

Merged
merged 1 commit into from
Mar 28, 2024
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
19 changes: 19 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ jobs:
if: always()
run: pnpm run lint
working-directory: packages/boxel-ui/test-app
- name: Lint Boxel Motion
if: always()
run: pnpm run lint
working-directory: packages/boxel-motion/addon
- name: Build Boxel Motion
# To faciliate linting of projects that depend on Boxel Motion
if: always()
run: pnpm run build
working-directory: packages/boxel-motion/addon
- name: Lint Boxel Motion Test App
if: always()
run: pnpm run lint
working-directory: packages/boxel-motion/test-app
- name: Lint Host
if: always()
run: pnpm run lint
Expand Down Expand Up @@ -159,6 +172,9 @@ jobs:
- name: Build boxel-ui
run: pnpm build
working-directory: packages/boxel-ui/addon
- name: Build boxel-motion
run: pnpm build
working-directory: packages/boxel-motion/addon
- name: Build host dist/ for fastboot
run: pnpm build
env:
Expand Down Expand Up @@ -192,6 +208,9 @@ jobs:
- name: Build boxel-ui
run: pnpm build
working-directory: packages/boxel-ui/addon
- name: Build boxel-motion
run: pnpm build
working-directory: packages/boxel-motion/addon
- name: Build host dist/ for fastboot
run: pnpm build
env:
Expand Down
4 changes: 3 additions & 1 deletion QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ To build the entire repository and run the application, follow these steps:
pnpm install
```

4. Build the boxel-ui:
4. Build the boxel-ui and boxl-motion addons:

```zsh
cd ./packages/boxel-ui/addon
pnpm rebuild:icons
pnpm build
cd ../../boxel-motion/addon
pnpm build
```

5. Build the host:
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ Make sure that you have created a matrix user for the base realm, drafts realm,
In order to run the ember-cli hosted app:

1. `pnpm build` in the boxel-ui/addon workspace to build the boxel-ui addon.
2. `pnpm start` in the host/ workspace to serve the ember app. Note that this script includes the environment variable `OWN_REALM_URL=http://localhost:4201/draft/` which configures the host to point to the draft realm's cards realm by default.
3. `pnpm start:all` in the realm-server/ to serve the base realm, draft realm and published realm -- this will also allow you to switch between the app and the tests without having to restart servers)
2. `pnpm build` in the boxel-motion/addon workspace to build the boxel-motion addon.
3. `pnpm start` in the host/ workspace to serve the ember app. Note that this script includes the environment variable `OWN_REALM_URL=http://localhost:4201/draft/` which configures the host to point to the draft realm's cards realm by default.
4. `pnpm start:all` in the realm-server/ to serve the base realm, draft realm and published realm -- this will also allow you to switch between the app and the tests without having to restart servers)

The app is available at http://localhost:4200. It will serve the draft realm (configurable with OWN_REALM_URL, as mentioned above). You can open the base and draft cards workspace directly by entering http://localhost:4201/base or http://localhost:4201/draft in the browser (and additionally the published realm by entering http://localhost:4201/published).

Expand Down
1 change: 1 addition & 0 deletions packages/boxel-motion/addon/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
"types": "./declarations/*.d.ts",
"default": "./dist/*.js"
},
"./styles/*.css": "./dist/styles/*.css",
"./addon-main.js": "./addon-main.cjs"
},
"files": [
Expand Down
4 changes: 4 additions & 0 deletions packages/boxel-motion/addon/rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export default {
// Ensure that .gjs files are properly integrated as Javascript
addon.gjs(),

// addons are allowed to contain imports of .css files, which we want rollup
// to leave alone and keep in the published output.
addon.keepAssets(['styles/*']),

// Remove leftover build artifacts when starting a new build.
addon.clean({ runOnce: true }),

Expand Down
2 changes: 2 additions & 0 deletions packages/boxel-motion/addon/src/behaviors/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ export type Frame = {
export type FrameGenerator = Generator<Frame | void, void, never>;

export default interface Behavior {
fill: boolean;

/**
* Calculates the frames for the given parameters.
*
Expand Down
1 change: 1 addition & 0 deletions packages/boxel-motion/addon/src/behaviors/spring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type SpringValues = {
};

export default class SpringBehavior implements Behavior {
fill = true;
private options: SpringOptions;

constructor(options?: SpringOptionsArgument) {
Expand Down
11 changes: 9 additions & 2 deletions packages/boxel-motion/addon/src/behaviors/static.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import type Behavior from './base.ts';
import type Behavior from './base';
import {
type Frame,
type StaticToFramesArgument,
timeToFrame,
} from './base.ts';

export interface StaticBehaviorOptions {
fill?: boolean;
}
export default class StaticBehavior implements Behavior {
fill: boolean;
constructor(options?: StaticBehaviorOptions) {
this.fill = options?.fill ?? false;
}

*getFrames(options: StaticToFramesArgument) {
let frameCount = timeToFrame(options.duration) + 1;

for (let i = 0; i < frameCount; i++) {
// TODO: this can explicitly be non-numeric, fix TS
yield { value: options.value, velocity: 0 } as Frame;
}
}
Expand Down
1 change: 1 addition & 0 deletions packages/boxel-motion/addon/src/behaviors/tween.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface TweenBehaviorOptions {
}

export default class TweenBehavior implements Behavior {
fill = true;
easing: Easing;

constructor(options?: TweenBehaviorOptions) {
Expand Down
1 change: 1 addition & 0 deletions packages/boxel-motion/addon/src/behaviors/wait.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type Behavior from './base.ts';
import { type WaitToFramesArgument, timeToFrame } from './base.ts';

export default class WaitBehavior implements Behavior {
fill = false;
*getFrames(options: WaitToFramesArgument) {
let frameCount = timeToFrame(options.duration) + 1;

Expand Down
22 changes: 21 additions & 1 deletion packages/boxel-motion/addon/src/components/animation-context.gts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { assert } from '@ember/debug';
import { action } from '@ember/object';
import { inject as service } from '@ember/service';
import { htmlSafe } from '@ember/template';
import Component from '@glimmer/component';
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
Expand All @@ -19,6 +20,7 @@ const { VOLATILE_TAG, consumeTag } =
Ember.__loader.require('@glimmer/validator');

interface AnimationContextArgs {
debugging?: boolean;
id?: string;
use: ((changeset: Changeset) => AnimationDefinition) | undefined;
}
Expand All @@ -37,7 +39,7 @@ export default class AnimationContextComponent
{
<template>
{{this.renderDetector}}
<div class='animation-context' {{registerContext this}} ...attributes>
<div class={{this.cssClasses}} {{registerContext this}} ...attributes>
<div
{{registerContextOrphansEl this}}
data-animation-context-orphan-element='true'
Expand All @@ -52,6 +54,13 @@ export default class AnimationContextComponent
get id(): string | undefined {
return this.args.id;
}
get cssClasses() {
let result = 'animation-context';
if (this.args.debugging) {
result += ' debugging';
}
return htmlSafe(result);
}

element!: HTMLElement; //set by template
orphansElement: HTMLElement | null = null; //set by template
Expand All @@ -67,6 +76,17 @@ export default class AnimationContextComponent
);
}

constructor(owner: unknown, args: AnimationContextArgs) {
super(owner, args);
if (!this.animations) {
throw new Error(
`Expected to find "animations" service in app.
Add 'app/services/animations.ts' with
\`export { AnimationsService as default } from '@cardstack/boxel-motion';\``,
);
}
}

willDestroy(): void {
super.willDestroy();
this.animations.unregisterContext(this);
Expand Down
7 changes: 5 additions & 2 deletions packages/boxel-motion/addon/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import StaticBehavior from './behaviors/static.ts';
import TweenBehavior from './behaviors/tween.ts';
import WaitBehavior from './behaviors/wait.ts';
import AnimationContext from './components/animation-context.gts';
import { type Changeset } from './models/animator.ts';
import { type Changeset, type IContext } from './models/animator.ts';
import {
type AnimationDefinition,
type AnimationTimeline,
OrchestrationMatrix,
} from './models/orchestration.ts';
import Sprite, { type ISpriteModifier, SpriteType } from './models/sprite.ts';
import sprite from './modifiers/sprite.ts';
import AnimationsService from './services/animations.ts';

export {
Expand All @@ -20,10 +21,12 @@ export {
AnimationTimeline,
Changeset,
FPS,
IContext,
ISpriteModifier,
OrchestrationMatrix,
SpringBehavior,
Sprite,
Sprite, // model
sprite, // modifier
SpriteType,
StaticBehavior,
TweenBehavior,
Expand Down
15 changes: 7 additions & 8 deletions packages/boxel-motion/addon/src/models/orchestration.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import type Behavior from '../behaviors/base.ts';
import StaticBehavior from '../behaviors/static.ts';
import WaitBehavior from '../behaviors/wait.ts';
import generateFrames from '../utils/generate-frames.ts';
import { type Keyframe, type Value } from '../value/index.ts';
Expand All @@ -8,9 +7,9 @@ import { type MotionOptions, type MotionProperty } from './motion.ts';
import type Sprite from './sprite.ts';

interface RowFragment {
fill: boolean;
frames: Frame[];
startColumn: number;
static: boolean;
}

export class OrchestrationMatrix {
Expand Down Expand Up @@ -39,8 +38,8 @@ export class OrchestrationMatrix {
fragmentsByColumn[rowFragment.startColumn] =
fragmentsByColumn[rowFragment.startColumn] ?? [];
fragmentsByColumn[rowFragment.startColumn]!.push(rowFragment);
// don't backfill static frames, they're intended to only be set for their duration
if (rowFragment.static === false && rowFragment.frames[0]) {
// some frames (where fill == false) are intended to only be set for their duration
if (rowFragment.fill && rowFragment.frames[0]) {
baseFrames.push(rowFragment.frames[0] as Frame);
}
}
Expand Down Expand Up @@ -80,8 +79,8 @@ export class OrchestrationMatrix {
if (frame) {
frames.push(frame);

// Detect the final frame for static behaviors, so we can exclude it from future frames (no forward-fill).
if (!fragment.frames.length && fragment.static) {
// Detect the final frame for behaviors that should not fill, so we can exclude it from future frames (no forward-fill).
if (!fragment.frames.length && !fragment.fill) {
propertiesToRemoveFromPreviousKeyframe.push(frame.property);
}
} else {
Expand Down Expand Up @@ -195,7 +194,7 @@ export class OrchestrationMatrix {
rowFragments.push({
frames,
startColumn: 0,
static: true,
fill: timing.behavior.fill,
});
maxLength = Math.max(frames.length, maxLength);
}
Expand All @@ -213,7 +212,7 @@ export class OrchestrationMatrix {
rowFragments.push({
frames,
startColumn: 0,
static: timing.behavior instanceof StaticBehavior,
fill: timing.behavior.fill,
});
maxLength = Math.max(frames.length, maxLength);
}
Expand Down
25 changes: 15 additions & 10 deletions packages/boxel-motion/addon/src/models/sprite.ts
Original file line number Diff line number Diff line change
Expand Up @@ -163,24 +163,29 @@ export default class Sprite {

get initial(): { [k in string]: Value } {
let initialBounds = {};
let boundsRect: DOMRect;
if (this.initialBounds) {
let { x, y, width, height, top, right, bottom, left } =
this.initialBounds.relativeToParent;
if (this.type == SpriteType.Removed) {
// because removed sprites are moved to the orphans container under the AnimationContext
boundsRect = this.initialBounds.relativeToContext;
} else {
boundsRect = this.initialBounds.relativeToParent;
}

initialBounds = {
// TODO: maybe also for top/left?
// TODO: figure out if we want the boundsDelta to be under these properties
'translate-x': `${-(this.boundsDelta?.x ?? 0)}px`,
'translate-y': `${-(this.boundsDelta?.y ?? 0)}px`,

x: `${x}px`,
y: `${y}px`,
width: `${width}px`,
height: `${height}px`,
top: `${top}px`,
right: `${right}px`,
bottom: `${bottom}px`,
left: `${left}px`,
x: `${boundsRect.x}px`,
y: `${boundsRect.y}px`,
width: `${boundsRect.width}px`,
height: `${boundsRect.height}px`,
top: `${boundsRect.top}px`,
right: `${boundsRect.right}px`,
bottom: `${boundsRect.bottom}px`,
left: `${boundsRect.left}px`,
};
}

Expand Down
9 changes: 9 additions & 0 deletions packages/boxel-motion/addon/src/styles/addon.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.animation-context {
position: relative;
}
.animation-context.debugging {
border: 1px dashed blue;
}
.animation-context.debugging .sprite {
border: 1px dotted green;
}
3 changes: 1 addition & 2 deletions packages/boxel-motion/addon/src/utils/generate-frames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export default function generateFrames(

return resolveFrameGenerator(normalizedProperty, generator);
}

if (typeof options !== 'object') {
if (!(timing.behavior instanceof StaticBehavior)) {
throw new Error(
Expand All @@ -84,7 +83,7 @@ export default function generateFrames(
}

if (!timing.duration) {
throw new Error('Static behavior requires a duration');
timing.duration = 1;
}

// todo maybe throw error if options is not numeric or string
Expand Down
1 change: 1 addition & 0 deletions packages/boxel-motion/test-app/app/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Application from '@ember/application';
import config from 'boxel-motion-test-app/config/environment';
import loadInitializers from 'ember-load-initializers';
import Resolver from 'ember-resolver';
import '@cardstack/boxel-motion/styles/addon.css';
import './deprecation-workflow';

export default class App extends Application {
Expand Down
Loading
Loading