import { ActionType as LrcActionType, useLrc } from "../hooks/useLrc.js";
import { ThemeMode } from "../hooks/usePref.js";
import { convertTimeToTag, stringify } from "../lrc-parser.js";
import { AudioActionType, audioStatePubSub } from "../utils/audiomodule.js";
import { appContext, ChangBits } from "./app.context.js";
import { Home } from "./home.js";
import { AkariNotFound, AkariOangoLoading } from "./svg.img.js";
const { lazy, useContext, useEffect, useRef, useState } = React;
const LazyEditor = lazy(() =>
import(/* webpackMode: "eager" */ "./editor.js").then(({ Eidtor }) => {
return { default: Eidtor };
}),
);
const LazySynchronizer = lazy(() =>
import(/* webpackMode: "eager" */ "./synchronizer.js").then(({ Synchronizer }) => {
return { default: Synchronizer };
}),
);
const LazyGist = lazy(() =>
import(/* webpackMode: "eager" */ "./gist.js").then(({ Gist }) => {
return { default: Gist };
}),
);
const LazyPreferences = lazy(() =>
import(/* webpackMode: "eager" */ "./preferences.js").then(({ Preferences }) => {
return { default: Preferences };
}),
);
export const Content: React.FC = () => {
const self = useRef(Symbol(Content.name));
const { prefState, trimOptions } = useContext(appContext, ChangBits.prefState);
const [path, setPath] = useState(location.hash);
useEffect(() => {
window.addEventListener("hashchange", () => {
setPath(location.hash);
});
}, []);
const [lrcState, lrcDispatch] = useLrc(() => {
return {
text: localStorage.getItem(LSK.lyric) || Const.emptyString,
options: trimOptions,
select: Number.parseInt(sessionStorage.getItem(SSK.selectIndex)!, 10) || 0,
};
});
useEffect(() => {
return audioStatePubSub.sub(self.current, (data) => {
if (data.type === AudioActionType.getDuration) {
lrcDispatch({
type: LrcActionType.info,
payload: {
name: "length",
value: convertTimeToTag(data.payload, prefState.fixed, false),
},
});
}
});
}, [lrcDispatch, prefState.fixed]);
useEffect(() => {
const saveState = (): void => {
lrcDispatch({
type: LrcActionType.getState,
payload: (lrc) => {
localStorage.setItem(LSK.lyric, stringify(lrc, prefState));
sessionStorage.setItem(SSK.selectIndex, lrc.selectIndex.toString());
},
});
localStorage.setItem(LSK.preferences, JSON.stringify(prefState));
};
document.addEventListener("visibilitychange", () => {
if (document.hidden) {
saveState();
}
});
window.addEventListener("beforeunload", () => {
saveState();
});
}, [lrcDispatch, prefState]);
useEffect(() => {
document.documentElement.addEventListener("drop", (ev) => {
const file = ev.dataTransfer?.files[0];
if (file && (file.type.startsWith("text/") || /(?:\.lrc|\.txt)$/i.test(file.name))) {
const fileReader = new FileReader();
const onload = (): void => {
lrcDispatch({
type: LrcActionType.parse,
payload: { text: fileReader.result as string, options: trimOptions },
});
};
fileReader.addEventListener("load", onload, {
once: true,
});
location.hash = Path.editor;
fileReader.readAsText(file, "utf-8");
}
});
}, [lrcDispatch, trimOptions]);
useEffect(() => {
const values = {
[ThemeMode.auto]: "auto",
[ThemeMode.light]: "light",
[ThemeMode.dark]: "dark",
} as const;
document.documentElement.dataset.theme = values[prefState.themeMode];
}, [prefState.themeMode]);
useEffect(() => {
const rgb = hex2rgb(prefState.themeColor);
document.documentElement.style.setProperty("--theme-rgb", rgb.join(", "));
// https://www.w3.org/TR/WCAG20/#contrast-ratiodef
// const contrast = (rgb1, rgb2) => {
// const c1 = luminanace(...rgb1) + 0.05;
// const c2 = luminanace(...rgb2) + 0.05;
// return c1 > c2 ? c1 / c2 : c2 / c1;
// };
// c: color ; b: black; w: white;
// if we need black text
//
// (lum(c) + 0.05) / (l(b) + 0.05) > (l(w) + 0.05) / (lum(c) + 0.05);
// => (lum(c) + 0.05)^2 > (l(b) +0.05) * (l(w) + 0.05) = 1.05 * 0.05 = 0.0525
const lum = luminanace(...rgb);
const con = lum + 0.05;
const contrastColor = con * con > 0.0525 ? "var(--black)" : "var(--white)";
document.documentElement.style.setProperty("--theme-contrast-color", contrastColor);
}, [prefState.themeColor]);
const content = ((): JSX.Element => {
switch (path) {
case Path.editor: {
return ;
}
case Path.synchronizer: {
if (lrcState.lyric.length === 0) {
return ;
}
return ;
}
case Path.gist: {
return ;
}
case Path.preferences: {
return ;
}
}
return ;
})();
return (
}>{content}
);
};
// https://www.w3.org/TR/WCAG20/#relativeluminancedef
const luminanace = (...rgb: [number, number, number]): number => {
return rgb
.map((v) => v / 255)
.map((v) => (v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)))
.reduce((p, c, i) => {
return p + c * [0.2126, 0.7152, 0.0722][i];
}, 0);
};
const hex2rgb = (hex: string): [number, number, number] => {
hex = hex.slice(1);
const value = Number.parseInt(hex, 16);
const r = (value >> 0x10) & 0xff;
const g = (value >> 0x08) & 0xff;
const b = (value >> 0x00) & 0xff;
return [r, g, b];
};