app-walkthrough
Version:
An intuitive guided walkthrough library with UI highlighting and voice narration for web apps.
81 lines (80 loc) • 2.75 kB
JavaScript
import { debounce } from "es-toolkit";
import { addRecordingButton, recordEvent } from "./utils";
import { RECORDABLE_EVENT_TYPES, RECORDER_UI_CONTAINER_CLASS, } from "./constants";
export class ActionRecorder {
reset() {
if (this.state.isRecording) {
this.stopRecording();
}
}
constructor() {
const wasRecording = localStorage.getItem("isRecording") === "true";
this.state = {
isInitialized: false,
isRecording: wasRecording,
events: [],
recordingStartTime: 0,
pageId: window.location.pathname,
};
this.uiContainer = undefined;
const boundRecordEvent = this._recordEvent.bind(this);
this.recordEvent = debounce(boundRecordEvent, 500);
this.startRecording = this.startRecording.bind(this);
this.stopRecording = this.stopRecording.bind(this);
this.updateUI = this.updateUI.bind(this);
}
static getInstance() {
if (!this.instance) {
this.instance = new ActionRecorder();
this.instance.init();
}
return this.instance;
}
getRecordingState() {
return this.state.isRecording;
}
_setRecordingState(isRecording) {
this.state.isRecording = isRecording;
localStorage.setItem("isRecording", isRecording ? "true" : "false");
}
_recordEvent(e) {
recordEvent(e, this.state);
}
startRecording() {
if (this.state.isRecording)
return;
this.state.isRecording = true;
this.state.events = [];
this.state.recordingStartTime = performance.now();
RECORDABLE_EVENT_TYPES.forEach((type) => {
document.addEventListener(type, this.recordEvent, { capture: true });
});
this._setRecordingState(true);
this.updateUI();
}
stopRecording() {
this._setRecordingState(false);
RECORDABLE_EVENT_TYPES.forEach((type) => document.removeEventListener(type, this.recordEvent, { capture: true }));
if (this.uiContainer) {
this.uiContainer.innerHTML = "";
}
}
getEvents() {
return this.state.events;
}
updateUI() {
if (!this.uiContainer) {
this.uiContainer = document.createElement("div");
this.uiContainer.id = "recorder-ui-container";
this.uiContainer.className = RECORDER_UI_CONTAINER_CLASS;
document.body.appendChild(this.uiContainer);
}
addRecordingButton(this.uiContainer, this.state, this.stopRecording);
}
init() {
if (this.state.isInitialized)
return;
this.state.isInitialized = true;
this.updateUI();
}
}