Skip to content

Commit f653911

Browse files
committed
chore: add playground demo page
1 parent 0d9e9db commit f653911

File tree

8 files changed

+454
-28
lines changed

8 files changed

+454
-28
lines changed

.github/workflows/deploy.yml

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Deploy playground demo # 工作流的名称
2+
3+
on: # 触发条件
4+
push:
5+
branches:
6+
- main # 当 main 分支收到推送时触发
7+
workflow_dispatch: # 允许手动触发
8+
9+
permissions: # GitHub token 的权限设置
10+
contents: read # 读取仓库内容
11+
pages: write # 写入 GitHub Pages
12+
id-token: write # 写入身份令牌
13+
14+
concurrency: # 并发控制
15+
group: pages # 同一时间只允许一个部署任务运行
16+
cancel-in-progress: true # 如果有新的部署,取消正在进行的部署
17+
18+
jobs:
19+
deploy:
20+
environment: # 环境配置
21+
name: github-pages # 环境名称
22+
url: ${{ steps.deployment.outputs.page_url }} # 部署后的 URL
23+
24+
runs-on: ubuntu-latest # 运行环境
25+
26+
steps:
27+
- uses: actions/checkout@v4
28+
- uses: pnpm/action-setup@v4
29+
with:
30+
run_install: false
31+
- uses: actions/setup-node@v4
32+
with:
33+
node-version: lts/*
34+
cache: pnpm
35+
36+
- run: pnpm i
37+
- run: pnpm playground:build
38+
39+
# 添加缓存验证步骤
40+
- name: Verify build
41+
run: |
42+
if [ ! -d "playground/dist" ]; then
43+
echo "Playground build failed - dist directory not found"
44+
exit 1
45+
fi
46+
47+
- uses: actions/configure-pages@v5
48+
with:
49+
enablement: true
50+
- uses: actions/upload-pages-artifact@v3
51+
with:
52+
path: playground/dist # 指定要部署的目录
53+
- uses: actions/deploy-pages@v4
54+
id: deployment

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ const timer = pausableTimers(() => {
5757
timer.pause()
5858
timer.resume()
5959
timer.clear()
60-
timer.reset()
60+
timer.restart()
6161
timer.isPaused()
6262
timers.getRemainingTime()
6363
timer.isCompleted() // only for timeout mode

package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@
4444
"scripts": {
4545
"build": "unbuild",
4646
"dev": "unbuild --stub",
47+
"playground:dev": "vite playground",
48+
"playground:build": "vite build playground",
49+
"playground:preview": "vite preview playground",
4750
"lint": "eslint .",
4851
"prepublishOnly": "automd && nr build",
4952
"release": "bumpp && pnpm publish",

playground/index.html

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title></title>
7+
<link rel="stylesheet" href="./style.css">
8+
</head>
9+
<body>
10+
<!-- GitHub Corner -->
11+
<a href="https://github.com/byronogis/pausable-timers" class="github-corner" aria-label="View source on GitHub">
12+
<svg width="80" height="80" viewBox="0 0 250 250" style="fill:var(--primary); color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true">
13+
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
14+
<path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"></path>
15+
<path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"></path>
16+
</svg>
17+
</a>
18+
<div class="container">
19+
<div class="project-info">
20+
<h1></h1>
21+
<span class="version"></span>
22+
</div>
23+
<div class="timer-display">
24+
<div class="progress-container">
25+
<div class="progress-bar"></div>
26+
</div>
27+
<div class="status-text"></div>
28+
</div>
29+
30+
<div class="control-panel">
31+
<div class="input-group">
32+
<input type="number" id="timeInput" value="5000" min="100" step="100">
33+
<label>ms</label>
34+
<select id="modeSelect">
35+
<option value="timeout">Timeout</option>
36+
<option value="interval">Interval</option>
37+
</select>
38+
</div>
39+
40+
<div class="button-group">
41+
<button id="startBtn">Start</button>
42+
<button id="pauseBtn" disabled>Pause</button>
43+
<button id="clearBtn" disabled>Clear</button>
44+
<button id="restartBtn" disabled>Restart</button>
45+
</div>
46+
</div>
47+
</div>
48+
<script type="module" src="./main.js"></script>
49+
</body>
50+
</html>

playground/index.ts

Lines changed: 0 additions & 27 deletions
This file was deleted.

playground/main.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
import pkg from '../package.json'
2+
import { pausableTimers } from '../src/index'
3+
4+
// 设置页面标题和项目信息
5+
document.title = `${pkg.name} | Playground`
6+
document.querySelector('.project-info h1').textContent = pkg.name
7+
document.querySelector('.version').textContent = `v${pkg.version}`
8+
9+
const elements = {
10+
progressBar: document.querySelector('.progress-bar'),
11+
statusText: document.querySelector('.status-text'),
12+
timeInput: document.querySelector('#timeInput'),
13+
modeSelect: document.querySelector('#modeSelect'),
14+
startBtn: document.querySelector('#startBtn'),
15+
pauseBtn: document.querySelector('#pauseBtn'),
16+
clearBtn: document.querySelector('#clearBtn'),
17+
restartBtn: document.querySelector('#restartBtn'),
18+
}
19+
20+
let timer = null
21+
let animationFrameId = null
22+
let cycleCount = 0
23+
24+
function updateProgress(remaining, total) {
25+
const progress = (remaining / total) * 100
26+
const progressBar = elements.progressBar
27+
28+
// 完全移除过渡效果
29+
progressBar.style.transition = 'none'
30+
progressBar.style.width = `${progress}%`
31+
32+
if (remaining === total) {
33+
// 当需要重置到 100% 时,保持 none
34+
return
35+
}
36+
37+
// 确保 DOM 更新后再添加过渡效果
38+
requestAnimationFrame(() => {
39+
progressBar.style.transition = 'width 0.1s linear'
40+
})
41+
}
42+
43+
function updateButtons(isRunning) {
44+
elements.startBtn.disabled = isRunning
45+
elements.pauseBtn.disabled = !isRunning
46+
elements.clearBtn.disabled = !isRunning
47+
elements.restartBtn.disabled = !isRunning
48+
elements.timeInput.disabled = isRunning
49+
elements.modeSelect.disabled = isRunning
50+
}
51+
52+
function animate(total) {
53+
if (!timer)
54+
return
55+
56+
const remaining = timer.getRemainingTime()
57+
updateProgress(remaining, total)
58+
59+
// 根据模式显示不同的状态信息
60+
if (elements.modeSelect.value === 'interval') {
61+
elements.statusText.textContent = `Cycle ${cycleCount} - Remaining: ${remaining}ms`
62+
}
63+
else {
64+
elements.statusText.textContent = `Remaining: ${remaining}ms`
65+
}
66+
67+
if (timer.isCompleted()) {
68+
elements.statusText.textContent = 'Completed!'
69+
updateButtons(false)
70+
return
71+
}
72+
73+
animationFrameId = requestAnimationFrame(() => animate(total))
74+
}
75+
76+
function startTimer() {
77+
const delay = Number.parseInt(elements.timeInput.value)
78+
// 添加输入值检查
79+
if (delay < 100) {
80+
elements.statusText.textContent = 'Please enter a value greater than 100ms'
81+
return
82+
}
83+
84+
const mode = elements.modeSelect.value
85+
86+
// 先清理现有的计时器和动画
87+
if (timer) {
88+
timer.clear()
89+
}
90+
if (animationFrameId) {
91+
cancelAnimationFrame(animationFrameId)
92+
animationFrameId = null
93+
}
94+
95+
elements.statusText.textContent = ''
96+
timer = pausableTimers(() => {
97+
if (mode === 'timeout') {
98+
cancelAnimationFrame(animationFrameId)
99+
updateProgress(0, delay)
100+
elements.statusText.textContent = 'Completed!'
101+
updateButtons(false)
102+
}
103+
else {
104+
// 在 interval 模式下,先取消动画帧
105+
cancelAnimationFrame(animationFrameId)
106+
// 立即重置进度条到 100%(无动画)
107+
updateProgress(delay, delay)
108+
// 在 interval 模式下添加循环次数显示
109+
cycleCount++
110+
elements.statusText.textContent = `Cycle ${cycleCount}`
111+
// 立即开始新的倒计时(有动画)
112+
requestAnimationFrame(() => animate(delay))
113+
}
114+
}, delay, { mode })
115+
116+
updateButtons(true)
117+
// 启动时也应用相同的逻辑
118+
updateProgress(delay, delay)
119+
requestAnimationFrame(() => animate(delay))
120+
}
121+
122+
// Event Listeners
123+
elements.startBtn.addEventListener('click', startTimer)
124+
125+
elements.pauseBtn.addEventListener('click', () => {
126+
if (!timer)
127+
return
128+
if (timer.isPaused()) {
129+
timer.resume()
130+
elements.pauseBtn.textContent = 'Pause'
131+
animate(Number.parseInt(elements.timeInput.value))
132+
}
133+
else {
134+
timer.pause()
135+
elements.pauseBtn.textContent = 'Resume'
136+
cancelAnimationFrame(animationFrameId)
137+
}
138+
})
139+
140+
elements.clearBtn.addEventListener('click', () => {
141+
if (!timer)
142+
return
143+
timer.clear()
144+
// 确保动画帧被取消
145+
if (animationFrameId) {
146+
cancelAnimationFrame(animationFrameId)
147+
animationFrameId = null
148+
}
149+
updateProgress(100, 100)
150+
elements.statusText.textContent = ''
151+
updateButtons(false)
152+
elements.pauseBtn.textContent = 'Pause'
153+
cycleCount = 0
154+
})
155+
156+
elements.restartBtn.addEventListener('click', () => {
157+
if (!timer)
158+
return
159+
// 先清理现有的动画帧
160+
if (animationFrameId) {
161+
cancelAnimationFrame(animationFrameId)
162+
animationFrameId = null
163+
}
164+
timer.restart()
165+
elements.pauseBtn.textContent = 'Pause'
166+
updateProgress(Number.parseInt(elements.timeInput.value), Number.parseInt(elements.timeInput.value))
167+
animate(Number.parseInt(elements.timeInput.value))
168+
cycleCount = 0
169+
})
170+
171+
// 添加输入框验证
172+
elements.timeInput.addEventListener('input', (e) => {
173+
const value = Number.parseInt(e.target.value)
174+
if (value < 100) {
175+
e.target.setCustomValidity('Please enter a value greater than 100ms')
176+
}
177+
else {
178+
e.target.setCustomValidity('')
179+
}
180+
})

0 commit comments

Comments
 (0)