-
Notifications
You must be signed in to change notification settings - Fork 19
Animation on hover w/ Gleam Logo #2807
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
base: main
Are you sure you want to change the base?
Changes from 3 commits
52b2af2
6859dfc
175ee69
c864859
c4ab10f
ea04fe0
a20cf9f
6cc3d5b
0335cf1
72c9864
4ecb22e
0ac1be3
cf62de1
1d67c55
d007423
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<div | ||
class="gleam-logo-container" | ||
{{did-insert this.setupRive}} | ||
{{will-destroy this.cleanupRive}} | ||
{{on "mouseenter" this.handleMouseEnter}} | ||
{{on "mouseleave" this.handleMouseLeave}} | ||
> | ||
{{! The canvas will be inserted here by Rive }} | ||
</div> |
Original file line number | Diff line number | Diff line change | ||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,123 @@ | ||||||||||||
import Component from '@glimmer/component'; | ||||||||||||
import { tracked } from '@glimmer/tracking'; | ||||||||||||
import { action } from '@ember/object'; | ||||||||||||
import { Rive } from '@rive-app/canvas'; | ||||||||||||
|
||||||||||||
interface GleamLogoSignature { | ||||||||||||
Element: HTMLDivElement; | ||||||||||||
Args: { | ||||||||||||
class?: string; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Arpan-206 in Ember you don't need to accept DOM-level attributes like |
||||||||||||
style?: string; | ||||||||||||
[key: string]: unknown; | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Arpan-206 what is this used for? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. well the first one, forgot to remove the signature There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We don't have to do this anywhere else in the repository, try figuring out why? |
||||||||||||
}; | ||||||||||||
Blocks: Record<string, never>; | ||||||||||||
} | ||||||||||||
|
||||||||||||
export default class GleamLogoComponent extends Component<GleamLogoSignature> { | ||||||||||||
@tracked riveInstance: Rive | null = null; | ||||||||||||
container: HTMLElement | null = null; | ||||||||||||
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAdd unit tests for this component. The component is completely missing test coverage according to static analysis. Consider adding unit tests that verify the initialization, animation behaviors, and cleanup processes. I can help you implement comprehensive tests for this component that verify:
Would you like me to provide a test implementation? 🌐 Web query:
💡 Result: Writing Unit Tests for Ember Components with Rive AnimationsOverview Testing Ember components that embed Rive animations requires a combination of standard Ember component testing strategies and considerations specific to third-party rendering handled by Rive. Because Rive animations are rendered in a Recommended Approach 1. Use Rendering or Integration TestsWhile "unit tests" in Ember often refer to logic-only tests, components—especially those outputting dynamic UI like Rive animations—are best tested with rendering (also known as integration) tests. These let you render the component, interact with it, and assert on the DOM or called behaviors[1][5][7][8].
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | my-rive-component', function(hooks) {
setupRenderingTest(hooks);
test('it renders the canvas for Rive', async function(assert) {
await render(hbs`<MyRiveComponent />`);
assert.dom('canvas').exists('Rive canvas is rendered');
});
}); [1][5][7] 2. Mock Rive Animation LogicExternal libraries like Rive often require DOM interaction and asynchronous file loading. To keep your tests fast and reliable:
// In your test setup
let riveInstanceMock = {
on: sinon.spy(),
play: sinon.spy(),
pause: sinon.spy()
};
window.Rive = sinon.stub().returns(riveInstanceMock);
3. Assert DOM and Behavioral Effects
test('it reacts to Rive animation event', async function(assert) {
await render(hbs`<MyRiveComponent />`);
// Simulate event
riveInstanceMock.on.withArgs('riveevent').callArgWith(1, { data: { name: 'someEvent' } });
// Assert component behavior
assert.dom('.some-result').hasText('Expected Result');
}); 4. Handle Asynchronous BehaviorMany Rive interactions (loading, animation events) are async. Use 5. Tips and Good Practices
Example Workflow
Summary
This test strategy ensures that your components work as intended, even when integrating complex, third-party animation libraries like Rive. Citations:
Add integration tests for GleamLogoComponent The
Feel free to follow Ember’s testing guide for rendering tests and let me know if you’d like a full example test implementation. |
||||||||||||
|
||||||||||||
@action | ||||||||||||
cleanupRive() { | ||||||||||||
if (this.riveInstance) { | ||||||||||||
this.riveInstance.stop(); | ||||||||||||
this.riveInstance = null; | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
@action | ||||||||||||
handleMouseEnter() { | ||||||||||||
if (this.riveInstance) { | ||||||||||||
const stateMachines = this.riveInstance.stateMachineNames; | ||||||||||||
|
||||||||||||
if (stateMachines && stateMachines.length > 0) { | ||||||||||||
const stateMachineName = stateMachines[0]; | ||||||||||||
console.log('Playing hover animation with state machine:', stateMachineName); | ||||||||||||
this.riveInstance.reset(); | ||||||||||||
this.riveInstance.play(stateMachineName); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
@action | ||||||||||||
handleMouseLeave() { | ||||||||||||
if (this.riveInstance) { | ||||||||||||
// Stop the animation and reset to initial state | ||||||||||||
this.riveInstance.stop(); | ||||||||||||
this.riveInstance.reset(); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
|
||||||||||||
@action | ||||||||||||
setupRive(element: HTMLDivElement) { | ||||||||||||
this.container = element; | ||||||||||||
|
||||||||||||
try { | ||||||||||||
// Create canvas element | ||||||||||||
const canvas = document.createElement('canvas'); | ||||||||||||
canvas.width = 141; // Fixed size for crisp rendering | ||||||||||||
canvas.height = 144; | ||||||||||||
canvas.style.width = '100%'; | ||||||||||||
canvas.style.height = '100%'; | ||||||||||||
Arpan-206 marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||
canvas.style.border = '1px solid red'; // Visual debugging | ||||||||||||
element.appendChild(canvas); | ||||||||||||
|
||||||||||||
// Initialize Rive | ||||||||||||
this.riveInstance = new Rive({ | ||||||||||||
src: '/assets/animations/gleam_logo_animation.riv', | ||||||||||||
canvas: canvas, | ||||||||||||
autoplay: false, | ||||||||||||
onLoad: () => { | ||||||||||||
console.log('Gleam logo animation loaded'); | ||||||||||||
|
||||||||||||
// Log available state machines | ||||||||||||
const stateMachines = this.riveInstance?.stateMachineNames; | ||||||||||||
console.log('All State Machines:', stateMachines); | ||||||||||||
|
||||||||||||
if (stateMachines && stateMachines.length > 0) { | ||||||||||||
// Log details about each state machine | ||||||||||||
stateMachines.forEach((name, index) => { | ||||||||||||
console.log(`State Machine ${index + 1}:`, { | ||||||||||||
name, | ||||||||||||
inputs: this.riveInstance?.stateMachineInputs(name), | ||||||||||||
}); | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
|
||||||||||||
// Play initial animation directly with longer delay | ||||||||||||
setTimeout(() => { | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does |
||||||||||||
if (this.riveInstance) { | ||||||||||||
const stateMachines = this.riveInstance.stateMachineNames; | ||||||||||||
|
||||||||||||
if (stateMachines && stateMachines.length > 0) { | ||||||||||||
// Try each state machine | ||||||||||||
stateMachines.forEach((stateMachineName) => { | ||||||||||||
console.log('Attempting to play with state machine:', stateMachineName); | ||||||||||||
|
||||||||||||
// Reset and play | ||||||||||||
this.riveInstance?.reset(); | ||||||||||||
this.riveInstance?.play(stateMachineName); | ||||||||||||
|
||||||||||||
// Log animation state after a short delay | ||||||||||||
setTimeout(() => { | ||||||||||||
if (this.riveInstance) { | ||||||||||||
console.log('Animation state after play:', { | ||||||||||||
stateMachine: stateMachineName, | ||||||||||||
isPlaying: this.riveInstance.isPlaying, | ||||||||||||
isPaused: this.riveInstance.isPaused, | ||||||||||||
isStopped: this.riveInstance.isStopped, | ||||||||||||
allStateMachines: this.riveInstance.stateMachineNames, | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
}, 500); | ||||||||||||
}); | ||||||||||||
} | ||||||||||||
} | ||||||||||||
}, 2000); | ||||||||||||
}, | ||||||||||||
}); | ||||||||||||
} catch (error: unknown) { | ||||||||||||
console.error('Error setting up Rive:', error); | ||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's report this to sentry, don't just swallow and ignore |
||||||||||||
} | ||||||||||||
} | ||||||||||||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,6 @@ | ||
{{#if (eq @variant "color")}} | ||
{{#if (and (eq @language.slug "gleam") (eq @variant "color"))}} | ||
<GleamLogo ...attributes /> | ||
{{else if (eq @variant "color")}} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Arpan-206 let's not assume that every color language logo usage of Gleam is going to be animated for now - let's keep this explicit (use You can imagine using a color logo in things like user profile pages where we might not want this animation to occur |
||
<img alt={{@language.name}} src="{{@language.colorLogoUrl}}" ...attributes /> | ||
{{else if (eq @variant "gray")}} | ||
<img alt={{@language.name}} src="{{@language.grayLogoUrl}}" ...attributes /> | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,5 +15,6 @@ export default class LanguageLogoComponent extends Component<Signature> {} | |
declare module '@glint/environment-ember-loose/registry' { | ||
export default interface Registry { | ||
LanguageLogo: typeof LanguageLogoComponent; | ||
GleamLogo: typeof import('./gleam-logo').default; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @Arpan-206 why is this registered here and not in |
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
.gleam-logo-container { | ||
Arpan-206 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
display: block; | ||
width: 100%; | ||
height: 100%; | ||
} | ||
|
||
.gleam-logo-container canvas { | ||
display: block; | ||
width: 100%; | ||
height: 100%; | ||
object-fit: contain; | ||
Arpan-206 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our convention for handlers is to always name them after the event (i.e. something like
this.handleDidInsert
in this case). Let's us now have to worry about naming each of these + allows us to easily edit handlers to add more functionality without having to rename them.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
noted!