171 lines
4.4 KiB
TypeScript
171 lines
4.4 KiB
TypeScript
import anime from 'animejs/lib/anime.es';
|
|
import store from '../store';
|
|
|
|
export enum CompanionState {
|
|
IDLE = 'IDLE',
|
|
AWAIT = 'AWAIT',
|
|
ACTIVE = 'ACTIVE',
|
|
SUCCESS = 'SUCCESS',
|
|
}
|
|
|
|
export interface CompanionMessage {
|
|
text: string;
|
|
inputWanted: boolean;
|
|
timestamp: number;
|
|
}
|
|
|
|
export class Companion {
|
|
ref: HTMLElement;
|
|
messageRef: HTMLElement;
|
|
messageTextRef: HTMLElement;
|
|
messageInputRef: HTMLElement;
|
|
messageButtonRef: HTMLElement;
|
|
hoverAnimation: any;
|
|
message: CompanionMessage;
|
|
|
|
constructor() {
|
|
this.ref = document.getElementById('companion');
|
|
this.messageRef = document.getElementById('message');
|
|
this.messageTextRef = document.getElementById('message-text');
|
|
this.messageInputRef = document.getElementById('message-input');
|
|
this.messageButtonRef = document.getElementById('message-confirm');
|
|
|
|
this.ref.addEventListener('click', () => this.handleClick());
|
|
this.ref.addEventListener('mouseover', () => this.handleMouseEnter());
|
|
this.ref.addEventListener('mouseleave', () => this.handleMouseLeave());
|
|
|
|
this.pupilFollowCursor();
|
|
|
|
store.subscribe(
|
|
(companionState) => {
|
|
if (companionState === CompanionState.ACTIVE) {
|
|
this.showActiveShape();
|
|
this.scaleUpCompanion();
|
|
} else if (companionState === CompanionState.IDLE) {
|
|
this.scaleDownCompanion();
|
|
this.showIdleShape();
|
|
} else if (companionState === CompanionState.AWAIT) {
|
|
this.playAwaitAnimation();
|
|
} else if (companionState === CompanionState.SUCCESS) {
|
|
this.playSuccessAnimation();
|
|
this.scaleDownCompanion();
|
|
this.showIdleShape();
|
|
}
|
|
},
|
|
(state) => state.companionState
|
|
);
|
|
|
|
store.subscribe(
|
|
(messages: CompanionMessage[], prevMessages: CompanionMessage[]) => {
|
|
if (prevMessages.length !== messages.length) {
|
|
const newMessage = messages[messages.length - 1];
|
|
this.showMessage(newMessage);
|
|
}
|
|
},
|
|
(state) => state.userMessages
|
|
);
|
|
}
|
|
|
|
showMessage(message: CompanionMessage) {
|
|
this.messageTextRef.innerText = message.text;
|
|
|
|
if (message.inputWanted) {
|
|
this.messageInputRef.style.display = 'block';
|
|
|
|
// TODO: Get text from textarea and log it on confirm, hide button after click on confirm as well
|
|
} else {
|
|
this.messageInputRef.style.display = 'none';
|
|
|
|
// TODO: Hide message on confirm click, log time looked at message
|
|
}
|
|
}
|
|
|
|
showActiveShape() {
|
|
const mouth = document.getElementById('companion-mouth');
|
|
mouth.style.opacity = '100%';
|
|
|
|
anime({
|
|
targets: '#companion-shape polygon',
|
|
points: [{ value: '40,0 68,12 80,40 80,75 40,80 0,75 0,40 12,12' }],
|
|
easing: 'easeOutQuad',
|
|
duration: 500,
|
|
loop: false,
|
|
});
|
|
}
|
|
|
|
showIdleShape() {
|
|
const mouth = document.getElementById('companion-mouth');
|
|
mouth.style.opacity = '0%';
|
|
|
|
anime({
|
|
targets: '#companion-shape polygon',
|
|
points: [{ value: '40,0 68,12 80,40 68,68 40,80 12,68 0,40 12,12' }],
|
|
easing: 'easeOutQuad',
|
|
duration: 500,
|
|
loop: false,
|
|
});
|
|
}
|
|
|
|
scaleUpCompanion() {
|
|
anime({
|
|
targets: '#companion',
|
|
scale: 3,
|
|
translateX: -15,
|
|
translateY: -12,
|
|
loop: false,
|
|
duration: 700,
|
|
});
|
|
}
|
|
|
|
scaleDownCompanion() {
|
|
anime({
|
|
targets: '#companion',
|
|
scale: 1,
|
|
loop: false,
|
|
translateX: 0,
|
|
translateY: 0,
|
|
duration: 800,
|
|
});
|
|
}
|
|
|
|
playSuccessAnimation() {}
|
|
|
|
playAwaitAnimation() {}
|
|
|
|
pupilFollowCursor() {
|
|
document.addEventListener('mousemove', (e) => {
|
|
const pupil = document.getElementById('companion-pupil');
|
|
const pupDim = pupil.getBoundingClientRect();
|
|
|
|
pupil.style.transform = `translate(${-((pupDim.x - e.pageX) * 0.005)}px, ${-(
|
|
(pupDim.y - e.pageY) *
|
|
0.012
|
|
)}px)`;
|
|
});
|
|
}
|
|
|
|
handleClick() {
|
|
const { companionState } = store.getState();
|
|
let newCompanionState: CompanionState;
|
|
|
|
if (companionState === CompanionState.ACTIVE) {
|
|
newCompanionState = CompanionState.IDLE;
|
|
} else {
|
|
newCompanionState = CompanionState.ACTIVE;
|
|
}
|
|
|
|
store.setState({ companionState: newCompanionState });
|
|
}
|
|
|
|
handleMouseEnter() {
|
|
this.showActiveShape();
|
|
}
|
|
|
|
handleMouseLeave() {
|
|
const { companionState } = store.getState();
|
|
|
|
if (companionState !== CompanionState.ACTIVE) {
|
|
this.showIdleShape();
|
|
}
|
|
}
|
|
}
|