diff --git a/ivette/src/dome/main/dome.ts b/ivette/src/dome/main/dome.ts index 9ab3ca18f76f7f9c695166ab8ea33a3fe241f931..6b77b3455661024d81ed75ec468d2de7e8daedf6 100644 --- a/ivette/src/dome/main/dome.ts +++ b/ivette/src/dome/main/dome.ts @@ -20,7 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable no-console */ /** @@ -50,6 +49,7 @@ import { shell, dialog, nativeTheme, + Rectangle, } from 'electron'; import installExtension, { REACT_DEVELOPER_TOOLS } from 'dome/devtools'; import SYS, * as System from 'dome/system'; @@ -65,7 +65,7 @@ import * as Menubar from './menubar'; // --- System Helpers // -------------------------------------------------------------------------- -function fstat(p: string) { +function fstat(p: string): fs.Stats | undefined { try { return fs.statSync(p); } catch (_error) { @@ -97,7 +97,7 @@ nativeTheme.on('updated', () => { broadcast('dome.theme.updated'); }); -function setNativeTheme(theme: string | undefined) { +function setNativeTheme(theme: string | undefined): void { switch (theme) { case 'dark': case 'light': @@ -113,7 +113,13 @@ function setNativeTheme(theme: string | undefined) { // --- Settings // -------------------------------------------------------------------------- -function loadSettings(file: string) { +type json = + | undefined | null | boolean | number | string + | { [key: string]: json }; +type Store = { [key: string]: json }; +type Frame = { x: number, y: number, width: number, height: number }; + +function loadSettings(file: string): Store { try { if (!fstat(file)) return {}; @@ -125,7 +131,7 @@ function loadSettings(file: string) { } } -function saveSettings(file: string, data = {}) { +function saveSettings(file: string, data: Store = {}): void { try { const text = JSON.stringify(data, undefined, DEVEL ? 2 : 0); fs.writeFileSync(file, text, { encoding: 'utf8' }); @@ -144,7 +150,7 @@ const APP_DIR = app.getPath('userData'); const PATH_WINDOW_SETTINGS = path.join(APP_DIR, 'WindowSettings.json'); const PATH_GLOBAL_SETTINGS = path.join(APP_DIR, 'GlobalSettings.json'); -function saveGlobalSettings() { +function saveGlobalSettings(): void { try { if (!fstat(APP_DIR)) fs.mkdirSync(APP_DIR); saveSettings(PATH_GLOBAL_SETTINGS, GlobalSettings); @@ -153,7 +159,7 @@ function saveGlobalSettings() { } } -function obtainGlobalSettings() { +function obtainGlobalSettings(): Store { if (_.isEmpty(GlobalSettings)) { GlobalSettings = loadSettings(PATH_GLOBAL_SETTINGS); } @@ -164,12 +170,10 @@ function obtainGlobalSettings() { // --- Window Settings & Frames // -------------------------------------------------------------------------- -type Store = { [key: string]: unknown }; - interface Handle { primary: boolean; // Primary window window: BrowserWindow; // Also prevents Gc - frame: Electron.Rectangle; // Window frame + frame: Electron.Rectangle | undefined; // Window frame devtools: boolean; // Developper tools visible reloaded: boolean; // Reloaded window config: string; // Path to config file @@ -179,9 +183,29 @@ interface Handle { const WindowHandles = new Map<number, Handle>(); // Indexed by *webContents* id -function saveWindowConfig(handle: Handle) { - const configData = { - frame: handle.frame, +function jInt(v: json): number { + return _.toSafeInteger(v); +} + +function jFrame(obj: json | Rectangle): Frame | undefined { + if (obj && typeof (obj) === 'object') + return { + x: jInt(obj.x), + y: jInt(obj.y), + width: jInt(obj.width), + height: jInt(obj.height), + }; + return undefined; +} + +function jStore(obj: json): Store { + return obj !== null && typeof (obj) === 'object' ? obj : {}; +} + +function saveWindowConfig(handle: Handle): void { + const frame = jFrame(handle.frame); + const configData: Store = { + frame, settings: handle.settings, storage: handle.storage, devtools: handle.devtools, @@ -189,7 +213,7 @@ function saveWindowConfig(handle: Handle) { saveSettings(handle.config, configData); } -function windowSyncSettings(event: IpcMainEvent) { +function windowSyncSettings(event: IpcMainEvent): void { const handle = WindowHandles.get(event.sender.id); event.returnValue = { globals: obtainGlobalSettings(), @@ -200,7 +224,7 @@ function windowSyncSettings(event: IpcMainEvent) { ipcMain.on('dome.ipc.settings.sync', windowSyncSettings); -function applyThemeSettings(settings: Store) { +function applyThemeSettings(settings: Store): void { const theme = settings['dome-color-theme']; if (typeof (theme) === 'string') setNativeTheme(theme); } @@ -209,9 +233,9 @@ function applyThemeSettings(settings: Store) { // --- Patching Settings // -------------------------------------------------------------------------- -type Patch = { key: string; value: unknown }; +type Patch = { key: string; value: json }; -function applyPatches(data: Store, args: Patch[]) { +function applyPatches(data: Store, args: Patch[]): void { args.forEach(({ key, value }) => { if (value === null) { delete data[key]; @@ -221,7 +245,7 @@ function applyPatches(data: Store, args: Patch[]) { }); } -function applyWindowSettings(event: IpcMainEvent, args: Patch[]) { +function applyWindowSettings(event: IpcMainEvent, args: Patch[]): void { const handle = WindowHandles.get(event.sender.id); if (handle) { applyPatches(handle.settings, args); @@ -229,7 +253,7 @@ function applyWindowSettings(event: IpcMainEvent, args: Patch[]) { } } -function applyStorageSettings(event: IpcMainEvent, args: Patch[]) { +function applyStorageSettings(event: IpcMainEvent, args: Patch[]): void { const handle = WindowHandles.get(event.sender.id); if (handle) { applyPatches(handle.storage, args); @@ -237,7 +261,7 @@ function applyStorageSettings(event: IpcMainEvent, args: Patch[]) { } } -function applyGlobalSettings(event: IpcMainEvent, args: Patch[]) { +function applyGlobalSettings(event: IpcMainEvent, args: Patch[]): void { const settings: Store = obtainGlobalSettings(); applyPatches(settings, args); applyThemeSettings(settings); @@ -258,7 +282,7 @@ ipcMain.on('dome.ipc.settings.storage', applyStorageSettings); // --- Renderer-Process Communication // -------------------------------------------------------------------------- -function broadcast(event: string, ...args: unknown[]) { +function broadcast(event: string, ...args: unknown[]): void { BrowserWindow.getAllWindows().forEach((w) => { w.webContents.send(event, ...args); }); @@ -274,16 +298,16 @@ const MODIFIED = '(*) '; /** Sets application window name */ -export function setName(title: string) { +export function setName(title: string): void { appName = title; } -function setTitle(event: IpcMainEvent, title: string) { +function setTitle(event: IpcMainEvent, title: string): void { const handle = WindowHandles.get(event.sender.id); if (handle) handle.window.setTitle(title || appName); } -function setModified(event: IpcMainEvent, modified: boolean) { +function setModified(event: IpcMainEvent, modified: boolean): void { const handle = WindowHandles.get(event.sender.id); if (handle) { const w = handle.window; @@ -303,7 +327,7 @@ function setModified(event: IpcMainEvent, modified: boolean) { ipcMain.on('dome.ipc.window.title', setTitle); ipcMain.on('dome.ipc.window.modified', setModified); -function getURL() { +function getURL(): string { if (DEVEL) return `http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}`; if (LOCAL) @@ -328,7 +352,7 @@ function navigateURL(sender: Electron.WebContents) { // --- Lookup for config file // -------------------------------------------------------------------------- -function lookupConfig(pwd = '.') { +function lookupConfig(pwd = '.'): string { const wdir = path.resolve(pwd); let cwd = wdir; const cfg = `.${appName.toLowerCase()}`; @@ -353,7 +377,7 @@ function createBrowserWindow( config: BrowserWindowConstructorOptions, argv?: string[], wdir?: string, -) { +): BrowserWindow { const isAppWindow = (argv !== undefined && wdir !== undefined); @@ -375,13 +399,16 @@ function createBrowserWindow( const configFile = isAppWindow ? lookupConfig(wdir) : PATH_WINDOW_SETTINGS; const configData = loadSettings(configFile); - const { frame, devtools, settings = {}, storage = {} } = configData; + const frame = jFrame(configData.frame); + const settings = jStore(configData.settings); + const storage = jStore(configData.storage); + const devtools = !!configData.devtools; + if (frame) { - const getInt = <A>(v: A) => v && _.toSafeInteger(v); - options.x = getInt(frame.x); - options.y = getInt(frame.y); - options.width = getInt(frame.width); - options.height = getInt(frame.height); + options.x = frame.x; + options.y = frame.y; + options.width = frame.width; + options.height = frame.height; } const theWindow = new BrowserWindow(options); @@ -468,11 +495,11 @@ function createBrowserWindow( // --- Application Window(s) & Command Line // -------------------------------------------------------------------------- -function stripElectronArgv(argv: string[]) { +function stripElectronArgv(argv: string[]): string[] { return argv.slice(DEVEL ? 3 : (LOCAL ? 2 : 1)).filter((p) => !!p); } -function createPrimaryWindow() { +function createPrimaryWindow(): void { // Initialize Menubar Menubar.install(); @@ -499,7 +526,7 @@ function createSecondaryWindow( _event: Electron.Event, chromiumArgv: string[], wdir: string, -) { +): void { const argStart = "--second-instance="; let argString = chromiumArgv.find(a => a.startsWith(argStart)); if (argString) { @@ -511,7 +538,7 @@ function createSecondaryWindow( } } -function createDesktopWindow() { +function createDesktopWindow(): void { const wdir = app.getPath('home'); const title = `${appName} #${++appCount}`; createBrowserWindow(false, { title }, [], wdir); @@ -521,7 +548,7 @@ function createDesktopWindow() { // --- Activate Windows (macOS) // -------------------------------------------------------------------------- -function activateWindows() { +function activateWindows(): void { let isFocused = false; let toFocus: BrowserWindow | undefined; BrowserWindow.getAllWindows().forEach((w) => { @@ -544,7 +571,7 @@ function activateWindows() { let PreferenceWindow: BrowserWindow | undefined; -function showSettingsWindow() { +function showSettingsWindow(): void { if (!PreferenceWindow) PreferenceWindow = createBrowserWindow( false, { @@ -560,7 +587,7 @@ function showSettingsWindow() { PreferenceWindow.on('closed', () => { PreferenceWindow = undefined; }); } -function restoreDefaultSettings() { +function restoreDefaultSettings(): void { GlobalSettings = {}; nativeTheme.themeSource = 'system'; if (DEVEL) saveGlobalSettings(); @@ -592,7 +619,7 @@ ipcMain.on('dome.app.paths', (event) => { // -------------------------------------------------------------------------- /** Starts the main process. */ -export function start() { +export function start(): void { // Workaround to recover the original commandline of a second instance // after chromium messes with the argument order. @@ -633,21 +660,21 @@ export function start() { /** Define a custom main window menu. */ -export function addMenu(label: string) { +export function addMenu(label: string): void { Menubar.addMenu(label); } /** Define a custom menu item. */ -export function addMenuItem(spec: Menubar.CustomMenuItemSpec) { +export function addMenuItem(spec: Menubar.CustomMenuItemSpec): void { Menubar.addMenuItem(spec); } /** Update a menu item. */ -export function setMenuItem(spec: Menubar.CustomMenuItem) { +export function setMenuItem(spec: Menubar.CustomMenuItem): void { Menubar.setMenuItem(spec); } diff --git a/ivette/src/dome/main/menubar.ts b/ivette/src/dome/main/menubar.ts index 417631484b83afa11b2f56767782084e7ddf15a6..14d9cfda557ab38ce8df7a6f949b5b8f6dbefafa 100644 --- a/ivette/src/dome/main/menubar.ts +++ b/ivette/src/dome/main/menubar.ts @@ -20,7 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable no-console */ // -------------------------------------------------------------------------- @@ -42,7 +41,7 @@ import * as System from 'dome/system'; // --- Special Callbacks // -------------------------------------------------------------------------- -function reloadWindow() { +function reloadWindow(): void { reset(); // declared below BrowserWindow.getAllWindows().forEach((win) => { if (win) { @@ -60,7 +59,7 @@ function toggleFullScreen( _item: MenuItem, focusedWindow: BrowserWindow | undefined, _evt: KeyboardEvent, -) { +): void { if (focusedWindow) focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); } @@ -69,7 +68,7 @@ function toggleDevTools( _item: MenuItem, focusedWindow: BrowserWindow | undefined, _evt: KeyboardEvent, -) { +): void { if (focusedWindow) focusedWindow.webContents.toggleDevTools(); } @@ -78,7 +77,7 @@ function userFindInfo( _item: MenuItem, focusedWindow: BrowserWindow | undefined, _evt: KeyboardEvent, -) { +): void { if (focusedWindow) focusedWindow.webContents.send('dome.ipc.find'); } @@ -292,7 +291,7 @@ const helpMenuItems: MenuSpec = [ let updateRequired = false; let updateTriggered = false; -function requestUpdate() { +function requestUpdate(): void { if (updateRequired && !updateTriggered) { updateTriggered = true; setImmediate(install); @@ -326,7 +325,7 @@ function findMenu(label: string): MenuSpec | undefined { } } -export function addMenu(label: string) { +export function addMenu(label: string): void { if (findMenu(label)) { console.warn(`Already defined menu '${label}'`); } else { @@ -348,7 +347,7 @@ export interface SeparatorItem { export type CustomMenuItemSpec = SeparatorItem | CustomMenuItem; -export function addMenuItem(custom: CustomMenuItemSpec) { +export function addMenuItem(custom: CustomMenuItemSpec): void { const menuSpec = findMenu(custom.menu); if (!menuSpec) { console.error('[Dome] Unknown menu', custom); @@ -402,12 +401,12 @@ export function addMenuItem(custom: CustomMenuItemSpec) { requestUpdate(); } -export function setMenuItem({ id, ...options }: CustomMenuItem) { +export function setMenuItem({ id, ...options }: CustomMenuItem): void { const entry = customItems.get(id); if (entry) { if (entry.spec) Object.assign(entry.spec, options); if (entry.item) Object.assign(entry.item, options); - requestUpdate (); + requestUpdate(); } else console.warn(`[Dome] unknown menu item #${id}`); } @@ -482,7 +481,7 @@ function template(): CustomMenu[] { let menubar: Menu; -function registerCustomItems(menu: Menu) { +function registerCustomItems(menu: Menu): void { menu.items.forEach((item: MenuItem) => { const entry = customItems.get(item.id); if (entry) entry.item = item; @@ -491,7 +490,7 @@ function registerCustomItems(menu: Menu) { } // Initialize the menubar machinery -export function install() { +export function install(): void { updateRequired = true; updateTriggered = false; menubar = Menu.buildFromTemplate(template()); @@ -500,7 +499,7 @@ export function install() { } // Called by reload above -function reset() { +function reset(): void { fileMenuItemsCustom.length = 0; editMenuItemsCustom.length = 0; viewMenuItemsCustom.length = 0; @@ -540,7 +539,7 @@ function handlePopupMenu(_: Event, items: PopupMenuItem[]): Promise<number> { const { display = true, enabled, checked } = item; if (display) { const label = item.label || `#${++kid}`; - const click = () => { selected = index; }; + const click = (): void => { selected = index; }; const type = checked !== undefined ? 'checkbox' : 'normal'; menu.append(new MenuItem({ label, enabled, type, checked, click })); } diff --git a/ivette/src/dome/renderer/data/settings.ts b/ivette/src/dome/renderer/data/settings.ts index 585fe1405557d4015898dcea5ca6bd0428bd2cfa..7678c516a52ab17d7ca6ca71bbaa89adb10114da 100644 --- a/ivette/src/dome/renderer/data/settings.ts +++ b/ivette/src/dome/renderer/data/settings.ts @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- States // -------------------------------------------------------------------------- @@ -207,7 +205,7 @@ class Driver { // --- Initial Data - sync(data: store) { + sync(data: store): void { this.commit.cancel(); this.store.clear(); this.diffs.clear(); @@ -224,7 +222,7 @@ class Driver { // --- Save Data - save(key: string | undefined, data: JSON.json) { + save(key: string | undefined, data: JSON.json): void { if (key === undefined) return; if (data === undefined) { this.store.delete(key); @@ -255,7 +253,7 @@ function useSettings<A>( // Foreign Settings Update React.useEffect(() => { const event = D.evt; - const onUpdate = () => { + const onUpdate = (): void => { const fromSettings = JSON.jCatch(S.decoder, undefined)(D.load(K)); if (fromSettings !== undefined) setValue(fromSettings); @@ -307,7 +305,7 @@ export function getWindowSettings<A>( export function setWindowSettings( key: string | undefined, value: JSON.json, -) { +): void { if (key) WindowSettingsDriver.save(key, value); } @@ -320,7 +318,7 @@ export function useWindowSettings<A>( key: string | undefined, decoder: JSON.Loose<A & JSON.json>, defaultValue: A & JSON.json, -) { +): State<A & JSON.json> { return useSettings({ decoder, encoder: JSON.identity, @@ -334,7 +332,7 @@ export function useWindowSettingsData<A>( decoder: JSON.Loose<A>, encoder: JSON.Encoder<A>, defaultValue: A, -) { +): State<A> { return useSettings({ decoder, encoder, @@ -343,7 +341,7 @@ export function useWindowSettingsData<A>( } /** Call the callback function on window settings events. */ -export function useWindowSettingsEvent(callback: () => void) { +export function useWindowSettingsEvent(callback: () => void): void { React.useEffect(() => { const { evt } = WindowSettingsDriver; SysEmitter.on(evt, callback); @@ -352,13 +350,13 @@ export function useWindowSettingsEvent(callback: () => void) { } /** @ignore DEPRECATED */ -export function onWindowSettings(callback: () => void) { +export function onWindowSettings(callback: () => void): void { const { evt } = WindowSettingsDriver; SysEmitter.on(evt, callback); } /** @ignore DEPRECATED */ -export function offWindowSettings(callback: () => void) { +export function offWindowSettings(callback: () => void): void { const { evt } = WindowSettingsDriver; SysEmitter.off(evt, callback); } @@ -396,7 +394,7 @@ export function getLocalStorage<A>( export function setLocalStorage( key: string | undefined, value: JSON.json, -) { +): void { if (key) LocalStorageDriver.save(key, value); } @@ -404,7 +402,7 @@ export function useLocalStorage<A>( key: string | undefined, decoder: JSON.Loose<A & JSON.json>, defaultValue: A & JSON.json, -) { +): State<A & JSON.json> { return useSettings<A & JSON.json>({ decoder, encoder: JSON.identity, @@ -418,7 +416,7 @@ export function useLocalStorageData<A>( decoder: JSON.Loose<A>, encoder: JSON.Encoder<A>, defaultValue: A, -) { +): State<A> { return useSettings({ decoder, encoder, @@ -427,7 +425,7 @@ export function useLocalStorageData<A>( } /** Call the callback function on window settings events. */ -export function useLocalStorageEvent(callback: () => void) { +export function useLocalStorageEvent(callback: () => void): void { React.useEffect(() => { const { evt } = LocalStorageDriver; SysEmitter.on(evt, callback); @@ -451,12 +449,12 @@ const GlobalSettingsDriver = new Driver({ back in the global user settings. The global user settings file is located in the usual place for the application with respect to the underlying system. */ -export function useGlobalSettings<A>(S: GlobalSettings<A>) { +export function useGlobalSettings<A>(S: GlobalSettings<A>): State<A> { return useSettings(S, GlobalSettingsDriver, S.name); } /** Call the callback function on global settings events. */ -export function useGlobalSettingsEvent(callback: () => void) { +export function useGlobalSettingsEvent(callback: () => void): void { React.useEffect(() => { const { evt } = GlobalSettingsDriver; SysEmitter.on(evt, callback); @@ -475,7 +473,7 @@ export const window = WindowSettingsDriver.evt; export const global = GlobalSettingsDriver.evt; /* @ internal */ -export function synchronize() { +export function synchronize(): void { const data = ipcRenderer.sendSync('dome.ipc.settings.sync'); const storage: store = data.storage ?? {}; const globals: store = data.globals ?? {}; diff --git a/ivette/src/dome/renderer/dome.tsx b/ivette/src/dome/renderer/dome.tsx index 9ad53344c660d7c123eb77c63716c9b2684eb514..de4868433588cbe584bc77615061e3b3b56bfb80 100644 --- a/ivette/src/dome/renderer/dome.tsx +++ b/ivette/src/dome/renderer/dome.tsx @@ -20,7 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-explicit-any */ /** @@ -62,7 +61,7 @@ import { State } from './data/states'; // main window focus let windowFocus = true; -function setContextAppNode() { +function setContextAppNode(): HTMLElement | null { const node = document.getElementById('app'); if (node) { node.className = @@ -100,25 +99,25 @@ function getPath(k: string): string { } /** Returns user's home directory. */ -export function getHome() { return getPath('home'); } +export function getHome(): string { return getPath('home'); } /** Returns user's desktop directory. */ -export function getDesktop() { return getPath('desktop'); } +export function getDesktop(): string { return getPath('desktop'); } /** Returns user's documents directory. */ -export function getDocuments() { return getPath('documents'); } +export function getDocuments(): string { return getPath('documents'); } /** Returns user's downloads directory. */ -export function getDownloads() { return getPath('downloads'); } +export function getDownloads(): string { return getPath('downloads'); } /** Returns temporary directory. */ -export function getTempDir() { return getPath('temp'); } +export function getTempDir(): string { return getPath('temp'); } /** Working directory (Application Window). */ -export function getWorkingDir() { return System.getWorkingDir(); } +export function getWorkingDir(): string { return System.getWorkingDir(); } /** Current process ID.. */ -export function getPID() { return System.getPID(); } +export function getPID(): number { return System.getPID(); } // -------------------------------------------------------------------------- // --- Application Emitter @@ -137,11 +136,11 @@ export class Event<A = void> { this.emit = this.emit.bind(this); } - on(callback: (arg: A) => void) { + on(callback: (arg: A) => void): void { System.emitter.on(this.name, callback); } - off(callback: (arg: A) => void) { + off(callback: (arg: A) => void): void { System.emitter.off(this.name, callback); } @@ -150,14 +149,14 @@ export class Event<A = void> { This methods is bound to the event, so you may use `myEvent.emit` as a callback function, instead of eg. `(arg) => myEvent.emit(arg)`. */ - emit(arg: A) { + emit(arg: A): void { System.emitter.emit(this.name, arg); } /** Number of currenty registered listeners. */ - listenerCount() { + listenerCount(): number { return System.emitter.listenerCount(this.name); } @@ -167,8 +166,8 @@ export class Event<A = void> { export function useEvent<A>( evt: undefined | null | Event<A>, callback: (arg: A) => void, -) { - return React.useEffect(() => { +): void { + React.useEffect(() => { if (evt) { evt.on(callback); return () => evt.off(callback); @@ -221,7 +220,7 @@ ipcRenderer.on('dome.ipc.find', () => find.emit()); /** Command-line arguments event handler. */ export function onCommand( job: (argv: string[], workingDir: string) => void, -) { +): void { System.emitter.on('dome.command', job); } @@ -245,7 +244,7 @@ export const globalSettings = new Event(Settings.global); ipcRenderer.on('dome.ipc.closing', System.doExit); /** Register a callback to be executed when the window is closing. */ -export function atExit(callback: () => void) { +export function atExit(callback: () => void): void { System.atExit(callback); } @@ -257,7 +256,7 @@ export function atExit(callback: () => void) { export const focus = new Event<boolean>('dome.focus'); /** Current focus state of the main window. See also [[useWindowFocus]]. */ -export function isFocused() { return windowFocus; } +export function isFocused(): boolean { return windowFocus; } ipcRenderer.on('dome.ipc.focus', (_sender, value) => { windowFocus = value; @@ -293,11 +292,11 @@ ipcRenderer.on( // --- Window Management // -------------------------------------------------------------------------- -export function isApplicationWindow() { +export function isApplicationWindow(): boolean { return process.argv.includes(SYS.WINDOW_APPLICATION_ARGV); } -export function isPreferencesWindow() { +export function isPreferencesWindow(): boolean { return process.argv.includes(SYS.WINDOW_PREFERENCES_ARGV); } @@ -307,12 +306,12 @@ export function isPreferencesWindow() { /** Sets the modified status of the window-frame flag. User feedback is platform dependent. */ -export function setModified(modified = false) { +export function setModified(modified = false): void { ipcRenderer.send('dome.ipc.window.modified', modified); } /** Sets the window-frame title. */ -export function setTitle(title: string) { +export function setTitle(title: string): void { ipcRenderer.send('dome.ipc.window.title', title); } @@ -322,7 +321,7 @@ export function setTitle(title: string) { function setContainer( Component: React.FunctionComponent | React.ComponentClass, -) { +): void { Settings.synchronize(); const appNode = setContextAppNode(); const contents = <AppContainer><Component /></AppContainer>; @@ -346,7 +345,7 @@ function setContainer( */ export function setApplicationWindow( Component: React.FunctionComponent | React.ComponentClass, -) { +): void { if (isApplicationWindow()) setContainer(Component); } @@ -366,7 +365,7 @@ export function setApplicationWindow( */ export function setPreferencesWindow( Component: React.FunctionComponent | React.ComponentClass, -) { +): void { if (isPreferencesWindow()) setContainer(Component); } @@ -389,7 +388,7 @@ const customItemCallbacks = new Map<string, callback>(); @param label - the menu title (shall be unique) */ -export function addMenu(label: string) { +export function addMenu(label: string): void { ipcRenderer.send('dome.ipc.menu.addmenu', label); } @@ -434,7 +433,7 @@ export interface MenuItemProps { windows would be ignored. It is also possible to call this function from the main process. */ -export function addMenuItem(props: MenuItemProps) { +export function addMenuItem(props: MenuItemProps): void { if (!props.id && props.type !== 'separator') { // eslint-disable-next-line no-console console.error('[Dome] Missing menu-item identifier', props); @@ -463,7 +462,7 @@ export interface MenuItemOptions { It is also possible to call this function from the main process. */ -export function setMenuItem(options: MenuItemOptions) { +export function setMenuItem(options: MenuItemOptions): void { const { onClick, ...updates } = options; if (onClick === null) { customItemCallbacks.delete(options.id); @@ -522,7 +521,7 @@ export type PopupMenuItem = PopupMenuItemProps | 'separator'; export function popupMenu( items: PopupMenuItem[], callback?: (item: string | undefined) => void, -) { +): void { const ipcItems = items.map((item) => { if (!item) return undefined; if (item === 'separator') return item; @@ -554,7 +553,7 @@ export function popupMenu( Hook to re-render on demand (Custom React Hook). Returns a callback to trigger a render on demand. */ -export function useForceUpdate() { +export function useForceUpdate(): () => void { const [tac, onTic] = React.useState(false); return () => onTic(!tac); } @@ -563,7 +562,7 @@ export function useForceUpdate() { Hook to re-render on Dome events (Custom React Hook). @param events - event names, defaults to a single `'dome.update'`. */ -export function useUpdate(...events: Event<any>[]) { +export function useUpdate(...events: Event<any>[]): void { const fn = useForceUpdate(); React.useEffect(() => { const theEvents = events ? events.slice() : [update]; @@ -572,6 +571,12 @@ export function useUpdate(...events: Event<any>[]) { }); } +interface PromiseHook<A> { + result: A | undefined; + error: Error | undefined; + loading: boolean; +} + /** Hook to re-render when a Promise returns. The promise will be typically created by using `React.useMemo()`. @@ -580,15 +585,18 @@ export function useUpdate(...events: Event<any>[]) { - error: the promise error if it fails, undefined otherwise; - loading: the promise status, true if the promise is still running. */ -export function usePromise<T>(job: Promise<T>) { - const [result, setResult] = React.useState<T | undefined>(); +export function usePromise<A>(job: Promise<A>): PromiseHook<A> { + const [result, setResult] = React.useState<A | undefined>(); const [error, setError] = React.useState<Error | undefined>(); const [loading, setLoading] = React.useState(true); React.useEffect(() => { let cancel = false; - const doCancel = () => { if (!cancel) setLoading(false); return cancel; }; - const onResult = (x: T) => { if (!doCancel()) setResult(x); }; - const onError = (e: Error) => { if (!doCancel()) setError(e); }; + const doCancel = (): boolean => { + if (!cancel) setLoading(false); + return cancel; + }; + const onResult = (x: A): void => { if (!doCancel()) setResult(x); }; + const onError = (e: Error): void => { if (!doCancel()) setError(e); }; job.then(onResult, onError); return () => { cancel = true; }; }, [job]); @@ -633,9 +641,9 @@ interface Clock { // Collection of clocks indexed by period const CLOCKS = new Map<number, Clock>(); -const CLOCKEVENT = (period: number) => `dome.clock.${period}`; +const CLOCKEVENT = (period: number): string => `dome.clock.${period}`; -const TIC_CLOCK = (clk: Clock) => () => { +const TIC_CLOCK = (clk: Clock) => (): void => { if (0 < clk.pending) { clk.time += clk.period; System.emitter.emit(clk.event, clk.time); @@ -645,7 +653,7 @@ const TIC_CLOCK = (clk: Clock) => () => { } }; -const INC_CLOCK = (period: number) => { +const INC_CLOCK = (period: number): string => { let clk = CLOCKS.get(period); if (!clk) { const event = CLOCKEVENT(period); @@ -658,7 +666,7 @@ const INC_CLOCK = (period: number) => { return clk.event; }; -const DEC_CLOCK = (period: number) => { +const DEC_CLOCK = (period: number): void => { const clk = CLOCKS.get(period); if (clk) clk.pending--; }; @@ -700,7 +708,7 @@ export function useClock(period: number, initStart: boolean): Timer { React.useEffect(() => { if (running) { const event = INC_CLOCK(period); - const callback = (t: number) => { + const callback = (t: number): void => { if (!started.current) started.current = t; else setTime(t - started.current); }; @@ -724,7 +732,7 @@ export function useClock(period: number, initStart: boolean): Timer { export function useBoolSettings( key: string | undefined, defaultValue = false, -) { +): State<boolean> { return Settings.useWindowSettings( key, Json.jBoolean, defaultValue, ); @@ -754,14 +762,19 @@ export function useNumberSettings( } /** String window settings. Default is `''` unless specified). */ -export function useStringSettings(key: string | undefined, defaultValue = '') { +export function useStringSettings( + key: string | undefined, + defaultValue = '' +): State<string> { return Settings.useWindowSettings( key, Json.jString, defaultValue, ); } /** Optional string window settings. Default is `undefined`. */ -export function useStringOptSettings(key: string | undefined) { +export function useStringOptSettings( + key: string | undefined +): State<string | undefined> { return Settings.useWindowSettings( key, Json.jString, undefined, ); @@ -778,7 +791,7 @@ export function useGlobalSettings<A extends Json.json>( globalKey: string, decoder: Json.Loose<A>, defaultValue: A, -) { +): State<A> { // Object creation is cheaper than useMemo... const G = new Settings.GlobalSettings( globalKey, decoder, Json.identity, defaultValue, @@ -798,19 +811,19 @@ export class Debug { /* eslint-disable no-console */ - log(...args: any) { + log(...args: any): void { if (DEVEL) console.log(`[${this.moduleName}]`, ...args); } - warn(...args: any) { + warn(...args: any): void { if (DEVEL) console.warn(`[${this.moduleName}]`, ...args); } - error(...args: any) { + error(...args: any): void { if (DEVEL) console.error(`[${this.moduleName}]`, ...args); } - /* eslint-enable */ + /* eslint-enable no-console */ } // -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/frame/toolbars.tsx b/ivette/src/dome/renderer/frame/toolbars.tsx index 9d7e9f196a9500a5e50299b99b956b5db906ada4..4693b33655e2c95e10dfaf1df0c4239e8f82e225 100644 --- a/ivette/src/dome/renderer/frame/toolbars.tsx +++ b/ivette/src/dome/renderer/frame/toolbars.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- ToolBars // -------------------------------------------------------------------------- @@ -52,7 +50,7 @@ export interface ToolBarProps { @class @summary Container for toolbar items. */ -export function ToolBar(props: ToolBarProps) { +export function ToolBar(props: ToolBarProps): JSX.Element | null { const { children } = props; const n = React.Children.count(children); if (n === 0) return null; @@ -75,16 +73,22 @@ export function ToolBar(props: ToolBarProps) { // -------------------------------------------------------------------------- /** Fixed (tiny) space. */ -export const Inset = (() => <div className="dome-xToolBar-inset" />); +export const Inset = (): JSX.Element => ( + <div className="dome-xToolBar-inset" /> +); /** Fixed space. */ -export const Space = (() => <div className="dome-xToolBar-space" />); +export const Space = (): JSX.Element => ( + <div className="dome-xToolBar-space" /> +); /** Auto-extensible space. */ -export const Filler = (() => <div className="dome-xToolBar-filler" />); +export const Filler = (): JSX.Element => ( + <div className="dome-xToolBar-filler" /> +); /** Fixed space with vertical rule. */ -export const Separator = () => ( +export const Separator = (): JSX.Element => ( <div className="dome-xToolBar-separator"> <div className="dome-xToolBar-vrule" /> </div> @@ -92,7 +96,7 @@ export const Separator = () => ( const SELECT = 'dome-xToolBar-control dome-selected'; const BUTTON = 'dome-xToolBar-control dome-color-frame'; -const KIND = (kind: undefined | string) => ( +const KIND = (kind: undefined | string): string => ( kind ? ` dome-xToolBar-${kind}` : '' ); @@ -129,7 +133,9 @@ export interface ButtonProps<A> { } /** Toolbar Button. */ -export function Button<A = undefined>(props: ButtonProps<A>) { +export function Button<A = undefined>( + props: ButtonProps<A> +): JSX.Element | null { const { visible = true, hidden = false } = props; if (!visible || hidden) return null; const { enabled = true, disabled = false } = props; @@ -170,7 +176,7 @@ export interface SwitchProps { } /** Toolbar Left/Right Switch. */ -export function Switch(props: SwitchProps) { +export function Switch(props: SwitchProps): JSX.Element | null { const { position, onChange } = props; const checked = position === 'right'; const { title, className, style } = props; @@ -217,7 +223,7 @@ export interface ButtonGroupProps<A> extends SelectionProps<A> { Properties of the button group are passed down the buttons of the group as appropriate defaults. */ -export function ButtonGroup<A>(props: ButtonGroupProps<A>) { +export function ButtonGroup<A>(props: ButtonGroupProps<A>): JSX.Element { const { children, value, onChange, enabled, disabled } = props; const baseProps: ButtonProps<A> = { enabled, @@ -247,11 +253,12 @@ export function ButtonGroup<A>(props: ButtonGroupProps<A>) { The list of options shall be given with standard `<option value={...} label={...}>` elements. */ -export function Select(props: SelectionProps<string>) { +export function Select(props: SelectionProps<string>): JSX.Element { const { enabled = true, disabled = false, onChange } = props; - const callback = (evt: React.ChangeEvent<HTMLSelectElement>) => { - if (onChange) onChange(evt.target.value); - }; + const callback = + (evt: React.ChangeEvent<HTMLSelectElement>): void => { + if (onChange) onChange(evt.target.value); + }; return ( <select className="dome-xToolBar-control dome-color-frame" @@ -279,7 +286,7 @@ export interface Hint { } /** Total order on hints. */ -export function byHint(a: Hint, b: Hint) { +export function byHint(a: Hint, b: Hint): number { return (a.rank ?? 0) - (b.rank ?? 0); } @@ -315,15 +322,18 @@ export interface ActionMode { const searchEvaluators = new Map<string, HintsEvaluator>(); // Updates to the new evaluator if the id is already registered -export function registerSearchHints(id: string, search: HintsEvaluator) { +export function registerSearchHints( + id: string, + search: HintsEvaluator +): void { searchEvaluators.set(id, search); } -export function unregisterSearchHints(id: string) { +export function unregisterSearchHints(id: string): void { searchEvaluators.delete(id); } -async function searchHints(pattern: string) { +async function searchHints(pattern: string): Promise<Hint[]> { if (pattern === '') return []; const promises = Array.from(searchEvaluators).map(([_id, E]) => E(pattern)); const hints = await Promise.all(promises); @@ -349,7 +359,7 @@ interface ModeButtonComponentProps { onClick: () => void; } -function ModeButton(props: ModeButtonComponentProps) { +function ModeButton(props: ModeButtonComponentProps): JSX.Element { const { current, onClick } = props; return ( <div @@ -375,11 +385,11 @@ interface SuggestionsProps { index: number; } -function scrollToRef (r: null | HTMLLabelElement) { +function scrollToRef(r: null | HTMLLabelElement): void { if (r) r.scrollIntoView({ block: 'nearest' }); } -function Suggestions(props: SuggestionsProps) { +function Suggestions(props: SuggestionsProps): JSX.Element { const { hints, onHint, index } = props; // Computing the relevant suggestions. */ @@ -406,7 +416,7 @@ function Suggestions(props: SuggestionsProps) { <div style={{ visibility: suggestions.length > 0 ? 'visible' : 'hidden' }} className='dome-xToolBar-suggestions' - onMouseDown={ (event) => event.preventDefault() } + onMouseDown={(event) => event.preventDefault()} > {suggestions} </div> @@ -430,16 +440,16 @@ interface ActionInputProps { inputRef: React.MutableRefObject<HTMLInputElement | null>; } -function ActionInput(props: ActionInputProps) { +function ActionInput(props: ActionInputProps): JSX.Element { const { title, placeholder, hints, onHint, onEnter } = props; const { index, setIndex, pattern, setPattern, inputRef } = props; // Blur Event - const onBlur = () => { setPattern(''); setIndex(-1); }; + const onBlur = (): void => { setPattern(''); setIndex(-1); }; // Key Up Events - const onKeyUp = (evt: React.KeyboardEvent) => { - const blur = () => inputRef.current?.blur(); + const onKeyUp = (evt: React.KeyboardEvent): void => { + const blur = (): void => inputRef.current?.blur(); switch (evt.key) { case 'Escape': blur(); @@ -462,12 +472,12 @@ function ActionInput(props: ActionInputProps) { }; // Key Down Events. Disables the default behavior on ArrowUp and ArrowDown. - const onKeyDown = (evt: React.KeyboardEvent) => { + const onKeyDown = (evt: React.KeyboardEvent): void => { if (evt.key === 'ArrowUp' || evt.key === 'ArrowDown') evt.preventDefault(); }; // // Input Events - const onChange = (evt: React.ChangeEvent<HTMLInputElement>) => { + const onChange = (evt: React.ChangeEvent<HTMLInputElement>): void => { setIndex(-1); setPattern(evt.target.value); }; @@ -497,27 +507,29 @@ export const RegisterMode: Event<ActionMode> = export const UnregisterMode: Event<ActionMode> = new Event('dome.actionmode.unregister'); -export function ModalActionField() { - +export function ModalActionField(): JSX.Element { + // Internal state of the component along with useful functions acting on it. const inputRef = React.useRef<HTMLInputElement | null>(null); const [index, setIndex] = React.useState(-1); const [pattern, setPattern] = React.useState(''); const [current, onModeChange] = React.useState<ActionMode>(searchMode); - const focus = () => inputRef.current?.focus(); - const changeMode = (m: ActionMode) => () => { onModeChange(m); focus(); }; - const toDefault = () => onModeChange(searchMode); - const reset = (m: ActionMode) => { if (current === m) toDefault(); }; + const focus = (): void => inputRef.current?.focus(); + const changeMode = (m: ActionMode) => + (): void => { onModeChange(m); focus(); }; + const toDefault = (): void => onModeChange(searchMode); + const reset = (m: ActionMode): void => { if (current === m) toDefault(); }; // Set of all modes currently active. We populate it by reacting to // RegisterMode and UnregisterMode events. We also activate the mode event if // available. Everything is cleaned when the component is unmounted. - const [allModes] = React.useState<Set<ActionMode>>(new Set()); + const [allModes] = React.useState<Set<ActionMode>>(new Set()); React.useEffect(() => { - const on = (m: ActionMode) => m.event?.on(changeMode(m)); - const register = (m: ActionMode) => { allModes.add(m); on(m); }; - const off = (m: ActionMode) => m.event?.off(changeMode(m)); - const remove = (m: ActionMode) => { allModes.delete(m); off(m); reset(m); }; + const on = (m: ActionMode): void => m.event?.on(changeMode(m)); + const register = (m: ActionMode): void => { allModes.add(m); on(m); }; + const off = (m: ActionMode): void => m.event?.off(changeMode(m)); + const remove = + (m: ActionMode): void => { allModes.delete(m); off(m); reset(m); }; RegisterMode.on(register); UnregisterMode.on(remove); return () => { RegisterMode.off(register); UnregisterMode.off(remove); }; }); @@ -531,17 +543,17 @@ export function ModalActionField() { const { result = [] } = usePromise(hintsPromise); // Auxiliary function that build a Hint from an ActionMode. - const modeToHint = (mode: ActionMode) => { + const modeToHint = (mode: ActionMode): Hint => { const { label, title = '', icon } = mode; const id = 'ActionMode-' + title + '-' + icon; - const value = () => { onModeChange(mode); }; + const value = (): void => { onModeChange(mode); }; return { id, icon, label, title, value, rank: -1000 }; }; // Hints provider for the mode of all modes. const modesHints = React.useCallback((input: string) => { const p = input.toLowerCase(); - const fit = (m: ActionMode) => m.label.toLowerCase().includes(p); + const fit = (m: ActionMode): boolean => m.label.toLowerCase().includes(p); return Promise.resolve(Array.from(allModes).filter(fit).map(modeToHint)); }, [allModes]); @@ -560,20 +572,20 @@ export function ModalActionField() { // the possible search hints. const searchModeHints = React.useCallback(async (input: string) => { const hs = await modesMode.hints(input); - const notCurrent = (h: Hint) => !(h.label.includes(current.label)); + const notCurrent = (h: Hint): boolean => !(h.label.includes(current.label)); return hs.filter(notCurrent); }, [current.label, modesMode]); // Register the new search engine. React.useEffect(() => { - registerSearchHints('ModesMode', searchModeHints); - return () => unregisterSearchHints('ModesMode'); - }, [searchModeHints]); + registerSearchHints('ModesMode', searchModeHints); + return () => unregisterSearchHints('ModesMode'); + }, [searchModeHints]); // Build the component. const { title, placeholder } = current; const handleModeClick = changeMode(modesMode); - const onBlur = () => reset(modesMode); + const onBlur = (): void => reset(modesMode); return ( <div className="dome-xToolBar-actionComponent" onBlur={onBlur}> <div className="dome-xToolBar-actionField"> @@ -592,7 +604,7 @@ export function ModalActionField() { /> </div> <Suggestions hints={result} onHint={onHint} index={index} /> - </div> + </div> ); } diff --git a/ivette/src/dome/renderer/layout/boxes.tsx b/ivette/src/dome/renderer/layout/boxes.tsx index 685be04352af54c1e285ad59b61af0dd71da63f3..f0cc5d41d803bf19157e73207e601185aded8bbc 100644 --- a/ivette/src/dome/renderer/layout/boxes.tsx +++ b/ivette/src/dome/renderer/layout/boxes.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Box Layout // -------------------------------------------------------------------------- @@ -78,7 +76,7 @@ const makeBox = ( boxClasses: string, props: DivProps, morestyle?: React.CSSProperties, -) => { +): JSX.Element => { const { children, className, style, ...others } = props; const allClasses = classes(className, boxClasses); const allStyles = styles(style, morestyle); @@ -97,37 +95,43 @@ const makeBox = ( Horizontal box (extends horizontally, no overflow). */ export const Hbox = - (props: DivProps) => makeBox('dome-xBoxes-hbox dome-xBoxes-box', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-hbox dome-xBoxes-box', props); /** Vertical box (extends vertically, no overflow). -*/ + */ export const Vbox = - (props: DivProps) => makeBox('dome-xBoxes-vbox dome-xBoxes-box', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-vbox dome-xBoxes-box', props); /** Compact Horizontal box (fixed dimensions, no overflow). -*/ + */ export const Hpack = - (props: DivProps) => makeBox('dome-xBoxes-hbox dome-xBoxes-pack', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-hbox dome-xBoxes-pack', props); /** Compact Vertical box (fixed dimensions, no overflow). -*/ + */ export const Vpack = - (props: DivProps) => makeBox('dome-xBoxes-vbox dome-xBoxes-pack', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-vbox dome-xBoxes-pack', props); /** Horizontally filled box (fixed height, maximal width, no overflow). -*/ + */ export const Hfill = - (props: DivProps) => makeBox('dome-xBoxes-hbox dome-xBoxes-fill', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-hbox dome-xBoxes-fill', props); /** Vertically filled box (fixed width, maximal height, no overflow). -*/ + */ export const Vfill = - (props: DivProps) => makeBox('dome-xBoxes-vbox dome-xBoxes-fill', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-vbox dome-xBoxes-fill', props); // -------------------------------------------------------------------------- // --- Scrolling & Spacing @@ -135,21 +139,22 @@ export const Vfill = /** Scrolling container. -*/ + */ export const Scroll = - (props: DivProps) => makeBox('dome-xBoxes-scroll dome-container', props); + (props: DivProps): JSX.Element => + makeBox('dome-xBoxes-scroll dome-container', props); /** Rigid space between items in a box. -*/ + */ export const Space = - (props: DivProps) => makeBox('dome-xBoxes-space', props); + (props: DivProps): JSX.Element => makeBox('dome-xBoxes-space', props); /** Extensible space between items in a box. -*/ + */ export const Filler = - (props: DivProps) => makeBox('dome-xBoxes-filler', props); + (props: DivProps): JSX.Element => makeBox('dome-xBoxes-filler', props); // -------------------------------------------------------------------------- // --- Grids @@ -168,8 +173,8 @@ export interface GridProps extends DivProps { columns?: string } properties. Example: `<Grid columns="25% auto auto"> ... </Grid>` -*/ -export const Grid = (props: GridProps) => { + */ +export const Grid = (props: GridProps): JSX.Element => { const { columns, ...others } = props; return makeBox('dome-xBoxes-grid', others, { gridTemplateColumns: columns }); }; @@ -197,7 +202,7 @@ export interface FolderProps { Foldable (vertical, packed) box. The head label is clickable to fold/unfold its contents. */ -export const Folder = (props: FolderProps) => { +export const Folder = (props: FolderProps): JSX.Element => { const { settings, defaultUnfold = false, diff --git a/ivette/src/dome/renderer/layout/dispatch.tsx b/ivette/src/dome/renderer/layout/dispatch.tsx index 7cecf5b9ae63fd73496e0605123ee04e0f054998..6dddb84135198cccc947cbf50d4153ed9444bc7e 100644 --- a/ivette/src/dome/renderer/layout/dispatch.tsx +++ b/ivette/src/dome/renderer/layout/dispatch.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Dispatch Layout // -------------------------------------------------------------------------- @@ -97,7 +95,7 @@ export interface ElementProps { Multiple definitions of the same element might produce unpredictable results. */ -export function DefineElement(props: ElementProps) { +export function DefineElement(props: ElementProps): JSX.Element | null { React.useEffect(() => { const item = getItem(props.id); item.update(props.children); @@ -122,14 +120,14 @@ export interface RenderProps { of the item, or `undefined` if there is no identifier or no corresponding `<DefineElement />` currently mounted. */ -export function RenderElement(props: RenderProps) { +export function RenderElement(props: RenderProps): JSX.Element { const [age, setAge] = React.useState(0); const { id, children } = props; const item = id ? getItem(id) : undefined; React.useEffect(() => { if (item) { const { event } = item; - const trigger = () => setAge(age + 1); + const trigger = (): void => setAge(age + 1); event.on(trigger); return () => event.off(trigger); } @@ -137,9 +135,9 @@ export function RenderElement(props: RenderProps) { }, [age, item]); if (item) item.rendered = true; if (typeof (children) === 'function') - return children(item?.content); - const content = item?.content || children; - return content; + return <>{children(item?.content)}</>; + const content = item?.content || children || null; + return <>{content}</>; } // -------------------------------------------------------------------------- diff --git a/ivette/src/dome/renderer/layout/forms.tsx b/ivette/src/dome/renderer/layout/forms.tsx index 135b474d5a9b13fa9e74c85d0b591a4568aafa39..4a949877069b3634abf50278f71f907965a17604 100644 --- a/ivette/src/dome/renderer/layout/forms.tsx +++ b/ivette/src/dome/renderer/layout/forms.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - /* --------------------------------------------------------------------------*/ /* --- Form Fields ---*/ /* --------------------------------------------------------------------------*/ @@ -373,10 +371,12 @@ interface FormContext { const CONTEXT = React.createContext<FormContext | undefined>(undefined); const HIDDEN = - ({ hidden = false, visible = true }: FilterProps) => hidden || !visible; + ({ hidden = false, visible = true }: FilterProps): boolean => + hidden || !visible; const DISABLED = - ({ disabled = false, enabled = true }: FilterProps) => disabled || !enabled; + ({ disabled = false, enabled = true }: FilterProps): boolean => + disabled || !enabled; function useContext(props?: FilterProps): FormContext { const Parent = React.useContext(CONTEXT); @@ -390,7 +390,7 @@ function useContext(props?: FilterProps): FormContext { Allow to locally disable or hide all its children fields. @category Form Containers */ -export function FormFilter(props: FilterProps & Children) { +export function FormFilter(props: FilterProps & Children): JSX.Element | null { const context = useContext(props); if (context.hidden) return null; return ( @@ -416,7 +416,7 @@ export interface PageProps extends FilterProps, Children { Main Form Container. @category Form Containers */ -export function Page(props: PageProps) { +export function Page(props: PageProps): JSX.Element | null { const { className, style, children, ...filter } = props; const { hidden, disabled } = useContext(filter); const css = Utils.classes(className ?? 'dome-xForm-grid'); @@ -444,7 +444,7 @@ export interface WarningProps { } /** Warning Badge. */ -export function Warning(props: WarningProps) { +export function Warning(props: WarningProps): JSX.Element { const { warning, error, offset = 0 } = props; const DETAILS = typeof error === 'string' ? error : undefined; const WARNING = warning && ( @@ -472,7 +472,7 @@ export function Warning(props: WarningProps) { Layout its contents inside a full-width container. @category Form Containers */ -export function FormBlock(props: FilterProps & Children) { +export function FormBlock(props: FilterProps & Children): JSX.Element { const { children, ...filter } = props; return ( <FormFilter {...filter}> @@ -504,7 +504,7 @@ export interface SectionProps extends FilterProps, Children { } /** @category Form Fields */ -export function Section(props: SectionProps) { +export function Section(props: SectionProps): JSX.Element | null { const { label, title, children, warning, error, ...filter } = props; const { disabled, hidden } = useContext(filter); const [unfold, flip] = Dome.useFlipSettings(props.settings, props.unfold); @@ -558,8 +558,8 @@ export interface GenericFieldProps extends FilterProps, Children { let FIELDID = 0; /** Generates a unique, stable identifier. */ -export function useHtmlFor() { - return React.useMemo(() => `dome-field ${FIELDID++}`, []); +export function useHtmlFor(): string { + return React.useMemo(() => `dome-field-${FIELDID++}`, []); } /** @@ -567,7 +567,7 @@ export function useHtmlFor() { Layout its content in a top-left aligned box on the right of the label. @category Form Fields */ -export function Field(props: GenericFieldProps) { +export function Field(props: GenericFieldProps): JSX.Element | null { const { hidden, disabled } = useContext(props); if (hidden) return null; @@ -630,7 +630,8 @@ export interface FieldProps<A> extends FilterProps { type InputEvent = { target: { value: string } }; type InputState = [string, FieldError, (evt: InputEvent) => void]; -function useChangeEvent(setState: Callback<string>) { +function useChangeEvent(setState: Callback<string>) + : ((evt: InputEvent) => void) { return React.useCallback( (evt: InputEvent) => { setState(evt.target.value, undefined); }, [setState], @@ -664,7 +665,7 @@ function useTextInputField( Text Field. @category Text Fields */ -export function TextField(props: TextFieldProps) { +export function TextField(props: TextFieldProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const css = Utils.classes('dome-xForm-text-field', props.className); @@ -694,7 +695,7 @@ export function TextField(props: TextFieldProps) { Monospaced Text Field. @category Text Fields */ -export function TextCodeField(props: TextFieldProps) { +export function TextCodeField(props: TextFieldProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const [value, error, onChange] = useTextInputField(props, 600); @@ -740,7 +741,7 @@ export interface TextFieldAreaProps extends TextFieldProps { Text Field Area. @category Text Fields */ -export function TextFieldArea(props: TextFieldAreaProps) { +export function TextFieldArea(props: TextFieldAreaProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const [value, error, onChange] = useTextInputField(props, 900); @@ -778,7 +779,7 @@ export function TextFieldArea(props: TextFieldAreaProps) { Monospaced Text Field Area. @category Text Fields */ -export function TextCodeFieldArea(props: TextFieldAreaProps) { +export function TextCodeFieldArea(props: TextFieldAreaProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const [value, error, onChange] = useTextInputField(props, 900); @@ -843,7 +844,7 @@ function NUMBER_OF_TEXT(s: string): number | undefined { Text Field for Numbers. @category Number Fields */ -export function NumberField(props: NumberFieldProps) { +export function NumberField(props: NumberFieldProps): JSX.Element { const { units, latency = 600 } = props; const { disabled } = useContext(props); const id = useHtmlFor(); @@ -895,7 +896,7 @@ export interface SpinnerFieldProps extends NumberFieldProps { Spinner Field @category Number Fields */ -export function SpinnerField(props: SpinnerFieldProps) { +export function SpinnerField(props: SpinnerFieldProps): JSX.Element { const { units, min, max, step = 1, latency = 600, checker } = props; const { disabled } = useContext(props); const id = useHtmlFor(); @@ -962,14 +963,15 @@ export interface SliderFieldProps extends FieldProps<number> { latency?: number; } -const FORMAT_VALUE = (v: number) => Number(v).toString(); -const FORMAT_RANGE = (v: number) => (v > 0 ? `+${v}` : `-${-v}`); -const FORMATING = (props: SliderFieldProps) => { - const { labelValue = true, min } = props; - if (labelValue === false) return undefined; - if (labelValue === true) return min < 0 ? FORMAT_RANGE : FORMAT_VALUE; - return labelValue; -}; +const FORMAT_VALUE = (v: number): string => Number(v).toString(); +const FORMAT_RANGE = (v: number): string => (v > 0 ? `+${v}` : `-${-v}`); +const FORMATING = + (props: SliderFieldProps): (undefined | ((v: number) => string)) => { + const { labelValue = true, min } = props; + if (labelValue === false) return undefined; + if (labelValue === true) return min < 0 ? FORMAT_RANGE : FORMAT_VALUE; + return labelValue; + }; const CSS_SLIDER = 'dome-text-label dome-xForm-units dome-xForm-slider-value'; const SHOW_SLIDER = `${CSS_SLIDER} dome-xForm-slider-show`; @@ -979,7 +981,7 @@ const HIDE_SLIDER = `${CSS_SLIDER} dome-xForm-slider-hide`; Slider Field @category Number Fields */ -export function SliderField(props: SliderFieldProps) { +export function SliderField(props: SliderFieldProps): JSX.Element { const { min, max, step = 1, latency = 600, onReset } = props; const { disabled } = useContext(props); const id = useHtmlFor(); @@ -1062,7 +1064,7 @@ export interface TimeOrDateFieldProps extends FieldProps<string | undefined> { @category Time and Date Fields */ -export function DateField(props: TimeOrDateFieldProps) { +export function DateField(props: TimeOrDateFieldProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const css = Utils.classes('dome-xForm-date-field', props.className); @@ -1098,7 +1100,7 @@ export function DateField(props: TimeOrDateFieldProps) { @category Time and Date Fields */ -export function TimeField(props: TimeOrDateFieldProps) { +export function TimeField(props: TimeOrDateFieldProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const css = Utils.classes('dome-xForm-date-field', props.className); @@ -1132,7 +1134,7 @@ export function TimeField(props: TimeOrDateFieldProps) { export type ColorFieldProps = FieldProps<string | undefined>; /** @category Form Fields */ -export function ColorField(props: ColorFieldProps) { +export function ColorField(props: ColorFieldProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const [value, error, onChange] = useTextInputField(props, 600); @@ -1165,7 +1167,7 @@ export interface CheckboxFieldProps extends FieldProps<boolean> { } /** @category Form Fields */ -export function CheckboxField(props: CheckboxFieldProps) { +export function CheckboxField(props: CheckboxFieldProps): JSX.Element | null { const { hidden, disabled } = useContext(props); if (hidden) return null; @@ -1176,7 +1178,7 @@ export function CheckboxField(props: CheckboxFieldProps) { 'dome-xForm-field dome-text-label', disabled && 'dome-disabled', ); - const onChange = () => setState(!value, undefined); + const onChange = (): void => setState(!value, undefined); return ( <Checkbox className={css} @@ -1199,13 +1201,13 @@ export interface RadioFieldProps<A> extends FieldProps<A> { } /** @category Form Fields */ -export function RadioField<A>(props: RadioFieldProps<A>) { +export function RadioField<A>(props: RadioFieldProps<A>): JSX.Element | null { const { hidden, disabled } = useContext(props); if (hidden) return null; const [selection, , setState] = props.state; - const onSelection = (value: A) => setState(value, undefined); + const onSelection = (value: A): void => setState(value, undefined); const { label, title, value } = props; const css = Utils.classes( 'dome-xForm-field dome-text-label', @@ -1240,11 +1242,12 @@ export interface SelectFieldProps extends FieldProps<string | undefined> { @category Form Fields */ -export function SelectField(props: SelectFieldProps) { +export function SelectField(props: SelectFieldProps): JSX.Element { const { disabled } = useContext(props); const id = useHtmlFor(); const [value, error, setState] = useChecker(props.state, props.checker); - const onChange = (newValue?: string) => setState(newValue, undefined); + const onChange = + (newValue: string | undefined): void => setState(newValue, undefined); const { children, placeholder } = props; return ( <Field diff --git a/ivette/src/dome/renderer/layout/splitters.tsx b/ivette/src/dome/renderer/layout/splitters.tsx index a5fb604eafbf6da56e87951bf981f531b2932483..e56f47dd345caba272617b45e6cb2071ffaf9edc 100644 --- a/ivette/src/dome/renderer/layout/splitters.tsx +++ b/ivette/src/dome/renderer/layout/splitters.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Splitters // -------------------------------------------------------------------------- @@ -129,7 +127,7 @@ type CSS = { split: string; }; -const getFlexCSS = (hsplit: boolean, fold: boolean) => ( +const getFlexCSS = (hsplit: boolean, fold: boolean): string => ( hsplit ? (fold ? HFOLD : HPANE) : (fold ? VFOLD : VPANE) ); @@ -189,7 +187,7 @@ function getSettingsFromPosition(L: Layout, P: number, D: number): number { return P / D; } -const inRange = (M: number, D: number, P: number) => ( +const inRange = (M: number, D: number, P: number): number => ( D < M ? D / 2 : Math.min(Math.max(P, M), D - M) ); @@ -200,7 +198,7 @@ const inRange = (M: number, D: number, P: number) => ( interface SplitterLayoutProps extends SplitterFoldProps { layout: Layout } interface SplitterEngineProps extends SplitterLayoutProps { size: Size } -function SplitterEngine(props: SplitterEngineProps) { +function SplitterEngine(props: SplitterEngineProps): JSX.Element { const defaultPosition = props.defaultPosition ?? 0; const [settings, setSettings] = Dome.useNumberSettings(props.settings, defaultPosition); @@ -300,7 +298,7 @@ function SplitterEngine(props: SplitterEngineProps) { ); } -const SplitterLayout = (props: SplitterLayoutProps) => ( +const SplitterLayout = (props: SplitterLayoutProps): JSX.Element => ( <div className={CONTAINER}> <AutoSizer> {(size: Size) => ( @@ -335,39 +333,39 @@ const getLayout = (d: Direction): Layout => { /** Splitter with specified direction. @category Base Component */ -export function Splitter(props: SplitterDirProps) { +export function Splitter(props: SplitterDirProps): JSX.Element { const { direction, ...others } = props; const layout = getLayout(direction); return <SplitterLayout layout={layout} {...others} />; } /** Horizontal Splitter. */ -export function HSplit(props: SplitterBaseProps) { +export function HSplit(props: SplitterBaseProps): JSX.Element { return <SplitterLayout layout={HLayout} {...props} />; } /** Vertical Splitter. */ -export function VSplit(props: SplitterBaseProps) { +export function VSplit(props: SplitterBaseProps): JSX.Element { return <SplitterLayout layout={VLayout} {...props} />; } /** Horizontal Splitter with stacked and foldable left element. */ -export function LSplit(props: SplitterFoldProps) { +export function LSplit(props: SplitterFoldProps): JSX.Element { return <SplitterLayout layout={LLayout} {...props} />; } /** Horizontal Splitter with stacked and foldable right element. */ -export function RSplit(props: SplitterFoldProps) { +export function RSplit(props: SplitterFoldProps): JSX.Element { return <SplitterLayout layout={RLayout} {...props} />; } /** Vertical Splitter with stacked and foldable top element. */ -export function TSplit(props: SplitterFoldProps) { +export function TSplit(props: SplitterFoldProps): JSX.Element { return <SplitterLayout layout={TLayout} {...props} />; } /** Vertical Splitter with stacked and foldable bottom element. */ -export function BSplit(props: SplitterFoldProps) { +export function BSplit(props: SplitterFoldProps): JSX.Element { return <SplitterLayout layout={BLayout} {...props} />; } diff --git a/ivette/src/dome/renderer/table/arrays.ts b/ivette/src/dome/renderer/table/arrays.ts index 4d084ff3f17dac3dfdedd43edd91068ab1136266..82b0ff01c9fd96d39b10176fab61b2e61522739c 100644 --- a/ivette/src/dome/renderer/table/arrays.ts +++ b/ivette/src/dome/renderer/table/arrays.ts @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Array Models // -------------------------------------------------------------------------- @@ -64,8 +62,8 @@ function orderBy<K, R>( const byData = columns[dataKey] ?? Compare.equal; const rv = ord.sortDirection === 'DESC'; type D = PACK<K, R>; - const byEntry = (x: D, y: D) => byData(x.row, y.row); - const byIndex = (x: D, y: D) => (x.index ?? 0) - (y.index ?? 0); + const byEntry = (x: D, y: D): number => byData(x.row, y.row); + const byIndex = (x: D, y: D): number => (x.index ?? 0) - (y.index ?? 0); return Compare.direction(Compare.sequence(byEntry, byIndex), rv); } @@ -165,20 +163,20 @@ export class ArrayModel<Key, Row> // -------------------------------------------------------------------------- /** Non filtered. */ - getTotalRowCount() { return this.getRowCount() + this.filtered; } + getTotalRowCount(): number { return this.getRowCount() + this.filtered; } - getRowCount() { return this.rebuild().length; } + getRowCount(): number { return this.rebuild().length; } - getRowAt(k: number) { return this.rebuild()[k]?.row; } + getRowAt(k: number): Row { return this.rebuild()[k]?.row; } - getKeyAt(k: number) { + getKeyAt(k: number): Key | undefined { const current = this.table; return current ? current[k]?.key : undefined; } - getKeyFor(k: number, _: Row) { return this.getKeyAt(k); } + getKeyFor(k: number, _: Row): Key | undefined { return this.getKeyAt(k); } - getIndexOf(key: Key) { + getIndexOf(key: Key): number | undefined { const pack = this.index.get(key); if (!pack) return undefined; const k = pack.index; @@ -197,7 +195,7 @@ export class ArrayModel<Key, Row> [[setNaturalOrder]] in response to user column selection with [[setSorting]] provided you enable by-column sorting from the table view. Finally triggers a reload. */ - setColumnOrder(columns?: ByColumns<Row>) { + setColumnOrder(columns?: ByColumns<Row>): void { this.columns = { ...this.columns, ...columns }; this.reload(); } @@ -207,7 +205,7 @@ export class ArrayModel<Key, Row> primary ordering can be refined in response to user column selection with [[setSorting]] provided you enable by-column sorting from the table view. Finally triggers a reload. */ - setNaturalOrder(order?: Order<Row>) { + setNaturalOrder(order?: Order<Row>): void { this.natural = order; this.reload(); } @@ -217,7 +215,7 @@ export class ArrayModel<Key, Row> orders by fields. This is a combination of [[setColumnOrder]] and [[setNaturalOrder]] with [[dome/data/compare.byFields]]. */ - setOrderingByFields(byfields: ByFields<Row>) { + setOrderingByFields(byfields: ByFields<Row>): void { this.natural = Compare.byFields(byfields); const columns = this.columns ?? {}; const keys = Object.keys(byfields); @@ -240,7 +238,7 @@ export class ArrayModel<Key, Row> /** Remove the sorting function for the provided column. */ - deleteColumnOrder(dataKey: string) { + deleteColumnOrder(dataKey: string): void { const { columns } = this; if (columns) delete columns[dataKey]; this.ring = this.ring.filter((ord) => ord.sortBy !== dataKey); @@ -250,7 +248,7 @@ export class ArrayModel<Key, Row> /** Reorder rows with the provided column and direction. Previous ordering is kept and refined by the new one. Use `undefined` or `null` to reset the natural ordering. */ - setSorting(ord?: undefined | null | SortingInfo) { + setSorting(ord?: undefined | null | SortingInfo): void { if (ord) { const { ring } = this; const cur = ring[0]; @@ -284,7 +282,7 @@ export class ArrayModel<Key, Row> // --- Filtering // -------------------------------------------------------------------------- - setFilter(fn?: Filter<Key, Row>) { + setFilter(fn?: Filter<Key, Row>): void { const phi = this.filter; if (phi !== fn) { this.filter = fn; @@ -297,7 +295,7 @@ export class ArrayModel<Key, Row> // -------------------------------------------------------------------------- /** Trigger a complete reload of the table. */ - reload() { + reload(): void { this.array = undefined; this.table = undefined; this.order = undefined; @@ -305,7 +303,7 @@ export class ArrayModel<Key, Row> } /** Remove all data and reload. */ - clear() { + clear(): void { this.index.clear(); this.reload(); } @@ -370,7 +368,7 @@ export class ArrayModel<Key, Row> @param key - the entry identifier @param row - new value of `null` for removal */ - update(key: Key, row?: undefined | null | Row) { + update(key: Key, row?: undefined | null | Row): void { if (row === undefined) return; const pack = this.index.get(key); let doReload = false; @@ -406,7 +404,7 @@ export class ArrayModel<Key, Row> Modification will be only visible after a final [[reload]]. Useful for a large number of batched updates. */ - removeAllData() { + removeAllData(): void { this.index.clear(); } @@ -416,7 +414,7 @@ export class ArrayModel<Key, Row> Useful for a large number of batched updates. @param key - the removed entry. */ - removeData(keys: Collection<Key>) { + removeData(keys: Collection<Key>): void { forEach(keys, (k) => this.index.delete(k)); } @@ -427,7 +425,7 @@ export class ArrayModel<Key, Row> @param key - the entry to update. @param row - the new row data or `null` for removal. */ - setData(key: Key, row: null | Row) { + setData(key: Key, row: null | Row): void { if (row !== null) { this.index.set(key, { key, row, index: undefined }); } else { @@ -471,19 +469,21 @@ export class CompactModel<Key, Row> extends ArrayModel<Key, Row> { } /** Use the key getter directly. */ - getKeyFor(_: number, data: Row) { return this.getkey(data); } + getKeyFor(_: number, data: Row): Key | undefined { return this.getkey(data); } - /** Silently add or update a collection of data. - Requires a final trigger to update views. */ - updateData(data: Collection<Row>) { + /** + Silently add or update a collection of data. + Requires a final trigger to update views. + */ + updateData(data: Collection<Row>): void { forEach(data, (row: Row) => this.setData(this.getkey(row), row)); } /** Replace all previous data with the new ones. Finally triggers a reload. - */ - replaceAllDataWith(data: Collection<Row>) { + */ + replaceAllDataWith(data: Collection<Row>): void { this.removeAllData(); this.updateData(data); this.reload(); diff --git a/ivette/src/dome/renderer/table/views.tsx b/ivette/src/dome/renderer/table/views.tsx index 128b6404dd629f254fd5d0f980a676164db01b39..9277450ebf67ce734835b03ab7ea3e363f8ff374 100644 --- a/ivette/src/dome/renderer/table/views.tsx +++ b/ivette/src/dome/renderer/table/views.tsx @@ -20,7 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-explicit-any */ // -------------------------------------------------------------------------- @@ -218,7 +217,7 @@ type Cprops = ColProps<any>; // --- Column Utilities // -------------------------------------------------------------------------- -const isVisible = (visible: Cmap<boolean>, col: Cprops) => { +const isVisible = (visible: Cmap<boolean>, col: Cprops): boolean => { const defaultVisible = col.visible ?? true; switch (defaultVisible) { case 'never': return false; @@ -228,12 +227,12 @@ const isVisible = (visible: Cmap<boolean>, col: Cprops) => { } }; -const defaultGetter = (row: any, dataKey: string) => { +const defaultGetter = (row: any, dataKey: string): any => { if (typeof row === 'object') return row[dataKey]; return undefined; }; -const defaultRenderer = (d: any) => ( +const defaultRenderer = (d: any): JSX.Element => ( <div className="dome-xTable-renderer dome-text-label"> {String(d)} </div> @@ -271,7 +270,7 @@ function makeDataRenderer( try { const contents = cellData ? render(cellData) : null; if (onContextMenu) { - const callback = (evt: React.MouseEvent) => { + const callback = (evt: React.MouseEvent): void => { evt.stopPropagation(); onContextMenu(props.rowData, props.rowIndex, props.dataKey); }; @@ -354,28 +353,28 @@ class TableState<Key, Row> { // --- Static Callbacks - unwind() { + unwind(): void { this.signal = undefined; this.onSelection = undefined; this.onContextMenu = undefined; } - forceUpdate() { + forceUpdate(): void { const s = this.signal; if (s) { this.signal = undefined; s(); } } - updateGrid() { + updateGrid(): void { this.tableRef.current?.forceUpdateGrid(); } - fullReload() { + fullReload(): void { this.scrolledKey = undefined; this.forceUpdate(); this.updateGrid(); } - getRef(id: string) { + getRef(id: string): divRef { const href = this.headerRef.get(id); if (href) return href; const nref: divRef = React.createRef(); @@ -394,13 +393,13 @@ class TableState<Key, Row> { return cw; } - startResizing(idx: number) { + startResizing(idx: number): void { this.resizing = idx; this.offset = 0; this.forceUpdate(); } - stopResizing() { + stopResizing(): void { this.resizing = undefined; this.offset = undefined; this.columnWith.clear(); @@ -408,7 +407,7 @@ class TableState<Key, Row> { } // Debounced - setResizeOffset(lcol: string, rcol: string, offset: number) { + setResizeOffset(lcol: string, rcol: string, offset: number): void { const colws = this.columnWith; const cwl = colws.get(lcol); const cwr = colws.get(rcol); @@ -425,7 +424,7 @@ class TableState<Key, Row> { // --- Table settings - updateSettings() { + updateSettings(): void { const userSettings = this.settings; if (userSettings) { const cws: Json.dict<number> = {}; @@ -444,7 +443,7 @@ class TableState<Key, Row> { this.forceUpdate(); } - importSettings(settings?: string) { + importSettings(settings?: string): void { if (settings !== this.settings) { this.settings = settings; const { resize } = this; @@ -467,7 +466,7 @@ class TableState<Key, Row> { // --- User Table properties - setSorting(sorting?: Sorting) { + setSorting(sorting?: Sorting): void { const info = sorting?.getSorting(); this.sortBy = info?.sortBy; this.sortDirection = info?.sortDirection; @@ -477,7 +476,7 @@ class TableState<Key, Row> { } } - setModel(model?: Model<Key, Row>) { + setModel(model?: Model<Key, Row>): void { if (model !== this.model) { this.client?.unlink(); this.model = model; @@ -494,7 +493,7 @@ class TableState<Key, Row> { } } - setRendering(rendering?: RenderByFields<Row>) { + setRendering(rendering?: RenderByFields<Row>): void { if (rendering !== this.rendering) { this.rendering = rendering; this.render.clear(); @@ -506,7 +505,7 @@ class TableState<Key, Row> { onSelection?: (data: Row, key: Key, index: number) => void; - onRowClick(info: RowMouseEventHandlerParams) { + onRowClick(info: RowMouseEventHandlerParams): void { const { index } = info; const data = info.rowData as (Row | undefined); const { model } = this; @@ -517,7 +516,7 @@ class TableState<Key, Row> { onSelection(data, key, index); } - onRowsRendered(info: IndexRange) { + onRowsRendered(info: IndexRange): void { this.range = info; this.client?.watch(info.startIndex, info.stopIndex); } @@ -527,7 +526,7 @@ class TableState<Key, Row> { return (index & 1 ? 'dome-xTable-even' : 'dome-xTable-odd'); // eslint-disable-line no-bitwise } - keyStepper(index: number) { + keyStepper(index: number): void { const { onSelection } = this; const key = this.model?.getKeyAt(index); const data = this.model?.getRowAt(index); @@ -546,7 +545,7 @@ class TableState<Key, Row> { return undefined; } - onSorting(ord?: SortingInfo) { + onSorting(ord?: SortingInfo): void { const { sorting } = this; if (sorting) { sorting.setSorting(ord); @@ -558,7 +557,7 @@ class TableState<Key, Row> { // ---- Row Events - onRowRightClick({ event, rowData, index }: RowMouseEventHandlerParams) { + onRowRightClick({ event, rowData, index }: RowMouseEventHandlerParams): void { const callback = this.onContextMenu; if (callback) { event.stopPropagation(); @@ -566,7 +565,7 @@ class TableState<Key, Row> { } } - onKeyDown(evt: React.KeyboardEvent) { + onKeyDown(evt: React.KeyboardEvent): void { const index = this.selectedIndex; switch (evt.key) { case 'ArrowUp': @@ -586,7 +585,7 @@ class TableState<Key, Row> { // ---- Header Context Menu - onHeaderMenu() { + onHeaderMenu(): void { let hasOrder = false; let hasResize = false; let hasVisible = false; @@ -598,11 +597,11 @@ class TableState<Key, Row> { if (col.visible !== 'never' && col.visible !== 'always') hasVisible = true; }); - const resetSizing = () => { + const resetSizing = (): void => { this.resize.clear(); this.updateSettings(); }; - const resetColumns = () => { + const resetColumns = (): void => { this.visible.clear(); this.resize.clear(); this.updateSettings(); @@ -633,7 +632,7 @@ class TableState<Key, Row> { default: { const { id, label, title } = col; const checked = isVisible(visible, col); - const onClick = () => { + const onClick = (): void => { visible.set(id, !checked); this.updateSettings(); }; @@ -646,7 +645,11 @@ class TableState<Key, Row> { // --- Getter & Setters - computeGetter(id: string, dataKey: string, props: Cprops) { + computeGetter( + id: string, + dataKey: string, + props: Cprops + ): TableCellDataGetter { const current = this.getter.get(id); if (current) return current; const dataGetter = makeDataGetter(dataKey, props.getter); @@ -654,7 +657,11 @@ class TableState<Key, Row> { return dataGetter; } - computeRender(id: string, dataKey: string, props: Cprops) { + computeRender( + id: string, + dataKey: string, + props: Cprops + ): TableCellRenderer { const current = this.render.get(id); if (current) return current; let renderer = props.render; @@ -671,7 +678,7 @@ class TableState<Key, Row> { private registry = new Map<string, null | ColProps<Row>>(); - setRegistry(id: string, props: null | ColProps<Row>) { + setRegistry(id: string, props: null | ColProps<Row>): void { this.registry.set(id, props); this.rebuild(); } @@ -688,7 +695,7 @@ class TableState<Key, Row> { return () => this.setRegistry(id, null); } - rebuild() { + rebuild(): void { const current = this.columns; const cols: ColProps<Row>[] = []; this.registry.forEach((col) => col && cols.push(col)); @@ -715,10 +722,13 @@ const ColumnContext = React.createContext<undefined | ColumnIndex>(undefined); /** Table Column. + @template Row - table row data of some table entries @template Cell - type of cell data to render in this column */ -export function Column<Row, Cell>(props: ColumnProps<Row, Cell>) { +export function Column<Row, Cell>( + props: ColumnProps<Row, Cell> +): JSX.Element | null { const context = React.useContext(ColumnContext); React.useEffect(() => { if (context) { @@ -733,8 +743,8 @@ function spawnIndex( state: TableState<any, any>, path: number[], children: React.ReactElement | React.ReactElement[], -) { - const indexChild = (elt: React.ReactElement, k: number) => ( +): JSX.Element { + const indexChild = (elt: React.ReactElement, k: number): JSX.Element => ( <ColumnContext.Provider value={{ state, path, index: k }}> {elt} </ColumnContext.Provider> @@ -790,7 +800,7 @@ export interface ColumnGroupProps { this implicit root column group, just pack your columns inside a classical React fragment: `<Table … ><>{children}</></Table>`. */ -export function ColumnGroup(props: ColumnGroupProps) { +export function ColumnGroup(props: ColumnGroupProps): JSX.Element | null { const context = React.useContext(ColumnContext); if (!context) return null; const { state, path, index: defaultIndex } = context; @@ -807,7 +817,7 @@ function makeColumn<Key, Row>( state: TableState<Key, Row>, props: ColProps<Row>, fill: boolean, -) { +): JSX.Element { const { id } = props; const align = { textAlign: props.align }; const dataKey = props.dataKey ?? id; @@ -842,7 +852,7 @@ function makeColumn<Key, Row>( ); } -const byIndex = (a: Cprops, b: Cprops) => { +const byIndex = (a: Cprops, b: Cprops): number => { const ak = a.index ?? 0; const bk = b.index ?? 0; if (ak < bk) return -1; @@ -850,7 +860,7 @@ const byIndex = (a: Cprops, b: Cprops) => { return 0; }; -function makeCprops<Key, Row>(state: TableState<Key, Row>) { +function makeCprops<Key, Row>(state: TableState<Key, Row>): Cprops[] { const cols: Cprops[] = []; state.columns.forEach((col) => { if (col && isVisible(state.visible, col)) { @@ -861,7 +871,10 @@ function makeCprops<Key, Row>(state: TableState<Key, Row>) { return cols; } -function makeColumns<Key, Row>(state: TableState<Key, Row>, cols: Cprops[]) { +function makeColumns<Key, Row>( + state: TableState<Key, Row>, + cols: Cprops[] +): JSX.Element[] { let fill: undefined | Cprops; let lastExt: undefined | Cprops; cols.forEach((col) => { @@ -877,25 +890,25 @@ function makeColumns<Key, Row>(state: TableState<Key, Row>, cols: Cprops[]) { // --- Table Utility Renderers // -------------------------------------------------------------------------- -const headerIcon = (icon?: string) => ( - icon && - ( - <div className="dome-xTable-header-icon"> - <SVG id={icon} /> - </div> - ) +const headerIcon = (icon?: string): JSX.Element | null => ( + icon ? + ( + <div className="dome-xTable-header-icon"> + <SVG id={icon} /> + </div> + ) : null ); -const headerLabel = (label?: string) => ( - label && - ( - <label className="dome-xTable-header-label dome-text-label"> - {label} - </label> - ) +const headerLabel = (label?: string): JSX.Element | null => ( + label ? + ( + <label className="dome-xTable-header-label dome-text-label"> + {label} + </label> + ) : null ); -const makeSorter = (id: string) => ( +const makeSorter = (id: string): JSX.Element => ( <div className="dome-xTable-header-sorter"> <SVG id={id} size={8} /> </div> @@ -904,7 +917,7 @@ const makeSorter = (id: string) => ( const sorterASC = makeSorter('ANGLE.UP'); const sorterDESC = makeSorter('ANGLE.DOWN'); -function headerRowRenderer(props: TableHeaderRowProps) { +function headerRowRenderer(props: TableHeaderRowProps): JSX.Element { return ( <div role="row" @@ -916,7 +929,7 @@ function headerRowRenderer(props: TableHeaderRowProps) { ); } -function headerRenderer(props: TableHeaderProps) { +function headerRenderer(props: TableHeaderProps): JSX.Element { const data: ColumnData = props.columnData; const { sortBy, sortDirection, dataKey } = props; const { icon, label, title, headerRef, headerMenu } = data; @@ -954,7 +967,7 @@ interface ResizerProps { onDrag: (offset: number) => void; } -const Resizer = (props: ResizerProps) => ( +const Resizer = (props: ResizerProps): JSX.Element => ( <DraggableCore onStart={props.onStart} onStop={props.onStop} @@ -1003,9 +1016,10 @@ function makeResizers( const lcol = b.left; const offset = state.offset ?? 0; const dragging = state.resizing === index; - const onStart = () => state.startResizing(index); - const onStop = () => state.stopResizing(); - const onDrag = (ofs: number) => state.setResizeOffset(lcol, rcol, ofs); + const onStart = (): void => state.startResizing(index); + const onStop = (): void => state.stopResizing(); + const onDrag = + (ofs: number): void => state.setResizeOffset(lcol, rcol, ofs); const resizer = ( <Resizer key={index} @@ -1036,7 +1050,7 @@ function makeTable<Key, Row>( props: TableProps<Key, Row>, state: TableState<Key, Row>, size: Size, -) { +): JSX.Element { const { width, height } = size; const { model } = props; @@ -1121,7 +1135,7 @@ function makeTable<Key, Row>( @template Row - data associated to each key in the table entries. */ -export function Table<Key, Row>(props: TableProps<Key, Row>) { +export function Table<Key, Row>(props: TableProps<Key, Row>): JSX.Element { const state = React.useMemo(() => new TableState<Key, Row>(), []); const [age, setAge] = React.useState(0); diff --git a/ivette/src/dome/renderer/text/buffers.ts b/ivette/src/dome/renderer/text/buffers.ts index 1a769d32272d8c67fe3fee1aa339215f3a95eaec..7193930c32d600e455e9423f63d53b29686dc55e 100644 --- a/ivette/src/dome/renderer/text/buffers.ts +++ b/ivette/src/dome/renderer/text/buffers.ts @@ -20,7 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ /* eslint-disable @typescript-eslint/no-explicit-any */ // -------------------------------------------------------------------------- @@ -101,7 +100,7 @@ interface BufferedMarker extends MarkerOptions { type BufferedTag = BufferedMarker | undefined; -function rankTag(lmin: number, lmax: number, tg: BufferedTag) { +function rankTag(lmin: number, lmax: number, tg: BufferedTag): number { if (tg && (lmin <= tg.stopIndex || tg.startIndex <= lmax)) { const size = tg.stopIndex - tg.startIndex; return size < BATCH_RMAX ? size : BATCH_RMAX; @@ -109,11 +108,11 @@ function rankTag(lmin: number, lmax: number, tg: BufferedTag) { return BATCH_RMAX; } -function byVisibleTag(lmin: number, lmax: number) { - return (a: BufferedTag, b: BufferedTag) => ( - rankTag(lmin, lmax, a) - rankTag(lmin, lmax, b) - ); -} +const byVisibleTag = + (lmin: number, lmax: number) => + (a: BufferedTag, b: BufferedTag): number => ( + rankTag(lmin, lmax, a) - rankTag(lmin, lmax, b) + ); // -------------------------------------------------------------------------- // --- Buffer @@ -230,14 +229,14 @@ export class RichTextBuffer extends Emitter { // -------------------------------------------------------------------------- /** Clear buffer contents and resets _edited_ state. */ - clear() { this.setValue(''); } + clear(): void { this.setValue(''); } /** Writes in the buffer. All parameters are converted to string and joined with spaces. The generated change event has origin `'buffer'` and it does not modifies the _edited_ internal state. */ - append(...values: any[]) { + append(...values: any[]): void { if (values.length > 0) { const text = values.join(' '); this.bufferedText += text; @@ -249,7 +248,7 @@ export class RichTextBuffer extends Emitter { Starts a new line in the buffer. If the current buffer content does not finish at the beginning of a fresh line, inserts a newline character. */ - flushline() { + flushline(): void { const doc = this.document; const buf = this.bufferedText; if (buf === '') { @@ -263,7 +262,7 @@ export class RichTextBuffer extends Emitter { Appends with newline and auto-scrolling. This is a short-cut to `flushline()` followed by `append(...value,'\n')` and `scroll()`. */ - log(...values: any[]) { + log(...values: any[]): void { this.flushline(); this.append(...values, '\n'); this.scroll(); @@ -273,7 +272,7 @@ export class RichTextBuffer extends Emitter { Replace all textual content with the given string. Also remove all markers. */ - setValue(txt = '') { + setValue(txt = ''): void { this.document.setValue(txt); this.cssmarkers.clear(); this.textmarkers.clear(); @@ -287,7 +286,7 @@ export class RichTextBuffer extends Emitter { } /** Return the textual contents of the buffer. */ - getValue() { return this.document.getValue(); } + getValue(): string { return this.document.getValue(); } // -------------------------------------------------------------------------- // --- Text Markers @@ -309,7 +308,7 @@ export class RichTextBuffer extends Emitter { Hence, you can safely invoke these methods on either the _proxy_ or the _final_ text marker at your convenience. */ - openTextMarker(props: MarkerProps) { + openTextMarker(props: MarkerProps): void { const { id, hover, className, ...options } = props; const startIndex = this.getLastIndex() + this.bufferedText.length; this.stacked.push({ startIndex, id, hover, className, options }); @@ -317,14 +316,8 @@ export class RichTextBuffer extends Emitter { /** Closes the last opened marker. - - Returns the (actual) [text marker] - (https://codemirror.net/doc/manual.html#api_marker) ; the proxy - returned by the corresponding call to [[openTextMarker]] is automatically - bound to the actual one. - */ - closeTextMarker() { + closeTextMarker(): void { const tag = this.stacked.pop(); if (tag) { const stopIndex = this.getLastIndex() + this.bufferedText.length; @@ -361,7 +354,7 @@ export class RichTextBuffer extends Emitter { @param fn - highlighter, `fn(id)` shall return a className to apply on text markers with the provided identifier. */ - setDecorator(fn: Decorator) { + setDecorator(fn: Decorator): void { this.decorator = fn; this.emit('decorated'); } @@ -369,7 +362,7 @@ export class RichTextBuffer extends Emitter { /** Current highlighter. */ - getDecorator() { return this.decorator; } + getDecorator(): Decorator | undefined { return this.decorator; } /** Rehighlight document. @@ -382,7 +375,7 @@ export class RichTextBuffer extends Emitter { The method is bound to `this`. */ - updateDecorations() { this.emit('decorated'); } + updateDecorations(): void { this.emit('decorated'); } // -------------------------------------------------------------------------- // --- Edited State @@ -401,7 +394,7 @@ export class RichTextBuffer extends Emitter { @param state - the new edited state (defaults to `true`). */ - setEdited(state = true) { + setEdited(state = true): void { if (state !== this.edited) { this.edited = state; this.emit('edited', state); @@ -425,7 +418,7 @@ export class RichTextBuffer extends Emitter { @param focus - the new focused state (defaults to `true`). */ - setFocused(state = true) { + setFocused(state = true): void { if (state !== this.focused) { this.focused = state; this.emit('focused', state); @@ -434,10 +427,10 @@ export class RichTextBuffer extends Emitter { } /** Returns the current _edited_ state. */ - isEdited() { return this.edited; } + isEdited(): boolean { return this.edited; } /** Returns the current _focused_ state. */ - isFocused() { return this.focused; } + isFocused(): boolean { return this.focused; } // -------------------------------------------------------------------------- // --- Document Scrolling @@ -453,7 +446,7 @@ export class RichTextBuffer extends Emitter { Although CodeMirror is powerfull enough to manage huge buffers, you should turn this limit _off_ with care. */ - setMaxlines(maxlines = 10000) { + setMaxlines(maxlines = 10000): void { this.maxlines = maxlines > 0 ? 1 + maxlines : 0; this.doShrink(); } @@ -508,7 +501,7 @@ export class RichTextBuffer extends Emitter { private onChange( _editor: CodeMirror.Editor, change: CodeMirror.EditorChange, - ) { + ): void { if (change.origin !== 'buffer') { this.setEdited(true); this.cacheIndex = -1; @@ -524,7 +517,7 @@ export class RichTextBuffer extends Emitter { @param cm - code mirror instance to link this document in. */ - link(cm: CodeMirror.Editor) { + link(cm: CodeMirror.Editor): void { const newDoc = this.document.linkedDoc( { sharedHist: true, mode: undefined }, ); @@ -537,7 +530,7 @@ export class RichTextBuffer extends Emitter { Release a linked CodeMirror document previously linked with [[link]]. @param cm - the code mirror instance to unlink */ - unlink(cm: CodeMirror.Editor) { + unlink(cm: CodeMirror.Editor): void { const oldDoc = cm.getDoc(); this.document.unlinkDoc(oldDoc); this.editors = this.editors.filter((cm0) => { @@ -557,7 +550,7 @@ export class RichTextBuffer extends Emitter { Exceptions raised by `fn` are catched and dumped in the console. */ - forEach(fn: (editor: CodeMirror.Editor) => void) { + forEach(fn: (editor: CodeMirror.Editor) => void): void { this.editors.forEach((cm) => { try { fn(cm); } catch (e) { D.error(e); } }); @@ -568,7 +561,7 @@ export class RichTextBuffer extends Emitter { // -------------------------------------------------------------------------- /* Append Operation */ - private doFlushText() { + private doFlushText(): void { const text = this.bufferedText; if (text.length > 0) { this.bufferedText = ''; @@ -581,7 +574,7 @@ export class RichTextBuffer extends Emitter { } /* Shrink Operation */ - private doShrink() { + private doShrink(): void { const lines = this.document.lineCount(); if (lines > this.maxlines) { const p = this.document.firstLine(); @@ -597,7 +590,7 @@ export class RichTextBuffer extends Emitter { } /* Close Operation */ - private doMark(tag: BufferedMarker) { + private doMark(tag: BufferedMarker): void { const { id, hover, className, startIndex, stopIndex } = tag; let markerId; if (id || hover) { @@ -635,7 +628,7 @@ export class RichTextBuffer extends Emitter { } } - private doFilterTags() { + private doFilterTags(): BufferedTag[] { const tgs = this.bufferedTags; if (tgs.length > 0 && this.editors.length > 0) { let lmin = Infinity; @@ -650,7 +643,7 @@ export class RichTextBuffer extends Emitter { return tgs.splice(0, BATCH_OPS); } - private getLastIndex() { + private getLastIndex(): number { if (this.cacheIndex < 0) { const doc = this.document; const line = doc.lastLine() + 1; @@ -663,7 +656,7 @@ export class RichTextBuffer extends Emitter { // --- Batched Operations // -------------------------------------------------------------------------- - private armBatch() { + private armBatch(): void { if (!this.batched) { this.batched = true; setTimeout(this.doBatch, BATCH_DELAY); @@ -671,7 +664,7 @@ export class RichTextBuffer extends Emitter { } /* Batch Operation */ - private doBatch() { + private doBatch(): void { this.batched = false; if (!this.bufferedText.length && !this.bufferedTags.length) return; try { diff --git a/ivette/src/dome/renderer/text/editors.tsx b/ivette/src/dome/renderer/text/editors.tsx index 5fe93f21e1e355366aadf6afe77bea1f67095e1f..e94492b865230fbb10e1520557f884015d0f662d 100644 --- a/ivette/src/dome/renderer/text/editors.tsx +++ b/ivette/src/dome/renderer/text/editors.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Text Documents // -------------------------------------------------------------------------- @@ -117,10 +115,15 @@ type CMoption = keyof EditorConfiguration; function forEachOption( config: EditorConfiguration, fn: (opt: CMoption) => void, -) { +): void { (Object.keys(config) as (keyof EditorConfiguration)[]).forEach(fn); } +interface _Decoration { + classNameId: string; + decoration: string | undefined; +} + // -------------------------------------------------------------------------- // --- Code Mirror Instance Wrapper // -------------------------------------------------------------------------- @@ -161,7 +164,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Mounting // -------------------------------------------------------------------------- - mountPoint(elt: HTMLDivElement | null) { + mountPoint(elt: HTMLDivElement | null): void { this.rootElement = elt; if (elt !== null) { // Mounting... @@ -207,7 +210,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Auto Refresh // -------------------------------------------------------------------------- - refresh() { + refresh(): void { const elt = this.rootElement; const cm = this.codeMirror; if (cm && elt) { @@ -219,7 +222,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { } // Polled every 250ms - autoRefresh() { + autoRefresh(): void { const elt = this.rootElement; const cm = this.codeMirror; if (cm && elt) { @@ -259,7 +262,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { classes: DOMTokenList, buffer: RichTextBuffer, decorator: Decorator, - ) { + ): _Decoration | undefined { let bestMarker: CSSMarker | undefined; let bestDecorated: CSSMarker | undefined; let bestDecoration: string | undefined; @@ -286,7 +289,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { } : undefined; } - _markElementsWith(classNameId: string, className: string) { + _markElementsWith(classNameId: string, className: string): void { const root = this.rootElement; const toMark = root && root.getElementsByClassName(classNameId); if (toMark) { @@ -296,7 +299,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { } } - _unmarkElementsWith(className: string) { + _unmarkElementsWith(className: string): void { const root = this.rootElement; const toUnmark = root && root.getElementsByClassName(className); if (toUnmark) { @@ -308,7 +311,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { } } - handleHover(target: Element) { + handleHover(target: Element): void { // Throttled (see constructor) const oldMarker = this.marker; const newMarker = this._findMarker(target); @@ -322,7 +325,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { } } - handleUpdate() { + handleUpdate(): void { const root = this.rootElement; const marked = root && root.getElementsByClassName('dome-xMarked'); if (!marked) return; @@ -357,13 +360,13 @@ class CodeMirrorWrapper extends React.Component<TextProps> { this.decorations = newDecorations; } - onMouseMove(evt: MouseEvt) { + onMouseMove(evt: MouseEvt): void { // Not throttled (can not leak Synthetic Events) const tgt = evt.target; if (tgt instanceof Element) this.handleHover(tgt); } - onMouseClick(evt: MouseEvt, callback: MarkerCallback | undefined) { + onMouseClick(evt: MouseEvt, callback: MarkerCallback | undefined): void { // No need for throttling if (callback) { const { target } = evt; @@ -375,11 +378,11 @@ class CodeMirrorWrapper extends React.Component<TextProps> { this.props.buffer?.setFocused(true); } - onClick(evt: MouseEvt) { + onClick(evt: MouseEvt): void { this.onMouseClick(evt, this.props.onSelection); } - onContextMenu(evt: MouseEvt) { + onContextMenu(evt: MouseEvt): void { this.onMouseClick(evt, this.props.onContextMenu); } @@ -387,7 +390,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Scrolling // -------------------------------------------------------------------------- - handleScrollTo(line: number) { + handleScrollTo(line: number): void { try { const cm = this.codeMirror; return cm && cm.scrollIntoView({ line, ch: 0 }); @@ -400,7 +403,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Focus // -------------------------------------------------------------------------- - handleKey(_cm: CodeMirror.Editor, key: string, _evt: Event) { + handleKey(_cm: CodeMirror.Editor, key: string, _evt: Event): void { switch (key) { case 'Esc': this.props.buffer?.setFocused(false); @@ -411,9 +414,9 @@ class CodeMirrorWrapper extends React.Component<TextProps> { } } - onFocus() { this.props.buffer?.setFocused(true); } - onBlur() { this.props.buffer?.setFocused(false); } - onScroll() { + onFocus(): void { this.props.buffer?.setFocused(true); } + onBlur(): void { this.props.buffer?.setFocused(false); } + onScroll(): void { const cm = this.codeMirror; const { buffer } = this.props; if (cm && buffer) { @@ -427,7 +430,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { // --- Rendering // -------------------------------------------------------------------------- - shouldComponentUpdate(newProps: TextProps) { + shouldComponentUpdate(newProps: TextProps): boolean { const cm = this.codeMirror; if (cm) { // Swap documents if necessary @@ -476,7 +479,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { return false; } - render() { + render(): JSX.Element { return ( <div className="dome-xText" @@ -542,7 +545,7 @@ class CodeMirrorWrapper extends React.Component<TextProps> { `import CodeMirror from 'codemirror/lib/codemirror.js'` ; using `from 'codemirror'` returns a different instance of `CodeMirror` class and will not work. */ -export function Text(props: TextProps) { +export function Text(props: TextProps): JSX.Element { const [appTheme] = Themes.useColorTheme(); let { className, style, fontSize, theme: usrTheme, ...cmprops } = props; if (fontSize !== undefined && fontSize < 4) fontSize = 4; diff --git a/ivette/src/frama-c/kernel/ASTview.tsx b/ivette/src/frama-c/kernel/ASTview.tsx index 339961b65b0344a4dddf128fdbbeb1b71abb56b5..c2f657e33a1df75a9bfdec9fb8784c17dbf9e58b 100644 --- a/ivette/src/frama-c/kernel/ASTview.tsx +++ b/ivette/src/frama-c/kernel/ASTview.tsx @@ -20,24 +20,24 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- AST Source Code // -------------------------------------------------------------------------- import React from 'react'; import _ from 'lodash'; -import * as Server from 'frama-c/server'; -import * as States from 'frama-c/states'; -import * as RichText from 'frama-c/richtext'; import * as Dome from 'dome'; +import * as Settings from 'dome/data/settings'; +import type { key } from 'dome/data/json'; import { RichTextBuffer } from 'dome/text/buffers'; import { Text } from 'dome/text/editors'; + import * as Preferences from 'ivette/prefs'; -import * as Settings from 'dome/data/settings'; +import * as Server from 'frama-c/server'; +import * as States from 'frama-c/states'; +import * as RichText from 'frama-c/richtext'; import * as Ast from 'frama-c/kernel/api/ast'; import * as Properties from 'frama-c/kernel/api/properties'; import { getCallers, getDeadCode } from 'frama-c/plugins/eva/api/general'; @@ -55,7 +55,7 @@ const D = new Dome.Debug('AST View'); async function loadAST( buffer: RichTextBuffer, theFunction?: string, theMarker?: string, -) { +): Promise<void> { buffer.clear(); if (theFunction) { buffer.log('// Loading', theFunction, '…'); @@ -83,7 +83,9 @@ async function loadAST( /* --- Function Callers ---*/ /* --------------------------------------------------------------------------*/ -async function functionCallers(functionName: string) { +type Caller = { fct: key<'#fct'>, marker: key<'#stmt'> }; + +async function functionCallers(functionName: string): Promise<Caller[]> { try { const data = await Server.send(getCallers, functionName); const locations = data.map(([fct, marker]) => ({ fct, marker })); @@ -99,12 +101,18 @@ async function functionCallers(functionName: string) { /* --------------------------------------------------------------------------*/ type access = 'Reads' | 'Writes'; +interface StudiaInfos { + name: string, + title: string, + locations: { fct: key<'#fct'>, marker: Ast.marker }[], + index: number, +} async function studia( marker: string, info: Ast.markerInfoData, kind: access, -) { +): Promise<StudiaInfos> { const request = kind === 'Reads' ? getReadsLval : getWritesLval; const data = await Server.send(request, marker); const locations = data.direct.map(([f, m]) => ({ fct: f, marker: m })); @@ -124,7 +132,7 @@ async function studia( /* --- Property Bullets ---*/ /* --------------------------------------------------------------------------*/ -function getBulletColor(status: States.Tag) { +function getBulletColor(status: States.Tag): string { switch (status.name) { case 'unknown': return '#FF8300'; case 'invalid': @@ -141,7 +149,7 @@ function getBulletColor(status: States.Tag) { } } -function makeBullet(status: States.Tag) { +function makeBullet(status: States.Tag): HTMLDivElement { const marker = document.createElement('div'); marker.style.color = getBulletColor(status); if (status.descr) @@ -155,7 +163,7 @@ function makeBullet(status: States.Tag) { // --- AST Printer // -------------------------------------------------------------------------- -export default function ASTview() { +export default function ASTview(): JSX.Element { // Hooks const buffer = React.useMemo(() => new RichTextBuffer(), []); @@ -200,7 +208,7 @@ export default function ASTview() { return () => { buffer.off('change', setBullets); }; }, [buffer, setBullets]); - async function reload() { + async function reload(): Promise<void> { printed.current = theFunction; loadAST(buffer, theFunction, theMarker); } @@ -215,7 +223,7 @@ export default function ASTview() { Server.onSignal(Ast.changed, reload); React.useEffect(() => { - const decorator = (marker: string) => { + const decorator = (marker: string): string | undefined => { if (multipleSelections?.some((location) => location?.marker === marker)) return 'highlighted-marker'; if (deadCode?.unreachable?.some((m) => m === marker)) @@ -234,20 +242,20 @@ export default function ASTview() { if (theMarker) buffer.scroll(theMarker); }, [buffer, theMarker]); - function onHover(markerId?: string) { + function onHover(markerId?: string): void { const marker = Ast.jMarker(markerId); const fct = selection?.current?.fct; States.setHovered(marker ? { fct, marker } : undefined); } - function onSelection(markerId: string, meta = false) { + function onSelection(markerId: string, meta = false): void { const fct = selection?.current?.fct; const location = { fct, marker: Ast.jMarker(markerId) }; updateSelection({ location }); if (meta) States.MetaSelection.emit(location); } - async function onContextMenu(markerId: string) { + async function onContextMenu(markerId: string): Promise<void> { const items = []; const selectedMarkerInfo = markersInfo.getData(markerId); if (selectedMarkerInfo?.var === 'function') { @@ -283,7 +291,7 @@ export default function ASTview() { } const enabled = selectedMarkerInfo?.kind === 'lvalue' || selectedMarkerInfo?.var === 'variable'; - function onClick(kind: access) { + function onClick(kind: access): void { if (selectedMarkerInfo) studia( markerId, diff --git a/ivette/src/frama-c/kernel/Messages.tsx b/ivette/src/frama-c/kernel/Messages.tsx index c570560a0564778a4396fcc4a538a980369c919a..93c61b8a7a773bee950b267ce900f3ab5cc8adea 100644 --- a/ivette/src/frama-c/kernel/Messages.tsx +++ b/ivette/src/frama-c/kernel/Messages.tsx @@ -20,11 +20,8 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - import * as React from 'react'; import * as Dome from 'dome'; -import { TitleBar } from 'ivette'; import { IconButton } from 'dome/controls/buttons'; import { Label, Cell } from 'dome/controls/labels'; @@ -36,8 +33,9 @@ import * as Forms from 'dome/layout/forms'; import * as Arrays from 'dome/table/arrays'; import { Table, Column, Renderer } from 'dome/table/views'; import * as Compare from 'dome/data/compare'; - import { State, GlobalState, useGlobalState } from 'dome/data/states'; + +import { TitleBar } from 'ivette'; import * as States from 'frama-c/states'; import * as Ast from 'frama-c/kernel/api/ast'; import * as Kernel from 'frama-c/kernel/api/services'; @@ -114,11 +112,11 @@ const defaultFilter: Filter = { emitter: emitterFilter, }; -function filterKind(filter: KindFilter, msg: Message) { +function filterKind(filter: KindFilter, msg: Message): boolean { return filter[msg.kind]; } -function filterEmitter(filter: EmitterFilter, msg: Message) { +function filterEmitter(filter: EmitterFilter, msg: Message): boolean { if (msg.plugin === 'kernel') return filter.kernel; if (msg.plugin in filter.plugins) @@ -126,7 +124,10 @@ function filterEmitter(filter: EmitterFilter, msg: Message) { return filter.others; } -function searchCategory(search: string | undefined, msg: string | undefined) { +function searchCategory( + search: string | undefined, + msg: string | undefined +): boolean { if (!search || search.length < 2) return true; if (!msg) @@ -151,7 +152,7 @@ function searchCategory(search: string | undefined, msg: string | undefined) { return (empty || show) && !hide; } -function searchString(search: string | undefined, msg: string) { +function searchString(search: string | undefined, msg: string): boolean { if (!search || search.length < 3) return true; if (search.charAt(0) === '"' && search.slice(-1) === '"') { @@ -168,18 +169,26 @@ function searchString(search: string | undefined, msg: string) { return show; } -function filterSearched(search: Search, msg: Message) { +function filterSearched(search: Search, msg: Message): boolean { return (searchString(search.message, msg.message) && searchCategory(search.category, msg.category)); } -function filterFunction(filter: Filter, kf: string | undefined, msg: Message) { +function filterFunction( + filter: Filter, + kf: string | undefined, + msg: Message +): boolean { if (filter.currentFct) return (kf === msg.fct); return true; } -function filterMessage(filter: Filter, kf: string | undefined, msg: Message) { +function filterMessage( + filter: Filter, + kf: string | undefined, + msg: Message +): boolean { return (filterFunction(filter, kf, msg) && filterSearched(filter.search, msg) && filterKind(filter.kind, msg) && @@ -190,7 +199,7 @@ function filterMessage(filter: Filter, kf: string | undefined, msg: Message) { // --- Filters panel and ratio // -------------------------------------------------------------------------- -function Section(p: Forms.SectionProps) { +function Section(p: Forms.SectionProps): JSX.Element { const settings = `ivette.messages.filter.${p.label}`; return ( <Forms.Section label={p.label} unfold settings={settings}> @@ -199,7 +208,7 @@ function Section(p: Forms.SectionProps) { ); } -function Checkbox(p: Forms.CheckboxFieldProps) { +function Checkbox(p: Forms.CheckboxFieldProps): JSX.Element { const lbl = p.label.charAt(0).toUpperCase() + p.label.slice(1).toLowerCase(); return <Forms.CheckboxField label={lbl} state={p.state} />; } @@ -207,7 +216,7 @@ function Checkbox(p: Forms.CheckboxFieldProps) { function MessageKindCheckbox(props: { kind: logkind, kindState: Forms.FieldState<KindFilter>, -}) { +}): JSX.Element { const { kind, kindState } = props; const state = Forms.useProperty(kindState, kind); return <Checkbox label={kind} state={state} />; @@ -216,12 +225,12 @@ function MessageKindCheckbox(props: { function PluginCheckbox(props: { plugin: string, pluginState: Forms.FieldState<PluginFilter>, -}) { +}): JSX.Element { const state = Forms.useProperty(props.pluginState, props.plugin); return <Checkbox label={props.plugin} state={state} />; } -function MessageFilter(props: { filter: State<Filter> }) { +function MessageFilter(props: { filter: State<Filter> }): JSX.Element { const state = Forms.useValid(props.filter); const search = Forms.useProperty(state, 'search'); const categoryState = Forms.useProperty(search, 'category'); @@ -282,7 +291,9 @@ function MessageFilter(props: { filter: State<Filter> }) { ); } -function FilterRatio<K, R>({ model }: { model: Arrays.ArrayModel<K, R> }) { +function FilterRatio<K, R>( + { model }: { model: Arrays.ArrayModel<K, R> } +): JSX.Element { const [filtered, total] = [model.getRowCount(), model.getTotalRowCount()]; const title = `${filtered} displayed messages / ${total} total messages`; return ( @@ -296,35 +307,38 @@ function FilterRatio<K, R>({ model }: { model: Arrays.ArrayModel<K, R> }) { // --- Messages Columns // -------------------------------------------------------------------------- -const renderKind: Renderer<logkind> = (kind: logkind) => { - const label = kind.toLocaleLowerCase(); - let icon = ''; - let color = 'black'; - switch (kind) { - case 'RESULT': icon = 'ANGLE.RIGHT'; break; - case 'FEEDBACK': icon = 'CIRC.INFO'; break; - case 'DEBUG': icon = 'HELP'; break; - case 'WARNING': icon = 'ATTENTION'; color = '#C00000'; break; - case 'ERROR': case 'FAILURE': icon = 'WARNING'; color = '#C00000'; break; - } - return <Icon title={label} id={icon} fill={color} />; -}; +const renderKind: Renderer<logkind> = + (kind: logkind): JSX.Element => { + const label = kind.toLocaleLowerCase(); + let icon = ''; + let color = 'black'; + switch (kind) { + case 'RESULT': icon = 'ANGLE.RIGHT'; break; + case 'FEEDBACK': icon = 'CIRC.INFO'; break; + case 'DEBUG': icon = 'HELP'; break; + case 'WARNING': icon = 'ATTENTION'; color = '#C00000'; break; + case 'ERROR': case 'FAILURE': icon = 'WARNING'; color = '#C00000'; break; + } + return <Icon title={label} id={icon} fill={color} />; + }; const renderCell: Renderer<string> = - (text: string) => (<Cell title={text}>{text}</Cell>); + (text: string): JSX.Element => (<Cell title={text}>{text}</Cell>); const renderMessage: Renderer<string> = - (text: string) => (<div title={text} className="message-cell"> {text} </div>); + (text: string): JSX.Element => + (<div title={text} className="message-cell"> {text} </div>); const renderDir: Renderer<Ast.source> = - (loc: Ast.source) => (<Cell label={loc.dir} title={loc.file} />); + (loc: Ast.source): JSX.Element => + (<Cell label={loc.dir} title={loc.file} />); const renderFile: Renderer<Ast.source> = - (loc: Ast.source) => ( + (loc: Ast.source): JSX.Element => ( <Cell label={`${loc.base}:${loc.line}`} title={loc.file} /> ); -const MessageColumns = () => ( +const MessageColumns = (): JSX.Element => ( <> <Column id="kind" @@ -403,10 +417,10 @@ const byMessage: Compare.ByFields<Message> = { const globalFilterState = new GlobalState(defaultFilter); -export default function RenderMessages() { +export default function RenderMessages(): JSX.Element { const [model] = React.useState(() => { - const f = (msg: Message) => msg.key; + const f = (msg: Message): string => msg.key; const m = new Arrays.CompactModel<string, Message>(f); m.setOrderingByFields(byMessage); return m; diff --git a/ivette/src/frama-c/kernel/Properties.tsx b/ivette/src/frama-c/kernel/Properties.tsx index 208002b85902bb9a4b3f1929b8ea1ebd0d12e2b9..fe050e3a81db8987a699001c3b4816aaa7f2ccc2 100644 --- a/ivette/src/frama-c/kernel/Properties.tsx +++ b/ivette/src/frama-c/kernel/Properties.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Properties // -------------------------------------------------------------------------- @@ -105,7 +103,7 @@ const DEFAULTS: { [key: string]: boolean } = { 'eva.ctrl_tainted_only': false, }; -function filter(path: string) { +function filter(path: string): boolean { const defaultValue = DEFAULTS[path] ?? true; return Settings.getWindowSettings( `ivette.properties.filter.${path}`, @@ -114,7 +112,7 @@ function filter(path: string) { ); } -function useFilter(path: string) { +function useFilter(path: string): [boolean, () => void] { const defaultValue = DEFAULTS[path] ?? true; return Dome.useFlipSettings( `ivette.properties.filter.${path}`, @@ -124,7 +122,7 @@ function useFilter(path: string) { function filterStatus( status: Properties.propStatus, -) { +): boolean { switch (status) { case 'valid': return filter('status.valid'); @@ -153,7 +151,7 @@ function filterStatus( function filterKind( kind: Properties.propKind, -) { +): boolean { switch (kind) { case 'assert': return filter('kind.assert'); case 'loop_invariant': return filter('kind.invariant'); @@ -173,7 +171,7 @@ function filterKind( } } -function filterAlarm(alarm: string | undefined) { +function filterAlarm(alarm: string | undefined): boolean { if (alarm) { if (!filter('alarms.alarms')) return false; switch (alarm) { @@ -202,7 +200,7 @@ function filterAlarm(alarm: string | undefined) { return filter('alarms.others'); } -function filterEva(p: Property) { +function filterEva(p: Property): boolean { let b = true; if (p.priority === false && filter('eva.priority_only')) b = false; @@ -224,7 +222,7 @@ function filterEva(p: Property) { return b; } -function filterProperty(p: Property) { +function filterProperty(p: Property): boolean { return filterStatus(p.status) && filterKind(p.kind) && filterAlarm(p.alarm) @@ -236,32 +234,33 @@ function filterProperty(p: Property) { // -------------------------------------------------------------------------- const renderCode: Renderer<string> = - (text: string) => (<Code className="code-column" title={text}>{text}</Code>); + (text: string): JSX.Element => + (<Code className="code-column" title={text}>{text}</Code>); const renderTag: Renderer<States.Tag> = - (d: States.Tag) => <Label label={d.label ?? d.name} title={d.descr} />; + (d: States.Tag): JSX.Element => + (<Label label={d.label ?? d.name} title={d.descr} />); const renderNames: Renderer<string[]> = - (names: string[]) => { + (names: string[]): JSX.Element | null => { const label = names?.join(': '); return (label ? <Label label={label} /> : null); }; const renderDir: Renderer<Ast.source> = - (loc: Ast.source) => ( - <Code className="code-column" label={loc.dir} title={loc.file} /> - ); + (loc: Ast.source): JSX.Element => + (<Code className="code-column" label={loc.dir} title={loc.file} />); const renderFile: Renderer<Ast.source> = - (loc: Ast.source) => ( - <Code className="code-column" label={loc.base} title={loc.file} /> - ); + (loc: Ast.source): JSX.Element => + (<Code className="code-column" label={loc.base} title={loc.file} />); const renderPriority: Renderer<boolean> = - (prio: boolean) => (prio ? <Icon id="ATTENTION" /> : null); + (prio: boolean): JSX.Element | null => + (prio ? <Icon id="ATTENTION" /> : null); const renderTaint: Renderer<States.Tag> = - (taint: States.Tag) => { + (taint: States.Tag): JSX.Element | null => { let id = null; let color = 'black'; switch (taint.name) { @@ -275,11 +274,11 @@ const renderTaint: Renderer<States.Tag> = return (id ? <Icon id={id} fill={color} title={taint.descr} /> : null); }; -function ColumnCode<Row>(props: ColumnProps<Row, string>) { +function ColumnCode<Row>(props: ColumnProps<Row, string>): JSX.Element { return <Column render={renderCode} {...props} />; } -function ColumnTag<Row>(props: ColumnProps<Row, States.Tag>) { +function ColumnTag<Row>(props: ColumnProps<Row, States.Tag>): JSX.Element { return <Column render={renderTag} {...props} />; } @@ -349,12 +348,12 @@ class PropertyModel this.setFilter(this.filterItem.bind(this)); } - setFilterFunction(kf?: string) { + setFilterFunction(kf?: string): void { this.filterFun = kf; if (filter('currentFunction')) this.reload(); } - filterItem(prop: Property) { + filterItem(prop: Property): boolean { const kf = prop.fct; const cf = this.filterFun; const filteringFun = cf && filter('currentFunction'); @@ -376,7 +375,7 @@ interface SectionProps { children: React.ReactNode; } -function Section(props: SectionProps) { +function Section(props: SectionProps): JSX.Element { const settings = `properties-section-${props.label}`; return ( <Folder @@ -396,9 +395,9 @@ interface CheckFieldProps { path: string; } -function CheckField(props: CheckFieldProps) { +function CheckField(props: CheckFieldProps): JSX.Element { const [value, setValue] = useFilter(props.path); - const onChange = () => { setValue(); Reload.emit(); }; + const onChange = (): void => { setValue(); Reload.emit(); }; return ( <Checkbox style={{ @@ -415,7 +414,7 @@ function CheckField(props: CheckFieldProps) { /* eslint-disable max-len */ -function PropertyFilter() { +function PropertyFilter(): JSX.Element { return ( <Scroll> <CheckField label="Current function" path="currentFunction" /> @@ -497,35 +496,29 @@ function PropertyFilter() { // --- Property Columns // ------------------------------------------------------------------------- -const PropertyColumns = () => { - +function PropertyColumns(): JSX.Element { const statusDict = States.useTags(Properties.propStatusTags); const kindDict = States.useTags(Properties.propKindTags); const alarmDict = States.useTags(Properties.alarmsTags); const taintDict = States.useTags(Eva.taintStatusTags); - const getStatus = React.useCallback( ({ status: st }: Property) => (statusDict.get(st) ?? { name: st }), [statusDict], ); - const getKind = React.useCallback( ({ kind: kd }: Property) => (kindDict.get(kd) ?? { name: kd }), [kindDict], ); - const getAlarm = React.useCallback( ({ alarm }: Property) => ( alarm === undefined ? alarm : (alarmDict.get(alarm) ?? { name: alarm }) ), [alarmDict], ); - const getTaint = React.useCallback( ({ taint }: Property) => (taintDict.get(taint) ?? { name: taint }), [taintDict], ); - return ( <> <Column @@ -587,10 +580,9 @@ const PropertyColumns = () => { /> </> ); +} -}; - -function FilterRatio({ model }: { model: PropertyModel }) { +function FilterRatio({ model }: { model: PropertyModel }): JSX.Element { Models.useModel(model); const [filtered, total] = [model.getRowCount(), model.getTotalRowCount()]; return ( @@ -608,7 +600,7 @@ function FilterRatio({ model }: { model: PropertyModel }) { // --- Properties Table // ------------------------------------------------------------------------- -export default function RenderProperties() { +export default function RenderProperties(): JSX.Element { // Hooks const model = React.useMemo(() => new PropertyModel(), []); diff --git a/ivette/src/frama-c/states.ts b/ivette/src/frama-c/states.ts index 14b03daf39dd0ed6e24699a0d9f9eda87b381733..c6b360cc86ad95daece6e8e54982d07c21570a05 100644 --- a/ivette/src/frama-c/states.ts +++ b/ivette/src/frama-c/states.ts @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Frama-C States // -------------------------------------------------------------------------- @@ -91,7 +89,7 @@ Server.onShutdown(() => { * Current Project (Custom React Hook). * @return The current project. */ -export function useProject() { +export function useProject(): string | undefined { Dome.useUpdate(PROJECT); return currentProject; } @@ -104,7 +102,7 @@ export function useProject() { * Emits `PROJECT`. * @param project The project identifier. */ -export async function setProject(project: string) { +export async function setProject(project: string): Promise<void> { if (Server.isRunning()) { try { const sr: Server.SetRequest<string, null> = { @@ -162,11 +160,11 @@ export function useRequest<In, Out>( const footprint = project ? JSON.stringify([project, rq.name, params]) : undefined; - const update = (opt: Out | undefined | null) => { + const update = (opt: Out | undefined | null): void => { if (opt !== null) setResponse(opt); }; - async function trigger() { + async function trigger(): Promise<void> { if (project && rq && params !== undefined) { try { update(options.pending); @@ -285,14 +283,14 @@ class SyncState<A> { PROJECT.on(this.update); } - getValue() { + getValue(): A | undefined { if (!this.upToDate && Server.isRunning()) { this.update(); } return this.value; } - async setValue(v: A) { + async setValue(v: A): Promise<void> { try { this.upToDate = true; this.value = v; @@ -308,7 +306,7 @@ class SyncState<A> { } } - async update() { + async update(): Promise<void> { try { this.upToDate = true; this.value = undefined; @@ -390,11 +388,11 @@ class SyncArray<K, A> { this.update = this.update.bind(this); } - update() { + update(): void { if (!this.upToDate && Server.isRunning()) this.fetch(); } - async fetch() { + async fetch(): Promise<void> { if (this.fetching || !Server.isRunning()) return; try { this.fetching = true; @@ -423,7 +421,7 @@ class SyncArray<K, A> { } } - async reload() { + async reload(): Promise<void> { try { this.model.clear(); this.upToDate = false; @@ -466,7 +464,7 @@ Server.onShutdown(() => syncArrays.clear()); // -------------------------------------------------------------------------- /** Force a Synchronized Array to reload. */ -export function reloadArray<K, A>(arr: Array<K, A>) { +export function reloadArray<K, A>(arr: Array<K, A>): void { lookupSyncArray(arr).reload(); } @@ -775,12 +773,12 @@ export const GlobalSelection = new GlobalState<Selection>(emptySelection); Server.onShutdown(() => GlobalSelection.setValue(emptySelection)); -export function setHovered(h: Hovered) { GlobalHovered.setValue(h); } +export function setHovered(h: Hovered): void { GlobalHovered.setValue(h); } export function useHovered(): [Hovered, (h: Hovered) => void] { return useGlobalState(GlobalHovered); } -export function setSelection(location: Location, meta = false) { +export function setSelection(location: Location, meta = false): void { const s = GlobalSelection.getValue(); GlobalSelection.setValue(reducer(s, { location })); if (meta) MetaSelection.emit(location); @@ -791,12 +789,12 @@ export function useSelection(): [Selection, (a: SelectionActions) => void] { const [current, setCurrent] = useGlobalState(GlobalSelection); const callback = React.useCallback((action) => { setCurrent(reducer(current, action)); - }, [ current, setCurrent ]); + }, [current, setCurrent]); return [current, callback]; } /** Resets the selected locations. */ -export async function resetSelection() { +export async function resetSelection(): Promise<void> { GlobalSelection.setValue(emptySelection); if (Server.isRunning()) { try { diff --git a/ivette/src/ivette/index.tsx b/ivette/src/ivette/index.tsx index 596110753d9bd49d9f689ad019cc86a216138fe1..6916d0be581172c2799d10c5964d4648934bef7d 100644 --- a/ivette/src/ivette/index.tsx +++ b/ivette/src/ivette/index.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - /* --------------------------------------------------------------------------*/ /* --- Lab View Component ---*/ /* --------------------------------------------------------------------------*/ @@ -74,7 +72,7 @@ let GROUP: string | undefined; If provided, the group is used by default for all components registered during the continuation. */ -export function registerGroup(group: ItemProps, job?: () => void) { +export function registerGroup(group: ItemProps, job?: () => void): void { Lab.addLibraryItem('groups', group); if (job) { const STACK = GROUP; @@ -96,7 +94,7 @@ export function registerGroup(group: ItemProps, job?: () => void) { */ export type Layout = string | Layout[]; -function makeLayout(ly: Layout, hsplit = false) { +function makeLayout(ly: Layout, hsplit = false): JSX.Element | null { if (typeof (ly) === 'string') return <GridItem id={ly} />; if (!ly) return null; if (hsplit) { @@ -122,7 +120,7 @@ export interface ViewLayoutProps extends ItemProps { } /** Register a new View. */ -export function registerView(view: ViewLayoutProps) { +export function registerView(view: ViewLayoutProps): void { const { layout, ...viewprops } = view; Lab.addLibraryItem('views', { ...viewprops, @@ -143,7 +141,7 @@ export interface ComponentProps extends ContentProps { Register the given Ivette Component. Components are sorted by rank and identifier among each group. */ -export function registerComponent(props: ComponentProps) { +export function registerComponent(props: ComponentProps): void { Lab.addLibraryItem('components', { group: GROUP, ...props }); } @@ -163,7 +161,7 @@ export interface TitleBarProps { Defines an alternative component title bar in current context. Default values are taken from the associated component. */ -export function TitleBar(props: TitleBarProps) { +export function TitleBar(props: TitleBarProps): JSX.Element | null { const { icon, label, title, children } = props; const context = Lab.useTitleContext(); if (!context.id) return null; @@ -190,15 +188,15 @@ export interface ToolProps { children?: React.ReactNode; } -export function registerSidebar(panel: ToolProps) { +export function registerSidebar(panel: ToolProps): void { Ext.SIDEBAR.register(panel); } -export function registerToolbar(tools: ToolProps) { +export function registerToolbar(tools: ToolProps): void { Ext.TOOLBAR.register(tools); } -export function registerStatusbar(status: ToolProps) { +export function registerStatusbar(status: ToolProps): void { Ext.STATUSBAR.register(status); } diff --git a/ivette/src/renderer/Extensions.tsx b/ivette/src/renderer/Extensions.tsx index 145f9b66b1edf59e1dbe3064a763f7ecd83f95d4..acd7d6ec63c0bd1062aab2e882c85433c8300dd0 100644 --- a/ivette/src/renderer/Extensions.tsx +++ b/ivette/src/renderer/Extensions.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - /* --------------------------------------------------------------------------*/ /* --- Ivette Extensions ---*/ /* --------------------------------------------------------------------------*/ @@ -41,7 +39,7 @@ export interface ElementProps { children?: React.ReactNode; } -function byPanel(p: ElementProps, q: ElementProps) { +function byPanel(p: ElementProps, q: ElementProps): number { const rp = p.rank ?? 0; const rq = q.rank ?? 0; if (rp < rq) return -1; @@ -58,14 +56,14 @@ export class ElementRack { private rank = 1; private readonly items = new Map<string, ElementProps>(); - register(elt: ElementProps) { + register(elt: ElementProps): void { if (elt.rank === undefined) elt.rank = this.rank; this.rank++; this.items.set(elt.id, elt); UPDATED.emit(); } - render() { + render(): JSX.Element { const panels: ElementProps[] = []; this.items.forEach((p) => { if (p.children) { panels.push(p); } }); const contents = panels.sort(byPanel).map((p) => p.children); @@ -74,7 +72,7 @@ export class ElementRack { } -export function useRack(E: ElementRack) { +export function useRack(E: ElementRack): JSX.Element { Dome.useUpdate(UPDATED); return E.render(); } @@ -83,8 +81,8 @@ export const SIDEBAR = new ElementRack(); export const TOOLBAR = new ElementRack(); export const STATUSBAR = new ElementRack(); -export function Sidebar() { return useRack(SIDEBAR); } -export function Toolbar() { return useRack(TOOLBAR); } -export function Statusbar() { return useRack(STATUSBAR); } +export function Sidebar(): JSX.Element { return useRack(SIDEBAR); } +export function Toolbar(): JSX.Element { return useRack(TOOLBAR); } +export function Statusbar(): JSX.Element { return useRack(STATUSBAR); } /* --------------------------------------------------------------------------*/ diff --git a/ivette/src/renderer/Preferences.tsx b/ivette/src/renderer/Preferences.tsx index 47ec4a74e2386cd36fbbe70660b97d3db90ccc4b..e587512dfc0ba04bbeeab9c2805ad0be3b27254d 100644 --- a/ivette/src/renderer/Preferences.tsx +++ b/ivette/src/renderer/Preferences.tsx @@ -20,8 +20,6 @@ /* */ /* ************************************************************************ */ -/* eslint-disable @typescript-eslint/explicit-function-return-type */ - // -------------------------------------------------------------------------- // --- Main React Component rendered by './index.js' // -------------------------------------------------------------------------- @@ -69,7 +67,7 @@ function ThemeFields(): JSX.Element { // --- Editor Fields // -------------------------------------------------------------------------- -function EditorFields() { +function EditorFields(): JSX.Element { const fontsize = Forms.useValid( Settings.useGlobalSettings(IvettePrefs.EditorFontSize) ); @@ -102,7 +100,10 @@ function EditorFields() { // -------------------------------------------------------------------------- // --- Console Scrollback Forms // -------------------------------------------------------------------------- -function ConsoleScrollbackFields(props: IvettePrefs.ConsoleScrollbackProps) { + +function ConsoleScrollbackFields( + props: IvettePrefs.ConsoleScrollbackProps +): JSX.Element { const scrollback = Forms.useDefined(Forms.useValid( Settings.useGlobalSettings(props.scrollback), )); @@ -114,7 +115,7 @@ function ConsoleScrollbackFields(props: IvettePrefs.ConsoleScrollbackProps) { // --- Export Components // -------------------------------------------------------------------------- -export default function Preferences() { +export default function Preferences(): JSX.Element { return ( <Forms.Page> <Forms.Section label="Theme" unfold>