dsh-scroll-piano 0.3.0 → 0.3.1
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 +2 -2
- package/lib/client.js +22 -23
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,12 +6,12 @@
|
|
|
6
6
|
|
|
7
7
|
- 聊天区右边缘一条细彩色竖条(开启时淡淡发光),鼠标进入该区间即触发。
|
|
8
8
|
- 鼠标纵向移动 = 连续刮奏;竖条上有个白点跟随鼠标位置。
|
|
9
|
-
-
|
|
9
|
+
- **气泡只在鼠标正落在「你发的某条消息」上时出现**,内容就是那条消息的正文(最多 3 行)+「点击跳转」提示;**其余位置整个不显示**(不显示音名、不显示 MIDI 数字)。
|
|
10
10
|
- **点击气泡** → 平滑滚动,把那条消息滚到视口中央。
|
|
11
11
|
- 左下角一枚 `🎵 滚动钢琴 ON/OFF` 胶囊:点开关切换、右侧滑块调音量(设置存 `localStorage`)。
|
|
12
12
|
- 默认音域 C3–C6(3 个八度),只按半音触发(同音不重复触发)。
|
|
13
13
|
|
|
14
|
-
> 预览**只显示用户自己发的消息**,且**不做「取最近一条」兜底**:滑到 AI 回复 / 工具调用 /
|
|
14
|
+
> 预览**只显示用户自己发的消息**,且**不做「取最近一条」兜底**:滑到 AI 回复 / 工具调用 / 过程块上时气泡整个收起,避免同一条消息在整片区域里被反复显示。声音与预览**彼此独立**——任何位置滑动都照常出声,只有预览挑消息。
|
|
15
15
|
|
|
16
16
|
## 消息预览怎么定位
|
|
17
17
|
|
package/lib/client.js
CHANGED
|
@@ -37,7 +37,7 @@ window.__ModuleLoader__.load({
|
|
|
37
37
|
".dshscp_prevTop{display:flex;align-items:center;gap:8px;margin-bottom:4px}",
|
|
38
38
|
".dshscp_prevRole{flex:none;padding:1px 5px;border-radius:4px;background:rgba(91,141,239,.3);color:#bcd4ff;font-size:10px;line-height:14px}",
|
|
39
39
|
".dshscp_prevRoleAI{background:rgba(160,120,240,.28);color:#d6c4ff}",
|
|
40
|
-
".
|
|
40
|
+
".dshscp_prevHint{margin-left:auto;flex:none;color:#9aa2b1;font-size:10px;line-height:14px}",
|
|
41
41
|
".dshscp_prevText{font-size:12px;line-height:17px;color:#e6e8ee;overflow:hidden;display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;word-break:break-word}"
|
|
42
42
|
].join("");
|
|
43
43
|
const tagId = "dsh-scroll-piano/scroll-piano.css";
|
|
@@ -150,12 +150,7 @@ window.__ModuleLoader__.load({
|
|
|
150
150
|
noise.start(now);
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
//#region
|
|
154
|
-
const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
|
|
155
|
-
function noteLabel(midi) {
|
|
156
|
-
return NOTE_NAMES[((midi % 12) + 12) % 12] + (Math.floor(midi / 12) - 1);
|
|
157
|
-
}
|
|
158
|
-
|
|
153
|
+
//#region chat anchors
|
|
159
154
|
/** 聊天区的滚动容器(从 `[data-chat-flow]` 往上找第一个可纵向滚动的祖先)。 */
|
|
160
155
|
function findChatScroller() {
|
|
161
156
|
const flow = document.querySelector("[data-chat-flow]");
|
|
@@ -223,7 +218,6 @@ window.__ModuleLoader__.load({
|
|
|
223
218
|
//#region component
|
|
224
219
|
function ScrollPiano() {
|
|
225
220
|
const [settings, setSettings] = useState(loadSettings);
|
|
226
|
-
const [label, setLabel] = useState("");
|
|
227
221
|
const settingsRef = useRef(settings);
|
|
228
222
|
settingsRef.current = settings;
|
|
229
223
|
const lastMidiRef = useRef(null);
|
|
@@ -232,7 +226,6 @@ window.__ModuleLoader__.load({
|
|
|
232
226
|
const markerRef = useRef(null);
|
|
233
227
|
const prevRef = useRef(null);
|
|
234
228
|
const roleRef = useRef(null);
|
|
235
|
-
const noteRef = useRef(null);
|
|
236
229
|
const textRef = useRef(null);
|
|
237
230
|
const bubbleHoverRef = useRef(false);
|
|
238
231
|
const activeMsgRef = useRef(null);
|
|
@@ -288,20 +281,28 @@ window.__ModuleLoader__.load({
|
|
|
288
281
|
const m = markerRef.current;
|
|
289
282
|
if (m) m.style.top = Math.round(y - 3.5) + "px";
|
|
290
283
|
};
|
|
291
|
-
|
|
284
|
+
/**
|
|
285
|
+
* 气泡**只在鼠标压在某条「用户消息」上时出现**,内容只有那条消息的正文。
|
|
286
|
+
* 其余位置(AI 回复 / 工具块 / 空白)整个收起——不放音名、不放 MIDI 数字。
|
|
287
|
+
*/
|
|
288
|
+
const showPreview = (y, msg) => {
|
|
292
289
|
const el = prevRef.current;
|
|
293
290
|
if (!el) return;
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
291
|
+
if (!msg) {
|
|
292
|
+
activeMsgRef.current = null;
|
|
293
|
+
el.classList.remove("dshscp_prevOn");
|
|
294
|
+
el.style.pointerEvents = "none";
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
activeMsgRef.current = msg;
|
|
298
|
+
if (textRef.current) textRef.current.textContent = msg.text;
|
|
299
|
+
el.style.pointerEvents = "auto";
|
|
300
|
+
el.style.cursor = "pointer";
|
|
301
301
|
// 竖直居中于鼠标,并夹在视口内
|
|
302
302
|
const h = el.offsetHeight || 40;
|
|
303
303
|
const top = Math.max(8, Math.min(window.innerHeight - h - 8, y - h / 2));
|
|
304
304
|
el.style.top = Math.round(top) + "px";
|
|
305
|
+
el.classList.add("dshscp_prevOn");
|
|
305
306
|
};
|
|
306
307
|
const hidePreview = () => {
|
|
307
308
|
activeMsgRef.current = null;
|
|
@@ -329,9 +330,9 @@ window.__ModuleLoader__.load({
|
|
|
329
330
|
// t=0(顶部)→ highNote;t=1(底部)→ lowNote
|
|
330
331
|
const midi = Math.round(s.highNote - t * (s.highNote - s.lowNote));
|
|
331
332
|
showMarker(e.clientY);
|
|
332
|
-
|
|
333
|
+
// 预览只看用户消息;没有就整个收起(不显示音名/MIDI)
|
|
334
|
+
showPreview(e.clientY, userMessageAtY(e.clientY));
|
|
333
335
|
if (markerRef.current) markerRef.current.classList.add("dshscp_markerOn");
|
|
334
|
-
if (prevRef.current) prevRef.current.classList.add("dshscp_prevOn");
|
|
335
336
|
if (midi !== lastMidiRef.current) {
|
|
336
337
|
lastMidiRef.current = midi;
|
|
337
338
|
// 限速:滑太快时不要瞬间堆几十个音(会糊成一片杂音)
|
|
@@ -340,7 +341,6 @@ window.__ModuleLoader__.load({
|
|
|
340
341
|
lastPlayRef.current = nowMs;
|
|
341
342
|
playNote(midi, s.volume);
|
|
342
343
|
}
|
|
343
|
-
setLabel(noteLabel(midi));
|
|
344
344
|
}
|
|
345
345
|
};
|
|
346
346
|
const onLeave = () => {
|
|
@@ -390,7 +390,7 @@ window.__ModuleLoader__.load({
|
|
|
390
390
|
},
|
|
391
391
|
h("div", { className: "dshscp_prevTop" },
|
|
392
392
|
h("span", { ref: roleRef, className: "dshscp_prevRole" }, "你"),
|
|
393
|
-
h("span", {
|
|
393
|
+
h("span", { className: "dshscp_prevHint" }, "点击跳转")
|
|
394
394
|
),
|
|
395
395
|
h("div", { ref: textRef, className: "dshscp_prevText" })
|
|
396
396
|
),
|
|
@@ -411,8 +411,7 @@ window.__ModuleLoader__.load({
|
|
|
411
411
|
value: settings.volume,
|
|
412
412
|
onChange: (e) => setVolume(parseFloat(e.target.value)),
|
|
413
413
|
title: "音量"
|
|
414
|
-
})
|
|
415
|
-
label ? h("span", { style: { opacity: 0.75, minWidth: "26px" } }, label) : null
|
|
414
|
+
})
|
|
416
415
|
)
|
|
417
416
|
);
|
|
418
417
|
}
|