bloub-to-vue-and-react 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +249 -111
- package/dist/bloub-react.js +80 -0
- package/dist/bloub-vue.js +96 -0
- package/dist/dom-DPqMRRT2.js +2318 -0
- package/dist/types/components/Core/BloubBot.vue.d.ts +8 -33
- package/dist/types/components/Core/react/BloubBot.d.ts +51 -0
- package/dist/types/components/Core/react.d.ts +9 -0
- package/dist/types/core/controller.d.ts +127 -0
- package/dist/types/core/dom.d.ts +12 -0
- package/dist/types/core/render.d.ts +33 -0
- package/package.json +78 -59
- package/dist/bloub.js +0 -2264
package/README.md
CHANGED
|
@@ -1,111 +1,249 @@
|
|
|
1
|
-
# bloub
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
1
|
+
# bloub
|
|
2
|
+
|
|
3
|
+
一个用 SVG 还原 x.ai 机器人头像的项目:**一个实心黑色形状**在 14 个状态之间形变,**两个白色形状**作为眼睛、各自独立形变,背景为纯色。不依赖任何动画库。
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
## 介绍
|
|
8
|
+
|
|
9
|
+
bloub 是一个框架无关的头像引擎,为 **Vue 3** 和 **React 18+** 提供了一等公民的适配器。整套东西由一个共享内核(`src/bot/` + `src/core/`)驱动,因此两个框架渲染出的画面像素级一致,也永远不会互相漂移。
|
|
10
|
+
|
|
11
|
+
| | Vue 3 | React 18+ |
|
|
12
|
+
|---|---|---|
|
|
13
|
+
| 入口 | `bloub-to-vue-and-react` | `bloub-to-vue-and-react/react` |
|
|
14
|
+
| 组件 | `BloubBot` | `BloubBot` |
|
|
15
|
+
| Props | `size shape color expression paper frozenAt cycle follow gaze ariaLabel` | 相同 |
|
|
16
|
+
| Models | `block state playing elapsed`(通过 `v-model`) | `block state playing`(受控)+ `elapsed`(输出) |
|
|
17
|
+
| 命令式 | `seek` `rendAt` | `seek` `rendAt`(通过 `ref`) |
|
|
18
|
+
|
|
19
|
+
## 安装
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pnpm add bloub-to-vue-and-react
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
`vue` 是 peer 依赖。`react` 和 `react-dom` 是**可选** peer 依赖:只有使用 React 适配器时才需要安装。纯 Vue 项目永远不会把 React 拉进来,反之亦然。
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# React 用户
|
|
29
|
+
pnpm add bloub-to-vue-and-react react react-dom
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## 使用
|
|
33
|
+
|
|
34
|
+
### Vue 3
|
|
35
|
+
|
|
36
|
+
```vue
|
|
37
|
+
<script setup lang="ts">
|
|
38
|
+
import { ref } from 'vue'
|
|
39
|
+
import { BloubBot } from 'bloub-to-vue-and-react'
|
|
40
|
+
|
|
41
|
+
const block = ref(0)
|
|
42
|
+
const state = ref('idle')
|
|
43
|
+
const playing = ref(false)
|
|
44
|
+
</script>
|
|
45
|
+
|
|
46
|
+
<template>
|
|
47
|
+
<BloubBot v-model:block="block" v-model:state="state" v-model:playing="playing" />
|
|
48
|
+
</template>
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
`block` 是播放游标——一段蒙太奇可能两次播放同一个状态,所以用索引来标识你所在的位置;`state` 作为输出跟随它。`playing` 用于开始和停止循环播放。
|
|
52
|
+
|
|
53
|
+
要渲染一帧精确画面而不启动动画循环(缩略图、状态看板),传入 `frozenAt`:
|
|
54
|
+
|
|
55
|
+
```vue
|
|
56
|
+
<BloubBot state="orbit" :size="120" :frozen-at="1.2" />
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
通过模板 ref 进行命令式播放:
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import { ref } from 'vue'
|
|
64
|
+
import { BloubBot } from 'bloub-to-vue-and-react'
|
|
65
|
+
|
|
66
|
+
const bot = ref<InstanceType<typeof BloubBot> | null>(null)
|
|
67
|
+
bot.value?.seek(2, 0.5) // 跳到第 2 块、0.5 秒处
|
|
68
|
+
bot.value?.rendAt(3.1) // 在绝对时间 3.1 秒处渲染蒙太奇
|
|
69
|
+
</script>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### React 18+
|
|
73
|
+
|
|
74
|
+
```tsx
|
|
75
|
+
import { useRef, useState } from 'react'
|
|
76
|
+
import { BloubBot, type BloubModel, type BloubBotHandle } from 'bloub-to-vue-and-react/react'
|
|
77
|
+
|
|
78
|
+
function Demo() {
|
|
79
|
+
const bot = useRef<BloubBotHandle>(null)
|
|
80
|
+
const [model, setModel] = useState<BloubModel>({ block: 0, state: 'idle', playing: false, elapsed: 0 })
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<BloubBot
|
|
84
|
+
ref={bot}
|
|
85
|
+
block={model.block}
|
|
86
|
+
state={model.state}
|
|
87
|
+
playing={model.playing}
|
|
88
|
+
onModelChange={setModel}
|
|
89
|
+
/>
|
|
90
|
+
)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`block` / `state` / `playing` 是受控的;`onModelChange` 会把内核的状态回报回来,让父组件在蒙太奇推进时保持同步。`elapsed` 是只读输出。
|
|
95
|
+
|
|
96
|
+
冻结单帧渲染:
|
|
97
|
+
|
|
98
|
+
```tsx
|
|
99
|
+
<BloubBot state="orbit" size={120} frozenAt={1.2} />
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
命令式播放:
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
bot.current?.seek(2, 0.5)
|
|
106
|
+
bot.current?.rendAt(3.1)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 包含内容
|
|
110
|
+
|
|
111
|
+
### 形状(8 种)
|
|
112
|
+
|
|
113
|
+
`cercle`、`galet`、`squircle`、`capsule`、`triangle`、`hexagone`、`nuage`、`goutte`。
|
|
114
|
+
|
|
115
|
+
### 颜色(12 种)
|
|
116
|
+
|
|
117
|
+
`encre`、`creme`、`brun`、`rouge`、`orange`、`ambre`、`vert`、`turquoise`、`bleu`、`violet`、`rose`、`gris`。
|
|
118
|
+
|
|
119
|
+
### 表情(31 个)
|
|
120
|
+
|
|
121
|
+
引擎内置 19 个面部表情和 12 个视线方向。其中带嘴巴的两个(`stupefait`、`sourire`)也会绘制出嘴巴。
|
|
122
|
+
|
|
123
|
+
| id | 含义 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| `neutre` | 中性(默认) |
|
|
126
|
+
| `attentif` | 专注 |
|
|
127
|
+
| `surpris` | 惊讶 |
|
|
128
|
+
| `excite` | 兴奋 |
|
|
129
|
+
| `heureux` | 开心 |
|
|
130
|
+
| `hilare` | 大笑 |
|
|
131
|
+
| `colere` | 愤怒 |
|
|
132
|
+
| `triste` | 悲伤 |
|
|
133
|
+
| `effraye` | 害怕 |
|
|
134
|
+
| `mefiant` | 狐疑 |
|
|
135
|
+
| `confus` | 困惑 |
|
|
136
|
+
| `curieux` | 好奇 |
|
|
137
|
+
| `fier` | 得意 |
|
|
138
|
+
| `timide` | 害羞 |
|
|
139
|
+
| `blase` | 冷漠 |
|
|
140
|
+
| `somnolent` | 困倦 |
|
|
141
|
+
| `admiratif` | 钦佩 |
|
|
142
|
+
| `stupefait` | 目瞪口呆(带嘴巴) |
|
|
143
|
+
| `sourire` | 微笑(带嘴巴) |
|
|
144
|
+
| `regardHaut` / `regardBas` | 向上看 / 向下看 |
|
|
145
|
+
| `regardGauche` / `regardDroite` | 向左看 / 向右看 |
|
|
146
|
+
| `regardHautGauche` / `regardHautDroite` | 左上 / 右上 |
|
|
147
|
+
| `regardBasGauche` / `regardBasDroite` | 左下 / 右下 |
|
|
148
|
+
| `regardArriereHaut` / `regardArriereBas` | 向后上 / 向后下 |
|
|
149
|
+
| `regardArriereGauche` / `regardArriereDroite` | 向后左 / 向后右 |
|
|
150
|
+
|
|
151
|
+
### 状态(14 个)
|
|
152
|
+
|
|
153
|
+
`idle`、`thinking`、`wink`、`wide`、`alert`、`notify`、`exclaim`、`sleep`、`egg`、`hexagon`、`play`、`orbit`、`burst`、`comet`——另加 `swirl`,保留用于界面过渡。
|
|
154
|
+
|
|
155
|
+
## 使用场景
|
|
156
|
+
|
|
157
|
+
- **聊天 / 助手头像**——空闲时目光游移、眨眼,模型流式输出时进入 `thinking`,来新消息时 `notify`。
|
|
158
|
+
- **响应式存在感**——用 `follow` 跟随指针,用 `gaze` 脚本编排入场序列,事件触发时 `alert` / `exclaim`。
|
|
159
|
+
- **动画导出**——冻结渲染器(`frozenAt`、`rendAt`)支撑逐帧精确导出为 SVG、PNG、GIF 和 MP4。
|
|
160
|
+
- **产品自定义器**——8 种形状 × 12 种颜色 × 31 个表情,持久化并作为 `cycle` 蒙太奇回放。
|
|
161
|
+
|
|
162
|
+
## 体积与兼容负担
|
|
163
|
+
|
|
164
|
+
内核与框架无关,因此消费方为框架特定代码付出的成本只有一层薄薄的适配器。生产构建体积:
|
|
165
|
+
|
|
166
|
+
| 分块 | 原始 | gzip | 谁加载 |
|
|
167
|
+
|---|---|---|---|
|
|
168
|
+
| 共享内核(`src/bot/` + `src/core/`) | 46.96 kB | 14.60 kB | 所有人 |
|
|
169
|
+
| Vue 适配器 | 2.80 kB | 1.21 kB | 仅 Vue |
|
|
170
|
+
| React 适配器 | 2.39 kB | 1.14 kB | 仅 React |
|
|
171
|
+
|
|
172
|
+
两个适配器是**相互独立的入口**,共享同一份内核分块:
|
|
173
|
+
|
|
174
|
+
- 一个 Vue 应用引入 `bloub-to-vue-and-react`,加载内核 + Vue 适配器,携带 **0 字节 React 代码**——产物中不含任何 `react`、`react/jsx-runtime` 或 `react-dom` 的引入。
|
|
175
|
+
- 一个 React 应用引入 `bloub-to-vue-and-react/react`,加载内核 + React 适配器,携带 **0 字节 Vue 代码**。
|
|
176
|
+
- `react` / `react-dom` 是可选 peer 依赖,因此纯 Vue 项目永远不会安装 React。
|
|
177
|
+
|
|
178
|
+
简而言之:约 47 kB 的内核才是真正的产品本体(形变、表情、动画);框架税是每个适配器约 1–1.2 kB(gzip),且没有任何跨框架冗余。
|
|
179
|
+
|
|
180
|
+
## 运行
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
pnpm install
|
|
184
|
+
pnpm dev
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
然后打开 http://localhost:5190。
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pnpm test # vitest
|
|
191
|
+
pnpm build # vue-tsc --noEmit && vite build
|
|
192
|
+
pnpm build:lib # 为两个入口打包并生成类型声明
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Vue 3、Vite、TypeScript、Tailwind 4。没有 ESLint 和 Prettier:`vue-tsc` 是唯一的关卡,所以完工前请先运行 `pnpm build`。
|
|
196
|
+
|
|
197
|
+
## 里面有什么
|
|
198
|
+
|
|
199
|
+
左侧的导航栏在三个视图之间切换。**Customise** 提供 8 种身体形状、12 种颜色和 31 个表情,跨访问保留。**Animations** 是一个小编辑器:把状态编排进时间轴、设置每个状态停留的时长、保存结果。**Settings** 存放语言(法语、英语或中文)和致谢。
|
|
200
|
+
|
|
201
|
+
屏幕上的任何东西都可以导出:头像可导出为 SVG、PNG 或动画 GIF,整条时间轴可导出为 GIF 或 MP4。静态格式完全不依赖任何库,视频编码器只在首次请求时才会按需加载。
|
|
202
|
+
|
|
203
|
+
有两个值得了解的 URL:
|
|
204
|
+
|
|
205
|
+
- `#planche`:14 个状态并排冻结展示,用于快速目视检查。
|
|
206
|
+
- `#etat=orbit&stop`:直接打开某个状态,播放暂停。
|
|
207
|
+
|
|
208
|
+

|
|
209
|
+
|
|
210
|
+
## 为什么这些数字看起来很随意
|
|
211
|
+
|
|
212
|
+
它们是测量出来的,而不是拍脑袋定的。参考视频按 10 fps 切帧,每个状态都从帧上测量出来:轮廓用亚像素射线投射,眼睛用胶囊拟合,颜色和描边宽度用直接采样。
|
|
213
|
+
|
|
214
|
+
所以代码里的常量都是**测量值**,把它们四舍五入成更"顺眼"的数值会破坏相似度——而这正是本项目唯一追求的东西。有几个反直觉的点值得在改动之前了解:
|
|
215
|
+
|
|
216
|
+
| 你以为的样子 | 视频里实际的样子 |
|
|
217
|
+
|---|---|
|
|
218
|
+
| 眼睛是 `//` 方向倾斜 | 它们是 `\\` 方向,相对竖直约倾斜 26° |
|
|
219
|
+
| 身体是方圆形 | 它是完美的圆形,径向偏差低于 0.7% |
|
|
220
|
+
| 过渡是弹簧动效 | 指数缓出;身体从不回弹过冲 |
|
|
221
|
+
| 彗星划过屏幕 | 圆点停在原地,是拖尾绕着它转 |
|
|
222
|
+
| 头像在静止时漂浮 | 并非如此,生命力来自目光游移和眨眼 |
|
|
223
|
+
|
|
224
|
+
[docs/measurements.md](docs/measurements.md) 里有其余内容,包括如何重新生成提取出的轮廓。
|
|
225
|
+
|
|
226
|
+
## 架构说明
|
|
227
|
+
|
|
228
|
+
`src/bot/` 和 `src/core/` 与框架无关、与时钟无关:`engine.sample(t)` 是时间的纯函数。暂停、恢复、跳转到任意时间点、运行测试,都会产出同一帧画面——这正是冻结状态看板和无需 DOM 的测试套件能够实现的原因。Vue 和 React 适配器([BloubBot.vue](src/components/Core/BloubBot.vue) 和 [BloubBot.tsx](src/components/Core/react/BloubBot.tsx))只是包住这个内核,做的唯一事情就是转发 props 并把帧推送到 DOM。
|
|
229
|
+
|
|
230
|
+
| | |
|
|
231
|
+
|---|---|
|
|
232
|
+
| [docs/architecture.md](docs/architecture.md) | 引擎、径向轮廓形变、作为遮罩孔洞的眼睛 |
|
|
233
|
+
| [docs/measurements.md](docs/measurements.md) | 测量了什么,以及如何重新生成 `profiles.ts` |
|
|
234
|
+
| [docs/intro.md](docs/intro.md) | 入场序列,以及为什么它只播放一个状态 |
|
|
235
|
+
| [docs/interface.md](docs/interface.md) | 布局、三栏场景、CSS 的坑 |
|
|
236
|
+
| [docs/export.md](docs/export.md) | 导出为 SVG、PNG、GIF 和 MP4 |
|
|
237
|
+
| [docs/i18n.md](docs/i18n.md) | 手写的翻译层 |
|
|
238
|
+
|
|
239
|
+
## 变更记录
|
|
240
|
+
|
|
241
|
+
[CHANGELOG.md](CHANGELOG.md),每个版本一条记录——这也是判断你手上的副本是否包含某个修复的方法。
|
|
242
|
+
|
|
243
|
+
## 致谢
|
|
244
|
+
|
|
245
|
+
bloub — 动画引擎与素材([jeremy-prt/bloub](https://github.com/jeremy-prt/bloub),MIT)。
|
|
246
|
+
|
|
247
|
+
## 许可证
|
|
248
|
+
|
|
249
|
+
MIT。见 [LICENSE](LICENSE)。
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c } from "./dom-DPqMRRT2.js";
|
|
2
|
+
import { forwardRef as l, useCallback as u, useEffect as d, useImperativeHandle as f, useRef as p, useSyncExternalStore as m } from "react";
|
|
3
|
+
import { jsx as h } from "react/jsx-runtime";
|
|
4
|
+
//#region src/components/Core/react/BloubBot.tsx
|
|
5
|
+
var g = "#f9f9f9", _ = "Animated bloub avatar", v = e().blocks;
|
|
6
|
+
function y(e) {
|
|
7
|
+
return a.get(e)?.hex ?? "#0a0a0c";
|
|
8
|
+
}
|
|
9
|
+
var b = l(function(e, a) {
|
|
10
|
+
let { size: l = 320, shape: b = t, color: x = s, expression: S = r, paper: C = g, frozenAt: w, cycle: T = v, follow: E = !1, gaze: D = null, ariaLabel: O = _, block: k, state: A, playing: j, onModelChange: M } = e, N = p(null);
|
|
11
|
+
N.current === null && (N.current = new n({
|
|
12
|
+
shape: b,
|
|
13
|
+
expression: S,
|
|
14
|
+
cycle: T,
|
|
15
|
+
follow: E,
|
|
16
|
+
gaze: D,
|
|
17
|
+
frozenAt: w,
|
|
18
|
+
block: k ?? 0,
|
|
19
|
+
state: A ?? "idle",
|
|
20
|
+
playing: j ?? !1,
|
|
21
|
+
elapsed: 0
|
|
22
|
+
}));
|
|
23
|
+
let P = N.current, F = p(C), I = p(y(x)), L = p(null), R = p(M), z = u(() => {
|
|
24
|
+
let e = L.current;
|
|
25
|
+
e && c(e, o(P.frame, {
|
|
26
|
+
paper: F.current,
|
|
27
|
+
ink: I.current,
|
|
28
|
+
uid: P.uid
|
|
29
|
+
}));
|
|
30
|
+
}, [P]), B = u((e) => P.subscribe(e), [P]), V = m(B, () => P.modelSnapshot);
|
|
31
|
+
d(() => {
|
|
32
|
+
let e = P.subscribeFrame(z);
|
|
33
|
+
return z(), e;
|
|
34
|
+
}, [P, z]), d(() => (P.setRectProvider(() => L.current?.getBoundingClientRect() ?? null), P.start(), () => P.stop()), [P]), d(() => {
|
|
35
|
+
F.current = C, z();
|
|
36
|
+
}, [C, z]), d(() => {
|
|
37
|
+
I.current = y(x), z();
|
|
38
|
+
}, [x, z]), d(() => {
|
|
39
|
+
k !== void 0 && k !== P.block && P.setBlock(k);
|
|
40
|
+
}, [k, P]), d(() => {
|
|
41
|
+
A !== void 0 && A !== P.state && P.setState(A);
|
|
42
|
+
}, [A, P]), d(() => {
|
|
43
|
+
j !== void 0 && j !== P.playing && P.setPlaying(j);
|
|
44
|
+
}, [j, P]), d(() => {
|
|
45
|
+
P.setShape(b);
|
|
46
|
+
}, [b, P]), d(() => {
|
|
47
|
+
P.setExpression(S);
|
|
48
|
+
}, [S, P]), d(() => {
|
|
49
|
+
P.setCycle(T);
|
|
50
|
+
}, [T, P]), d(() => {
|
|
51
|
+
P.setFrozenAt(w);
|
|
52
|
+
}, [w, P]), d(() => {
|
|
53
|
+
P.setFollow(E);
|
|
54
|
+
}, [E, P]), d(() => {
|
|
55
|
+
P.setGaze(D);
|
|
56
|
+
}, [D, P]), d(() => {
|
|
57
|
+
R.current = M;
|
|
58
|
+
}, [M]);
|
|
59
|
+
let H = p(0);
|
|
60
|
+
return d(() => {
|
|
61
|
+
H.current !== V && (H.current = V, R.current?.({
|
|
62
|
+
block: P.block,
|
|
63
|
+
state: P.state,
|
|
64
|
+
playing: P.playing,
|
|
65
|
+
elapsed: P.elapsed
|
|
66
|
+
}));
|
|
67
|
+
}, [V, P]), f(a, () => ({
|
|
68
|
+
seek: (e, t = 0) => P.seek(e, t),
|
|
69
|
+
rendAt: (e) => P.rendAt(e)
|
|
70
|
+
}), [P]), /* @__PURE__ */ h("svg", {
|
|
71
|
+
ref: L,
|
|
72
|
+
width: l,
|
|
73
|
+
height: l,
|
|
74
|
+
viewBox: i,
|
|
75
|
+
role: "img",
|
|
76
|
+
"aria-label": O
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
//#endregion
|
|
80
|
+
export { b as BloubBot };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { a as e, c as t, i as n, l as r, n as i, o as a, r as o, s, t as c } from "./dom-DPqMRRT2.js";
|
|
2
|
+
import { computed as l, createElementBlock as u, defineComponent as d, mergeModels as f, onBeforeUnmount as p, onMounted as m, openBlock as h, ref as g, unref as _, useModel as v, watch as y } from "vue";
|
|
3
|
+
//#region src/components/Core/BloubBot.vue?vue&type=script&setup=true&lang.ts
|
|
4
|
+
var b = [
|
|
5
|
+
"width",
|
|
6
|
+
"height",
|
|
7
|
+
"viewBox",
|
|
8
|
+
"aria-label"
|
|
9
|
+
], x = /* @__PURE__ */ d({
|
|
10
|
+
__name: "BloubBot",
|
|
11
|
+
props: /*@__PURE__*/ f({
|
|
12
|
+
size: { default: 320 },
|
|
13
|
+
shape: { default: t },
|
|
14
|
+
color: { default: s },
|
|
15
|
+
expression: { default: r },
|
|
16
|
+
paper: { default: "#f9f9f9" },
|
|
17
|
+
frozenAt: { default: void 0 },
|
|
18
|
+
cycle: { default: () => e().blocks },
|
|
19
|
+
follow: {
|
|
20
|
+
type: Boolean,
|
|
21
|
+
default: !1
|
|
22
|
+
},
|
|
23
|
+
gaze: {
|
|
24
|
+
type: [Function, null],
|
|
25
|
+
default: null
|
|
26
|
+
},
|
|
27
|
+
ariaLabel: { default: "Animated bloub avatar" }
|
|
28
|
+
}, {
|
|
29
|
+
block: { default: 0 },
|
|
30
|
+
blockModifiers: {},
|
|
31
|
+
state: { default: "idle" },
|
|
32
|
+
stateModifiers: {},
|
|
33
|
+
playing: {
|
|
34
|
+
type: Boolean,
|
|
35
|
+
default: !1
|
|
36
|
+
},
|
|
37
|
+
playingModifiers: {},
|
|
38
|
+
elapsed: { default: 0 },
|
|
39
|
+
elapsedModifiers: {}
|
|
40
|
+
}),
|
|
41
|
+
emits: [
|
|
42
|
+
"update:block",
|
|
43
|
+
"update:state",
|
|
44
|
+
"update:playing",
|
|
45
|
+
"update:elapsed"
|
|
46
|
+
],
|
|
47
|
+
setup(e, { expose: t }) {
|
|
48
|
+
let r = e, s = v(e, "block"), d = v(e, "state"), f = v(e, "playing"), x = v(e, "elapsed"), S = l(() => a.get(r.color)?.hex ?? "#0a0a0c"), C = new n({
|
|
49
|
+
shape: r.shape,
|
|
50
|
+
expression: r.expression,
|
|
51
|
+
cycle: r.cycle,
|
|
52
|
+
follow: r.follow,
|
|
53
|
+
gaze: r.gaze,
|
|
54
|
+
frozenAt: r.frozenAt,
|
|
55
|
+
block: s.value,
|
|
56
|
+
state: d.value,
|
|
57
|
+
playing: f.value,
|
|
58
|
+
elapsed: x.value
|
|
59
|
+
}), w = g(null);
|
|
60
|
+
function T() {
|
|
61
|
+
let e = w.value;
|
|
62
|
+
e && c(e, o(C.frame, {
|
|
63
|
+
paper: r.paper,
|
|
64
|
+
ink: S.value,
|
|
65
|
+
uid: C.uid
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
let E = !1;
|
|
69
|
+
return C.subscribe(() => {
|
|
70
|
+
E = !0, s.value = C.block, d.value = C.state, f.value = C.playing, x.value = C.elapsed, E = !1, T();
|
|
71
|
+
}), y(s, (e) => {
|
|
72
|
+
E || C.setBlock(e);
|
|
73
|
+
}, { flush: "sync" }), y(d, (e) => {
|
|
74
|
+
E || C.setState(e);
|
|
75
|
+
}, { flush: "sync" }), y(f, (e) => {
|
|
76
|
+
E || C.setPlaying(e);
|
|
77
|
+
}, { flush: "sync" }), y(() => r.shape, (e) => C.setShape(e)), y(() => r.expression, (e) => C.setExpression(e)), y(() => r.cycle, (e) => C.setCycle(e)), y(() => r.frozenAt, (e) => C.setFrozenAt(e)), y(() => r.follow, (e) => C.setFollow(e)), y(() => r.gaze, (e) => C.setGaze(e)), y([() => r.paper, S], () => T()), t({
|
|
78
|
+
seek: (e, t = 0) => C.seek(e, t),
|
|
79
|
+
rendAt: (e) => C.rendAt(e)
|
|
80
|
+
}), m(() => {
|
|
81
|
+
C.setRectProvider(() => w.value?.getBoundingClientRect() ?? null), T(), C.start();
|
|
82
|
+
}), p(() => {
|
|
83
|
+
C.stop();
|
|
84
|
+
}), (e, t) => (h(), u("svg", {
|
|
85
|
+
ref_key: "svg",
|
|
86
|
+
ref: w,
|
|
87
|
+
width: r.size,
|
|
88
|
+
height: r.size,
|
|
89
|
+
viewBox: _(i),
|
|
90
|
+
role: "img",
|
|
91
|
+
"aria-label": r.ariaLabel
|
|
92
|
+
}, null, 8, b));
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
//#endregion
|
|
96
|
+
export { x as BloubBot };
|