Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,7 @@ application-local.properties
/admin-frontend/node_modules/
/workplace/

/node_modules/
/node_modules/

### TypeScript
*.tsbuildinfo
14 changes: 11 additions & 3 deletions packages/live2d/src/components/Live2dChatWindow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
configContext,
} from "@/live2d/context/config-context";
import { sendMessage } from "@/live2d/helpers/sendMessage";
import { DraggableMixin } from "@/live2d/mixins/draggable";
import { consume } from "@lit/context";
import { type PropertyValues, type TemplateResult, html } from "lit";
import { property, query, state } from "lit/decorators.js";
Expand All @@ -14,6 +15,12 @@ const CHAT_PANEL_WIDTH = "min(26rem, calc(100vw - 1rem))";
const CHAT_PANEL_BOTTOM = "2rem";
const CHAT_PANEL_TRANSITION_MS = 220;

const DraggableUnoLitElement = DraggableMixin(UnoLitElement, {
storageKey: "chat-window",
targetSelector: "#live2d-chat-model",
clearTransformOnPosition: true,
});

type PopoverCapableElement = HTMLDivElement & {
hidePopover: () => void;
showPopover: () => void;
Expand All @@ -27,7 +34,7 @@ const isPopoverCapable = (
"function" &&
typeof (element as Partial<PopoverCapableElement>).hidePopover === "function";

export class Live2dChatWindow extends UnoLitElement {
export class Live2dChatWindow extends DraggableUnoLitElement {
@consume({ context: configContext })
@property({ attribute: false })
public config?: Live2dConfig;
Expand All @@ -53,6 +60,8 @@ export class Live2dChatWindow extends UnoLitElement {

connectedCallback(): void {
super.connectedCallback();
// 应用保存的位置
this.applySavedPosition();
window.addEventListener("live2d:toggle-chat-window", this.handleToggle);
}

Expand All @@ -63,7 +72,6 @@ export class Live2dChatWindow extends UnoLitElement {
}

render(): TemplateResult {
const positionStyle = `inset: auto auto ${CHAT_PANEL_BOTTOM} 50%; margin: 0; width: ${CHAT_PANEL_WIDTH}; transform: translateX(-50%); transition: opacity ${CHAT_PANEL_TRANSITION_MS}ms ease;`;
const panelClasses = [
"fixed z-[10000] overflow-hidden rounded-full border border-[#eadfce] bg-[#fffaf4]/96 shadow-[0_10px_24px_rgba(15,23,42,0.08)] backdrop-blur-sm will-change-[opacity]",
this._isShow
Expand All @@ -80,7 +88,7 @@ export class Live2dChatWindow extends UnoLitElement {
id="live2d-chat-model"
popover="manual"
class=${panelClasses}
style=${positionStyle}
style="inset: auto auto ${CHAT_PANEL_BOTTOM} 50%; margin: 0; width: ${CHAT_PANEL_WIDTH}; transform: translateX(-50%); transition: opacity ${CHAT_PANEL_TRANSITION_MS}ms ease;"
>
<div class="flex items-center gap-1.5 px-1 py-1">
<input
Expand Down
45 changes: 34 additions & 11 deletions packages/live2d/src/components/Live2dToggle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ import {
readWidgetSuppression,
rememberWidgetDismissal,
} from "@/live2d/helpers/widgetVisibility";
import { DraggableMixin } from "@/live2d/mixins/draggable";
import { consume } from "@lit/context";
import { type TemplateResult, html } from "lit";
import { property } from "lit/decorators.js";
import { state } from "lit/decorators.js";
import "iconify-icon";

export class Live2dToggle extends UnoLitElement {
const DraggableUnoLitElement = DraggableMixin(UnoLitElement, {
storageKey: "toggle",
targetSelector: "#live2d-toggle",
});

export class Live2dToggle extends DraggableUnoLitElement {
@consume({ context: configContext })
@property({ attribute: false })
public config?: Live2dConfig;
Expand All @@ -28,6 +35,8 @@ export class Live2dToggle extends UnoLitElement {

connectedCallback(): void {
super.connectedCallback();
// 应用保存的位置
this.applySavedPosition();
this.addEventListener("click", this.handleClick);
window.addEventListener("live2d:toggle-canvas", this.handleGlobalToggle);

Expand All @@ -52,25 +61,39 @@ export class Live2dToggle extends UnoLitElement {

render(): TemplateResult {
const positionClass =
this.config?.live2dLocation === "right" ? "right-0" : "left-0";
this.config?.live2dLocation === "right" ? "right-4" : "left-4";
const visibilityClass = this._isShow
? "pointer-events-auto opacity-100 scale-100"
: "pointer-events-none opacity-0 scale-90";
return html`<div
class="fixed bottom-16 ${positionClass} rounded-md bg-[#fa0] color-white cursor-pointer text-3 py-1.5 px-0.5 writing-vertical-rl ${
this.config?.live2dLocation === "right" ? "-mr-1" : "-ml-1"
} w-9 hover:transform-translate-x-0
${
this._isShow
? "-transform-translate-x-4"
: "-transform-translate-x-full"
} transition-transform duration-1000"
id="live2d-toggle"
class="fixed bottom-16 ${positionClass} z-9999 inline-flex cursor-grab select-none items-center gap-1.5 rounded-full border border-[#f3d7b8] bg-[#fffaf4]/96 px-2.5 py-1.5 text-3.25 color-[#8b5e34] shadow-[0_8px_22px_rgba(139,94,52,0.18)] backdrop-blur-sm transition-[opacity,transform,box-shadow] duration-300 hover:scale-105 hover:shadow-[0_10px_26px_rgba(139,94,52,0.24)] active:cursor-grabbing ${visibilityClass}"
role="button"
tabindex="0"
aria-label="打开看板娘"
@keydown=${this.handleKeydown}
>
看板娘
<span
class="inline-flex h-7 w-7 items-center justify-center rounded-full bg-[#ffb86c] color-white shadow-[inset_0_-2px_0_rgba(139,94,52,0.14)]"
>
<iconify-icon icon="ph:cat" width="18" height="18"></iconify-icon>
</span>
<span class="whitespace-nowrap font-500 leading-none">看板娘</span>
</div>`;
}

handleClick() {
this.dispatchEvent(new ToggleCanvasEvent({ isShow: this._isShow }));
}

handleKeydown = (e: KeyboardEvent) => {
if (e.key !== "Enter" && e.key !== " ") {
return;
}
e.preventDefault();
this.handleClick();
};

handleGlobalToggle = (e: Event) => {
const event = e as ToggleCanvasEvent;
if (event.detail.isShow) {
Expand Down
77 changes: 62 additions & 15 deletions packages/live2d/src/components/Live2dWidget.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,16 @@ import "@/live2d/components/Live2dChatWindow";
import type { ToggleCanvasEvent } from "@/live2d/events/toggle-canvas";
import {
WIDGET_DRAWER_DURATION_MS,
WIDGET_DRAWER_HIDDEN_BOTTOM,
WIDGET_DRAWER_VISIBLE_BOTTOM,
} from "@/live2d/helpers/widgetDrawer";
import { DraggableMixin } from "@/live2d/mixins/draggable";

export class Live2dWidget extends UnoLitElement {
const DraggableUnoLitElement = DraggableMixin(UnoLitElement, {
storageKey: "widget",
targetSelector: "#live2d-plugin",
});

export class Live2dWidget extends DraggableUnoLitElement {
@consume({ context: configContext })
@property({ attribute: false })
public config?: Live2dConfig;
Expand All @@ -29,7 +34,11 @@ export class Live2dWidget extends UnoLitElement {
@state()
private _hasMountedWidget = false;

@state()
private _isDrawerAnimating = false;

private showAnimationFrameId?: number;
private drawerAnimationTimer?: number;

render(): TemplateResult {
return html`
Expand All @@ -49,6 +58,16 @@ export class Live2dWidget extends UnoLitElement {
}
}

renderLive2dTips() {
if (!this._isShow && !this._isDrawerAnimating) {
return;
}

return html`<live2d-tips
class="pointer-events-none absolute left-1/2 -translate-x-1/2"
></live2d-tips>`;
}

renderLive2dWidget() {
if (!this._hasMountedWidget) {
return;
Expand All @@ -61,24 +80,29 @@ export class Live2dWidget extends UnoLitElement {
const visibilityClass = this._isShow
? "pointer-events-auto"
: "pointer-events-none";
const bottom = this._isShow
? WIDGET_DRAWER_VISIBLE_BOTTOM
: WIDGET_DRAWER_HIDDEN_BOTTOM;
const shouldClipDrawer = !this._isShow || this._isDrawerAnimating;
const drawerBoundaryStyle = shouldClipDrawer
? `bottom: ${WIDGET_DRAWER_VISIBLE_BOTTOM}; clip-path: inset(-100vh -100vw 0 -100vw);`
: `bottom: ${WIDGET_DRAWER_VISIBLE_BOTTOM};`;
const drawerClass = this._isShow ? "translate-y-0" : "translate-y-full";
return html`<div
id="live2d-plugin"
class="fixed z-9998 inline-block linear transition-[bottom] ${positionClass} ${visibilityClass}"
style="bottom: ${bottom}; transition-duration: ${WIDGET_DRAWER_DURATION_MS}ms;"
class="fixed z-9998 inline-block ${positionClass} ${visibilityClass}"
style=${drawerBoundaryStyle}
>
<div
class="group flex flex-col items-center relative translate-y-1 transition-transform duration-300 hover:translate-y-0"
class="linear transition-transform ${drawerClass}"
style="transition-duration: ${WIDGET_DRAWER_DURATION_MS}ms;"
>
<live2d-tips
class="pointer-events-none absolute left-1/2 -translate-x-1/2"
></live2d-tips>
<live2d-canvas
class="inline-block h-[300px] w-[300px] z-1"
></live2d-canvas>
${this.renderLive2dTools()}
<div
class="group flex flex-col items-center relative translate-y-1 transition-transform duration-300 hover:translate-y-0"
>
${this.renderLive2dTips()}
<live2d-canvas
class="inline-block h-[300px] w-[300px] z-1"
></live2d-canvas>
${this.renderLive2dTools()}
</div>
</div>
</div>`;
}
Expand All @@ -92,6 +116,7 @@ export class Live2dWidget extends UnoLitElement {
}

this.cancelScheduledShow();
this.startDrawerAnimation();
this._isShow = e.detail.isShow;
this.requestUpdate();
};
Expand All @@ -108,6 +133,8 @@ export class Live2dWidget extends UnoLitElement {

connectedCallback(): void {
super.connectedCallback();
// 应用保存的位置
this.applySavedPosition();
// 页面加载时清除历史消息
// 对应原始代码中的 window.onload
window.addEventListener("load", this.clearChatHistory);
Expand All @@ -121,6 +148,7 @@ export class Live2dWidget extends UnoLitElement {
disconnectedCallback(): void {
super.disconnectedCallback();
this.cancelScheduledShow();
this.cancelDrawerAnimation();
window.removeEventListener("load", this.clearChatHistory);
window.removeEventListener(
"live2d:toggle-canvas",
Expand All @@ -139,6 +167,7 @@ export class Live2dWidget extends UnoLitElement {
this.cancelScheduledShow();
this.showAnimationFrameId = window.requestAnimationFrame(() => {
this.showAnimationFrameId = undefined;
this.startDrawerAnimation();
this._isShow = true;
this.requestUpdate();
});
Expand All @@ -152,6 +181,24 @@ export class Live2dWidget extends UnoLitElement {
window.cancelAnimationFrame(this.showAnimationFrameId);
this.showAnimationFrameId = undefined;
}

private startDrawerAnimation(): void {
this.cancelDrawerAnimation();
this._isDrawerAnimating = true;
this.drawerAnimationTimer = window.setTimeout(() => {
this.drawerAnimationTimer = undefined;
this._isDrawerAnimating = false;
}, WIDGET_DRAWER_DURATION_MS);
}

private cancelDrawerAnimation(): void {
if (this.drawerAnimationTimer === undefined) {
return;
}

window.clearTimeout(this.drawerAnimationTimer);
this.drawerAnimationTimer = undefined;
}
}

customElements.define("live2d-widget", Live2dWidget);
Loading