Skip to content

Commit 575f9db

Browse files
authored
Merge pull request #498 from webstackdev/feature/pause-and-play-on-hero
Feature/pause and play on hero
2 parents dc66c66 + 524db89 commit 575f9db

24 files changed

Lines changed: 839 additions & 79 deletions

File tree

.github/workflows/deployment.yml

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,63 @@ jobs:
7272
- name: Comment preview URL on PR
7373
if: always() && steps.vercel-preview.outcome == 'success'
7474
uses: actions/github-script@v8
75+
env:
76+
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
77+
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
78+
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
7579
with:
7680
script: |
77-
const previewUrl = '${{ steps.vercel-preview.outputs.preview-url }}';
7881
const workflowRun = context.payload.workflow_run;
7982
const pr = workflowRun?.pull_requests?.[0];
80-
if (!pr || !previewUrl) {
81-
core.warning('Missing pull request metadata or preview URL; skipping preview success comment.');
83+
if (!pr) {
84+
core.warning('Missing pull request metadata; skipping preview success comment.');
85+
return;
86+
}
87+
88+
const rawPreviewUrl = '${{ steps.vercel-preview.outputs.preview-url }}'.trim();
89+
const isVercelUrl = (url) => typeof url === 'string' && /vercel\.(app|com)/.test(url);
90+
const fetchDeploymentUrl = async () => {
91+
if (!process.env.VERCEL_TOKEN || !process.env.VERCEL_PROJECT_ID) {
92+
core.info('Missing Vercel credentials; cannot query deployment API.');
93+
return null;
94+
}
95+
if (typeof fetch !== 'function') {
96+
core.info('Fetch API unavailable in this runtime.');
97+
return null;
98+
}
99+
const query = new URLSearchParams({
100+
projectId: process.env.VERCEL_PROJECT_ID,
101+
'meta-githubCommitSha': workflowRun?.head_sha ?? '',
102+
limit: '1'
103+
});
104+
if (process.env.VERCEL_ORG_ID) {
105+
query.set('teamId', process.env.VERCEL_ORG_ID);
106+
}
107+
const response = await fetch(`https://api.vercel.com/v6/deployments?${query.toString()}`, {
108+
headers: {
109+
Authorization: `Bearer ${process.env.VERCEL_TOKEN}`
110+
}
111+
});
112+
if (!response.ok) {
113+
core.warning(`Unable to fetch deployment info (status ${response.status}).`);
114+
return null;
115+
}
116+
const data = await response.json();
117+
const deployment = data?.deployments?.[0];
118+
if (deployment?.url) {
119+
return `https://${deployment.url}`;
120+
}
121+
if (deployment?.inspectorUrl) {
122+
return deployment.inspectorUrl.startsWith('http')
123+
? deployment.inspectorUrl
124+
: `https://${deployment.inspectorUrl}`;
125+
}
126+
return null;
127+
};
128+
129+
let previewUrl = isVercelUrl(rawPreviewUrl) ? rawPreviewUrl : await fetchDeploymentUrl();
130+
if (!isVercelUrl(previewUrl)) {
131+
core.warning('Unable to resolve Vercel preview URL; skipping preview success comment.');
82132
return;
83133
}
84134
@@ -89,7 +139,13 @@ jobs:
89139
const commitUrl = sha
90140
? `https://github.com/${context.repo.owner}/${context.repo.repo}/commit/${sha}`
91141
: `https://github.com/${context.repo.owner}/${context.repo.repo}`;
92-
const actor = workflowRun.actor ?? 'workflow_run';
142+
const actorLogin = typeof workflowRun.actor === 'string'
143+
? workflowRun.actor
144+
: workflowRun.actor?.login;
145+
const actor = actorLogin ?? context.actor ?? 'workflow_run';
146+
const actorLink = workflowRun.actor?.html_url || (actorLogin ? `https://github.com/${actorLogin}` : null);
147+
const actorDisplay = actor.startsWith('@') ? actor : `@${actor}`;
148+
const triggeredBy = actorLink ? `[${actorDisplay}](${actorLink})` : actorDisplay;
93149
94150
const body = [
95151
commentTag,
@@ -99,9 +155,9 @@ jobs:
99155
'| --- | --- |',
100156
`| Branch | \`${branch}\` |`,
101157
`| Commit | [${shortSha}](${commitUrl}) |`,
102-
`| Preview | [Open preview](${previewUrl}) |`,
158+
`| Preview | <a href="${previewUrl}" target="_blank" rel="noopener noreferrer">Open preview</a> |`,
103159
'',
104-
`_Triggered by @${actor}_`
160+
`_Triggered by ${triggeredBy}_`
105161
].join('\n');
106162
107163
const comments = await github.paginate(github.rest.issues.listComments, {

.husky/prepare.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/usr/bin/env node
2+
/* eslint-disable no-undef */
3+
/**
4+
* Husky prepare script to install git hooks. It's designed to quiet warnings on
5+
* CI environments where .git directory may be missing when "prepare" script runs
6+
* (e.g., during "npm install" step).
7+
*/
8+
import { existsSync } from 'node:fs'
9+
import { join } from 'node:path'
10+
import { execSync } from 'node:child_process'
11+
12+
const projectRoot = process.cwd()
13+
const gitDirectory = join(projectRoot, '.git')
14+
15+
if (!existsSync(gitDirectory)) {
16+
console.warn(`✅ Skipping Husky install: missing .git directory at ${gitDirectory}`)
17+
process.exit(0)
18+
}
19+
20+
try {
21+
console.log(`Running Husky install from ${projectRoot}`)
22+
execSync('husky', { stdio: 'inherit', cwd: projectRoot })
23+
console.log('✅ Husky install complete')
24+
} catch (error) {
25+
console.error('❌ Husky install failed')
26+
const status = typeof error === 'object' && error && 'status' in error && typeof error.status === 'number'
27+
? error.status
28+
: 1
29+
process.exit(status)
30+
}

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"squoosh",
9797
"tanabata",
9898
"TIMESTAMPTZ",
99+
"tktco",
99100
"Trino",
100101
"TRUNC",
101102
"Tscompile",

@types/window.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ declare global {
8989
updateConsent?: (_category: 'analytics' | 'marketing' | 'functional', _value: boolean) => void
9090
cacheEmbed?: (_key: string, _data: unknown, _ttl: number) => void
9191
saveMastodonInstance?: (_domain: string) => void
92+
setOverlayPauseState?: (_source: string, _isPaused: boolean) => void
9293

9394
/**
9495
* Custom evaluation error injected during Playwright tests

_TODO.md

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,10 @@
11
<!-- markdownlint-disable-file -->
22
# TODO
33

4-
## Pause and Play
5-
6-
Next, I'd like to add a "pause" and "play" icon to src/components/Animations/Computers
7-
There are icons with those names already configured for the Icon component.
8-
There are hooks for pause and play already setup in the component.
9-
The icon should be displayed in the low right hand corner of the animation, with 4px of padding from the bottom and right side. It should overlay the animation, not expand the bounding box of the animation.
10-
114
## Performance
125

136
Implement mitigations in test/e2e/specs/07-performance/PERFORMANCE.md
147

15-
## GitHub
16-
17-
- Make sure actions workflows are working correctly after performance tests pass and whole suite is green
18-
- Change Dependabut to open a single PR with all dependency updates
19-
- Add 'hotfix' branch and add branch protection rules
20-
218
## Analytics
229

2310
Vercel Analytics
@@ -35,6 +22,8 @@ See note in src/components/scripts/sentry/client.ts - "User Feedback - allow use
3522

3623
docs/CONTACT_FORM.md
3724

25+
Where to upload to?
26+
3827
## Search
3928

4029
Add Upstash Search as a Vercel Marketplace Integration.

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"format:code": "FORCE_COLOR=1 npx prettier --write \"@types/**/*.{js,ts}\" \"src/**/*.{js,ts,tsx,astro}\" --plugin=prettier-plugin-astro",
3636
"format:json": "FORCE_COLOR=1 npx prettier --write '**/*.json' --cache --ignore-path .gitignore",
3737
"format:style": "FORCE_COLOR=1 npx stylelint --fix \"src/**/*.{css,astro}\"",
38-
"lint": "npm run lint:base && npm run lint:actions",
38+
"lint": "npm run lint:base && npm run lint:actions && npm run check",
3939
"lint:base": "npm run lint:json && npm run lint:style && npm run lint:tsc:check && npm run lint:code",
4040
"lint:code": "npx eslint \"@types/**/*.{js,ts}\" \"src/**/*.{js,ts,tsx,astro}\" \"test/**/*.{js,ts,tsx,astro}\"",
4141
"lint:tsc:check": "tsc --noEmit -p tsconfig.json --pretty false",
@@ -60,7 +60,7 @@
6060
"test:e2e:full": "dotenv -e .env.development -- cross-env FORCE_COLOR=1 E2E_MOCKS=1 npx playwright test",
6161
"test:unit": "FORCE_COLOR=1 npx vitest run",
6262
"upgrade": "npx @astrojs/upgrade",
63-
"prepare": "node -e \"const fs=require('node:fs');if(!fs.existsSync('.git')){console.log('Skipping Husky install (missing .git directory)');process.exit(0);}\" && husky"
63+
"prepare": "node .husky/prepare.js"
6464
},
6565
"dependencies": {
6666
"@astrojs/check": "0.9.6",
@@ -158,7 +158,7 @@
158158
"eslint-import-resolver-typescript": "^4.4.4",
159159
"eslint-plugin-astro": "1.5.0",
160160
"eslint-plugin-import": "2.32.0",
161-
"eslint-plugin-jsdoc": "61.4.1",
161+
"eslint-plugin-jsdoc": "61.4.2",
162162
"eslint-plugin-jsx-a11y": "6.10.2",
163163
"eslint-plugin-security": "3.0.1",
164164
"eslint-plugin-yml": "1.19.0",

playwright.config.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import { defineConfig, devices } from '@playwright/test'
44
* Read environment variables from file.
55
* https://github.com/motdotla/dotenv
66
*/
7-
import 'dotenv/config'
7+
import dotenv from 'dotenv'
8+
import { isCI } from 'src/lib/config/environmentServer'
9+
10+
if ( !isCI() ) dotenv.config({ path: '.env.development' })
811

912
/**
1013
* See https://playwright.dev/docs/test-configuration.

src/components/Animations/Computers/client/__tests__/index.spec.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,19 @@ describe('ComputersAnimationElement', () => {
171171
const controllerArgs = createAnimationControllerMock.mock.calls[0]?.[0]
172172
const pauseHandler = controllerArgs?.onPause
173173
const resumeHandler = controllerArgs?.onPlay
174+
const toggleButton = element.querySelector<HTMLButtonElement>('[data-animation-toggle]')
174175

175176
pauseHandler?.()
177+
expect(element.getAttribute('data-animation-state')).toBe('paused')
178+
expect(toggleButton?.getAttribute('aria-pressed')).toBe('true')
179+
expect(toggleButton?.getAttribute('aria-label')).toBe('Play animation')
176180
resumeHandler?.()
177181

178182
expect(timelineMock.pause).toHaveBeenCalled()
179183
expect(timelineMock.play).toHaveBeenCalled()
184+
expect(element.getAttribute('data-animation-state')).toBe('playing')
185+
expect(toggleButton?.getAttribute('aria-pressed')).toBe('false')
186+
expect(toggleButton?.getAttribute('aria-label')).toBe('Pause animation')
180187
expect(getBreadcrumbOperations()).toEqual(expect.arrayContaining(['pause', 'resume']))
181188
})
182189
})
@@ -221,10 +228,31 @@ describe('ComputersAnimationElement', () => {
221228
element.initialize()
222229

223230
element.pause()
231+
expect(element.getAttribute('data-animation-state')).toBe('paused')
224232
element.resume()
225233

226234
expect(timelineMock.pause).toHaveBeenCalledTimes(1)
227235
expect(timelineMock.play).toHaveBeenCalledTimes(1)
236+
expect(element.getAttribute('data-animation-state')).toBe('playing')
237+
})
238+
})
239+
240+
it('requests pause and play through the animation controller when the toggle is clicked', async () => {
241+
await renderComputersAnimation(async ({ element }) => {
242+
element.initialize()
243+
244+
const toggleButton = element.querySelector<HTMLButtonElement>('[data-animation-toggle]')
245+
const controllerHandle = getLastControllerHandle()
246+
247+
expect(toggleButton).toBeTruthy()
248+
249+
toggleButton?.click()
250+
expect(controllerHandle?.requestPause).toHaveBeenCalledTimes(1)
251+
252+
element.pause()
253+
254+
toggleButton?.click()
255+
expect(controllerHandle?.requestPlay).toHaveBeenCalledTimes(1)
228256
})
229257
})
230258

0 commit comments

Comments
 (0)