mirror of
https://github.com/mat-1/variance.git
synced 2025-08-02 15:26:04 +00:00
prepare to start adding custom themes
This commit is contained in:
parent
2e55b739b8
commit
8c803d4c45
8 changed files with 246 additions and 120 deletions
|
@ -1,5 +1,4 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import './SegmentedControls.scss';
|
||||
|
||||
import { blurOnBubbling } from '../button/script';
|
||||
|
@ -7,26 +6,38 @@ import { blurOnBubbling } from '../button/script';
|
|||
import Text from '../text/Text';
|
||||
import RawIcon from '../system-icons/RawIcon';
|
||||
|
||||
function SegmentedControls({ selected, segments, onSelect }) {
|
||||
const [select, setSelect] = useState(selected);
|
||||
function SegmentedControls({
|
||||
selectedId,
|
||||
segments,
|
||||
onSelect,
|
||||
}: {
|
||||
selectedId: string;
|
||||
segments: {
|
||||
iconSrc?: string;
|
||||
text?: string;
|
||||
id: string;
|
||||
}[];
|
||||
onSelect: (id: string) => void;
|
||||
}) {
|
||||
const [select, setSelect] = useState(selectedId);
|
||||
|
||||
function selectSegment(segmentIndex) {
|
||||
setSelect(segmentIndex);
|
||||
onSelect(segmentIndex);
|
||||
function selectSegment(segmentId: string) {
|
||||
setSelect(segmentId);
|
||||
onSelect(segmentId);
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
setSelect(selected);
|
||||
}, [selected]);
|
||||
setSelect(selectedId);
|
||||
}, [selectedId]);
|
||||
|
||||
return (
|
||||
<div className="segmented-controls">
|
||||
{segments.map((segment, index) => (
|
||||
{segments.map((segment) => (
|
||||
<button
|
||||
key={Math.random().toString(20).substr(2, 6)}
|
||||
className={`segment-btn${select === index ? ' segment-btn--active' : ''}`}
|
||||
key={Math.random().toString(20).slice(2, 6)}
|
||||
className={`segment-btn${select === segment.id ? ' segment-btn--active' : ''}`}
|
||||
type="button"
|
||||
onClick={() => selectSegment(index)}
|
||||
onClick={() => selectSegment(segment.id)}
|
||||
onMouseUp={(e) => blurOnBubbling(e, '.segment-btn')}
|
||||
>
|
||||
<div className="segment-btn__base">
|
||||
|
@ -39,15 +50,4 @@ function SegmentedControls({ selected, segments, onSelect }) {
|
|||
);
|
||||
}
|
||||
|
||||
SegmentedControls.propTypes = {
|
||||
selected: PropTypes.number.isRequired,
|
||||
segments: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
iconSrc: PropTypes.string,
|
||||
text: PropTypes.string,
|
||||
}),
|
||||
).isRequired,
|
||||
onSelect: PropTypes.func.isRequired,
|
||||
};
|
||||
|
||||
export default SegmentedControls;
|
||||
|
|
|
@ -128,14 +128,14 @@ function RoomMembers({ roomId }) {
|
|||
searchMembers ? `Found — ${mList.length}` : members.length
|
||||
} members`}</MenuHeader>
|
||||
<SegmentedControls
|
||||
selected={(() => {
|
||||
const getSegmentIndex = { join: 0, invite: 1, ban: 2 };
|
||||
return getSegmentIndex[membership];
|
||||
})()}
|
||||
segments={[{ text: 'Joined' }, { text: 'Invited' }, { text: 'Banned' }]}
|
||||
onSelect={(index) => {
|
||||
const memberships = ['join', 'invite', 'ban'];
|
||||
setMembership(memberships[index]);
|
||||
selectedId={membership}
|
||||
segments={[
|
||||
{ text: 'Joined', id: 'join' },
|
||||
{ text: 'Invited', id: 'invite' },
|
||||
{ text: 'Banned', id: 'ban' },
|
||||
]}
|
||||
onSelect={(id) => {
|
||||
setMembership(id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -98,17 +98,16 @@ function AppearanceSection() {
|
|||
title="Theme"
|
||||
content={
|
||||
<SegmentedControls
|
||||
selected={settings.useSystemTheme ? -1 : settings.getThemeIndex()}
|
||||
segments={[
|
||||
{ text: 'Light' },
|
||||
{ text: 'Silver' },
|
||||
{ text: 'Dark' },
|
||||
{ text: 'Butter' },
|
||||
{ text: 'Ayu' },
|
||||
]}
|
||||
onSelect={(index) => {
|
||||
selectedId={settings.getThemeSettings().getThemeId()}
|
||||
segments={Array.from(settings.getThemeSettings().themeIdToName).map(
|
||||
([themeId, themeName]) => ({
|
||||
text: themeName,
|
||||
id: themeId,
|
||||
}),
|
||||
)}
|
||||
onSelect={(themeId: string) => {
|
||||
if (settings.useSystemTheme) toggleSystemTheme();
|
||||
settings.setTheme(index);
|
||||
settings.setThemeId(themeId);
|
||||
updateState({});
|
||||
}}
|
||||
/>
|
||||
|
|
|
@ -2,14 +2,15 @@ import EventEmitter from 'events';
|
|||
import appDispatcher from '../dispatcher';
|
||||
|
||||
import cons from './cons';
|
||||
import { ThemeSettings } from './themes';
|
||||
|
||||
function getSettings() {
|
||||
function getSettings(): Record<string, unknown> | null {
|
||||
const settings = localStorage.getItem('settings');
|
||||
if (settings === null) return null;
|
||||
return JSON.parse(settings);
|
||||
}
|
||||
|
||||
function setSettings(key, value) {
|
||||
function setSettings(key: string, value: unknown) {
|
||||
let settings = getSettings();
|
||||
if (settings === null) settings = {};
|
||||
settings[key] = value;
|
||||
|
@ -19,11 +20,7 @@ function setSettings(key, value) {
|
|||
class Settings extends EventEmitter {
|
||||
isTouchScreenDevice: boolean;
|
||||
|
||||
themes: string[];
|
||||
|
||||
themeIndex: number;
|
||||
|
||||
useSystemTheme: boolean;
|
||||
themeSettings: ThemeSettings;
|
||||
|
||||
isMarkdown: boolean;
|
||||
|
||||
|
@ -56,10 +53,8 @@ class Settings extends EventEmitter {
|
|||
|
||||
this.isTouchScreenDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
this.themes = ['', 'silver-theme', 'dark-theme', 'butter-theme', 'ayu-theme'];
|
||||
this.themeIndex = this.getThemeIndex();
|
||||
this.themeSettings = this.getThemeSettings();
|
||||
|
||||
this.useSystemTheme = this.getUseSystemTheme();
|
||||
this.isMarkdown = this.getIsMarkdown();
|
||||
this.isPeopleDrawer = this.getIsPeopleDrawer();
|
||||
this.hideMembershipEvents = this.getHideMembershipEvents();
|
||||
|
@ -75,88 +70,65 @@ class Settings extends EventEmitter {
|
|||
this.clearUrls = this.getClearUrls();
|
||||
}
|
||||
|
||||
getThemeIndex() {
|
||||
if (typeof this.themeIndex === 'number') return this.themeIndex;
|
||||
getThemeSettings(): ThemeSettings {
|
||||
if (this.themeSettings !== undefined) return this.themeSettings;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return 0;
|
||||
if (typeof settings.themeIndex === 'undefined') return 0;
|
||||
// eslint-disable-next-line radix
|
||||
return parseInt(settings.themeIndex);
|
||||
const themeSettings = ThemeSettings.fromSettings(settings ?? {});
|
||||
|
||||
return themeSettings;
|
||||
}
|
||||
|
||||
getThemeName() {
|
||||
return this.themes[this.themeIndex];
|
||||
}
|
||||
updateThemeSettings(): void {
|
||||
const settings = this.getThemeSettings().toSettings();
|
||||
|
||||
_clearTheme() {
|
||||
document.body.classList.remove('system-theme');
|
||||
this.themes.forEach((themeName) => {
|
||||
if (themeName === '') return;
|
||||
document.body.classList.remove(themeName);
|
||||
Object.entries(settings).forEach(([key, value]) => {
|
||||
setSettings(key, value);
|
||||
});
|
||||
}
|
||||
|
||||
applyTheme() {
|
||||
this._clearTheme();
|
||||
if (this.useSystemTheme) {
|
||||
document.body.classList.add('system-theme');
|
||||
} else if (this.themes[this.themeIndex]) {
|
||||
document.body.classList.add(this.themes[this.themeIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
setTheme(themeIndex) {
|
||||
this.themeIndex = themeIndex;
|
||||
setSettings('themeIndex', this.themeIndex);
|
||||
this.applyTheme();
|
||||
setThemeId(themeId: string): void {
|
||||
this.themeSettings.setThemeId(themeId);
|
||||
this.updateThemeSettings();
|
||||
this.themeSettings.applyTheme();
|
||||
}
|
||||
|
||||
toggleUseSystemTheme() {
|
||||
this.useSystemTheme = !this.useSystemTheme;
|
||||
setSettings('useSystemTheme', this.useSystemTheme);
|
||||
this.applyTheme();
|
||||
this.updateThemeSettings();
|
||||
this.themeSettings.applyTheme();
|
||||
|
||||
this.emit(cons.events.settings.SYSTEM_THEME_TOGGLED, this.useSystemTheme);
|
||||
}
|
||||
|
||||
getUseSystemTheme() {
|
||||
if (typeof this.useSystemTheme === 'boolean') return this.useSystemTheme;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.useSystemTheme === 'undefined') return true;
|
||||
return settings.useSystemTheme;
|
||||
}
|
||||
|
||||
getIsMarkdown() {
|
||||
getIsMarkdown(): boolean {
|
||||
if (typeof this.isMarkdown === 'boolean') return this.isMarkdown;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.isMarkdown === 'undefined') return true;
|
||||
if (typeof settings.isMarkdown !== 'boolean') return true;
|
||||
return settings.isMarkdown;
|
||||
}
|
||||
|
||||
getHideMembershipEvents() {
|
||||
getHideMembershipEvents(): boolean {
|
||||
if (typeof this.hideMembershipEvents === 'boolean') return this.hideMembershipEvents;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return false;
|
||||
if (typeof settings.hideMembershipEvents === 'undefined') return false;
|
||||
if (typeof settings.hideMembershipEvents !== 'boolean') return false;
|
||||
return settings.hideMembershipEvents;
|
||||
}
|
||||
|
||||
getHideNickAvatarEvents() {
|
||||
getHideNickAvatarEvents(): boolean {
|
||||
if (typeof this.hideNickAvatarEvents === 'boolean') return this.hideNickAvatarEvents;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.hideNickAvatarEvents === 'undefined') return true;
|
||||
if (typeof settings.hideNickAvatarEvents !== 'boolean') return true;
|
||||
return settings.hideNickAvatarEvents;
|
||||
}
|
||||
|
||||
getSendOnEnter() {
|
||||
getSendOnEnter(): boolean {
|
||||
if (typeof this.sendMessageOnEnter === 'boolean') return this.sendMessageOnEnter;
|
||||
|
||||
const settings = getSettings();
|
||||
|
@ -164,48 +136,48 @@ class Settings extends EventEmitter {
|
|||
const defaultSendOnEnter = !this.isTouchScreenDevice;
|
||||
|
||||
if (settings === null) return defaultSendOnEnter;
|
||||
if (typeof settings.sendMessageOnEnter === 'undefined') return defaultSendOnEnter;
|
||||
if (typeof settings.sendMessageOnEnter !== 'boolean') return defaultSendOnEnter;
|
||||
return settings.sendMessageOnEnter;
|
||||
}
|
||||
|
||||
getOnlyAnimateOnHover() {
|
||||
getOnlyAnimateOnHover(): boolean {
|
||||
if (typeof this.onlyAnimateOnHover === 'boolean') return this.onlyAnimateOnHover;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.onlyAnimateOnHover === 'undefined') return true;
|
||||
if (typeof settings.onlyAnimateOnHover !== 'boolean') return true;
|
||||
return settings.onlyAnimateOnHover;
|
||||
}
|
||||
|
||||
getIsPeopleDrawer() {
|
||||
getIsPeopleDrawer(): boolean {
|
||||
if (typeof this.isPeopleDrawer === 'boolean') return this.isPeopleDrawer;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.isPeopleDrawer === 'undefined') return true;
|
||||
if (typeof settings.isPeopleDrawer !== 'boolean') return true;
|
||||
return settings.isPeopleDrawer;
|
||||
}
|
||||
|
||||
get showNotifications() {
|
||||
get showNotifications(): boolean {
|
||||
if (window.Notification?.permission !== 'granted') return false;
|
||||
return this._showNotifications;
|
||||
}
|
||||
|
||||
getShowNotifications() {
|
||||
getShowNotifications(): boolean {
|
||||
if (typeof this._showNotifications === 'boolean') return this._showNotifications;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.showNotifications === 'undefined') return true;
|
||||
if (typeof settings.showNotifications !== 'boolean') return true;
|
||||
return settings.showNotifications;
|
||||
}
|
||||
|
||||
getIsNotificationSounds() {
|
||||
getIsNotificationSounds(): boolean {
|
||||
if (typeof this.isNotificationSounds === 'boolean') return this.isNotificationSounds;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.isNotificationSounds === 'undefined') return true;
|
||||
if (typeof settings.isNotificationSounds !== 'boolean') return true;
|
||||
return settings.isNotificationSounds;
|
||||
}
|
||||
|
||||
|
@ -216,12 +188,12 @@ class Settings extends EventEmitter {
|
|||
this.emit(cons.events.settings.SHOW_ROOM_LIST_AVATAR_TOGGLED, this.showRoomListAvatar);
|
||||
}
|
||||
|
||||
getShowRoomListAvatar() {
|
||||
getShowRoomListAvatar(): boolean {
|
||||
if (typeof this.showRoomListAvatar === 'boolean') return this.showRoomListAvatar;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return false;
|
||||
if (typeof settings.showRoomListAvatar === 'undefined') return false;
|
||||
if (typeof settings.showRoomListAvatar !== 'boolean') return false;
|
||||
return settings.showRoomListAvatar;
|
||||
}
|
||||
|
||||
|
@ -232,12 +204,12 @@ class Settings extends EventEmitter {
|
|||
this.emit(cons.events.settings.SHOW_YOUTUBE_EMBED_PLAYER_TOGGLED, this.showYoutubeEmbedPlayer);
|
||||
}
|
||||
|
||||
getShowYoutubeEmbedPlayer() {
|
||||
getShowYoutubeEmbedPlayer(): boolean {
|
||||
if (typeof this.showYoutubeEmbedPlayer === 'boolean') return this.showYoutubeEmbedPlayer;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return false;
|
||||
if (typeof settings.showYoutubeEmbedPlayer === 'undefined') return false;
|
||||
if (typeof settings.showYoutubeEmbedPlayer !== 'boolean') return false;
|
||||
return settings.showYoutubeEmbedPlayer;
|
||||
}
|
||||
|
||||
|
@ -248,30 +220,30 @@ class Settings extends EventEmitter {
|
|||
this.emit(cons.events.settings.SHOW_URL_PREVIEW_TOGGLED, this.showUrlPreview);
|
||||
}
|
||||
|
||||
getShowUrlPreview() {
|
||||
getShowUrlPreview(): boolean {
|
||||
if (typeof this.showUrlPreview === 'boolean') return this.showUrlPreview;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return false;
|
||||
if (typeof settings.showUrlPreview === 'undefined') return false;
|
||||
if (typeof settings.showUrlPreview !== 'boolean') return false;
|
||||
return settings.showUrlPreview;
|
||||
}
|
||||
|
||||
getSendReadReceipts() {
|
||||
getSendReadReceipts(): boolean {
|
||||
if (typeof this.sendReadReceipts === 'boolean') return this.sendReadReceipts;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.sendReadReceipts === 'undefined') return true;
|
||||
if (typeof settings.sendReadReceipts !== 'boolean') return true;
|
||||
return settings.sendReadReceipts;
|
||||
}
|
||||
|
||||
getClearUrls() {
|
||||
getClearUrls(): boolean {
|
||||
if (typeof this.clearUrls === 'boolean') return this.clearUrls;
|
||||
|
||||
const settings = getSettings();
|
||||
if (settings === null) return true;
|
||||
if (typeof settings.clearUrls === 'undefined') return true;
|
||||
if (typeof settings.clearUrls !== 'boolean') return true;
|
||||
return settings.clearUrls;
|
||||
}
|
||||
|
||||
|
|
153
src/client/state/themes.ts
Normal file
153
src/client/state/themes.ts
Normal file
|
@ -0,0 +1,153 @@
|
|||
interface ThemeStyle {
|
||||
'bg-surface': string;
|
||||
}
|
||||
|
||||
const DEFAULT_THEME_STYLE: ThemeStyle = {
|
||||
'bg-surface': '#000000',
|
||||
};
|
||||
|
||||
const DEFAULT_DARK_THEME_ID = 'ayu';
|
||||
const DEFAULT_LIGHT_THEME_ID = 'light';
|
||||
|
||||
const DEFAULT_THEME_ID_TO_NAME = {
|
||||
ayu: 'Ayu',
|
||||
classic: 'Classic',
|
||||
butter: 'Butter',
|
||||
light: 'Light',
|
||||
silver: 'Silver',
|
||||
// custom: 'Custom',
|
||||
} as const;
|
||||
|
||||
interface ThemeSettingsOpts {
|
||||
customThemeName: string | null;
|
||||
customTheme: Partial<ThemeStyle>;
|
||||
themeId: string;
|
||||
useSystemTheme: boolean;
|
||||
}
|
||||
|
||||
export class ThemeSettings {
|
||||
themeIdToName: Map<string, string>;
|
||||
|
||||
customThemeStyle: ThemeStyle;
|
||||
|
||||
themeId: string;
|
||||
|
||||
useSystemTheme: boolean;
|
||||
|
||||
constructor(opts: ThemeSettingsOpts) {
|
||||
this.themeIdToName = new Map(Object.entries(DEFAULT_THEME_ID_TO_NAME));
|
||||
// if (opts.customThemeName) this.themeIdToName.set('custom', opts.customThemeName);
|
||||
|
||||
this.customThemeStyle = {
|
||||
...DEFAULT_THEME_STYLE,
|
||||
...opts.customTheme,
|
||||
};
|
||||
|
||||
this.themeId = opts.themeId;
|
||||
|
||||
this.useSystemTheme = opts.useSystemTheme;
|
||||
|
||||
// detect when the system theme changes and apply
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
|
||||
mediaQuery.addEventListener('change', () => {
|
||||
this.applyTheme();
|
||||
});
|
||||
}
|
||||
|
||||
static fromSettings(settings: Record<string, unknown>): ThemeSettings {
|
||||
const customThemeName =
|
||||
typeof settings.customThemeName === 'string' ? settings.customThemeName : null;
|
||||
|
||||
const customTheme = settings.customTheme || {};
|
||||
|
||||
const themeId = typeof settings.themeId === 'string' ? settings.themeId : DEFAULT_DARK_THEME_ID;
|
||||
|
||||
const useSystemTheme =
|
||||
typeof settings.useSystemTheme === 'boolean' ? settings.useSystemTheme : true;
|
||||
|
||||
return new ThemeSettings({
|
||||
customThemeName,
|
||||
customTheme,
|
||||
themeId,
|
||||
useSystemTheme,
|
||||
});
|
||||
}
|
||||
|
||||
toSettings(): Record<string, unknown> {
|
||||
return {
|
||||
customThemeName: this.getCustomThemeName(),
|
||||
customTheme: this.getCustomThemeStyle(),
|
||||
themeId: this.getThemeId(),
|
||||
useSystemTheme: this.getUseSystemTheme(),
|
||||
};
|
||||
}
|
||||
|
||||
getThemeName(themeId: string): string | null {
|
||||
return this.themeIdToName.get(themeId) || null;
|
||||
}
|
||||
|
||||
getCustomThemeName(): string | null {
|
||||
return this.themeIdToName.get('custom') || null;
|
||||
}
|
||||
|
||||
setCustomThemeName(themeName: string): void {
|
||||
this.themeIdToName.set('custom', themeName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the theme ID that was selected by the user in settings. Note that if useSystemThee is
|
||||
* enabled then this might not be the theme that's actually being used.
|
||||
*/
|
||||
getThemeId(): string {
|
||||
return this.themeId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the actual theme ID that is going to be used. This may not be the same as `getThemeId`
|
||||
* because this takes the system theme into account.
|
||||
* @returns The theme ID that is actually being used.
|
||||
*/
|
||||
getActualThemeId(): string {
|
||||
if (this.useSystemTheme) {
|
||||
const mediaQuery = window.matchMedia('(prefers-color-scheme: light)');
|
||||
return mediaQuery.matches ? DEFAULT_LIGHT_THEME_ID : DEFAULT_DARK_THEME_ID;
|
||||
}
|
||||
return this.themeId;
|
||||
}
|
||||
|
||||
setThemeId(themeId: string): void {
|
||||
this.themeId = themeId;
|
||||
}
|
||||
|
||||
getCustomThemeStyle(): ThemeStyle {
|
||||
return this.customThemeStyle;
|
||||
}
|
||||
|
||||
setCustomThemeStyle(themeStyle: Partial<ThemeStyle>): void {
|
||||
this.customThemeStyle = {
|
||||
...this.customThemeStyle,
|
||||
...themeStyle,
|
||||
};
|
||||
}
|
||||
|
||||
getUseSystemTheme(): boolean {
|
||||
return this.useSystemTheme;
|
||||
}
|
||||
|
||||
setUseSystemTheme(useSystemTheme: boolean): void {
|
||||
this.useSystemTheme = useSystemTheme;
|
||||
}
|
||||
|
||||
_clearTheme() {
|
||||
this.themeIdToName.forEach((_, themeId) => {
|
||||
if (themeId === '') return;
|
||||
document.body.classList.remove(`${themeId}-theme`);
|
||||
});
|
||||
}
|
||||
|
||||
applyTheme() {
|
||||
this._clearTheme();
|
||||
const themeId = this.getActualThemeId();
|
||||
document.body.classList.add(`${themeId}-theme`);
|
||||
}
|
||||
}
|
|
@ -309,7 +309,7 @@
|
|||
--ic-surface-low: rgba(255, 251, 222, 64%);
|
||||
}
|
||||
|
||||
.dark-theme,
|
||||
.classic-theme,
|
||||
.butter-theme {
|
||||
@include dark-mode();
|
||||
}
|
||||
|
|
|
@ -7,8 +7,9 @@ import settings from './client/state/settings';
|
|||
|
||||
import App from './app/pages/App';
|
||||
|
||||
settings.applyTheme();
|
||||
settings.getThemeSettings().applyTheme();
|
||||
|
||||
const container = document.getElementById('root');
|
||||
if (!container) throw new Error('Root element not found');
|
||||
const root = createRoot(container);
|
||||
root.render(<App />);
|
||||
|
|
|
@ -2,13 +2,14 @@
|
|||
"compilerOptions": {
|
||||
"sourceMap": true,
|
||||
"jsx": "react",
|
||||
"target": "ES6",
|
||||
"target": "ESNext",
|
||||
"allowJs": true,
|
||||
"esModuleInterop": true,
|
||||
"moduleResolution": "Node",
|
||||
"outDir": "dist",
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext"
|
||||
"module": "ESNext",
|
||||
"strict": true
|
||||
},
|
||||
"exclude": ["node_modules", "dist"],
|
||||
"include": ["src"]
|
||||
|
|
Loading…
Add table
Reference in a new issue