UNPKG

tiny-electron-essentials

Version:

A lightweight and modular utility library for Electron apps, offering simplified window management, tray support, IPC channels, and custom frameless window styling.

441 lines (381 loc) 14.6 kB
'use strict'; var fs = require('fs'); var promises = require('fs/promises'); var path = require('path'); /** * Save CSS content into a .css file with strict validation. * * @param {string} directory - The folder path where the file will be saved. * @param {string} filename - The name of the file (should end with .css). * @param {string} cssContent - The CSS content to be saved. * @returns {Promise<void>} * @throws {Error} If validation fails or write fails. */ async function saveCssFile(directory, filename, cssContent) { if (typeof directory !== 'string' || directory.trim() === '') throw new Error('Invalid directory path.'); if (typeof filename !== 'string' || !/^[\w\- ]+\.css$/.test(filename)) throw new Error('Invalid filename. Must be a valid name ending with .css'); if (typeof cssContent !== 'string') throw new Error('CSS content must be a string.'); const absolutePath = path.resolve(directory); const fullFilePath = path.join(absolutePath, filename); if (!fs.existsSync(absolutePath)) throw new Error(`Directory does not exist: ${absolutePath}`); try { await promises.writeFile(fullFilePath, cssContent, 'utf8'); console.log(`✔ CSS file saved to: ${fullFilePath}`); } catch (error) { const message = error instanceof Error ? error.message : String(error); throw new Error(`Failed to save CSS file: ${message}`); } } // TinyWindowFrameManager /** * Returns the default CSS variables for the custom window frame. * * This CSS string defines a set of variables under `:root` that control * colors, layout, spacing, fonts, and component sizes for a window frame UI. * * @returns {string} CSS string containing :root variables for styling the window frame. */ const getDefaultWindowFrameRoot = () => { return ` :root { /* Layout */ --frame-root-background: #fff; --frame-height: 32px; /* Colors */ --frame-background: rgba(30, 30, 30, 1); --frame-blur-background: rgba(30, 30, 30, 0.95); /* Border */ --frame-border-color: rgb(76 76 76); --frame-border-radius: 7px; --frame-border-size: 1px; /* Text */ --frame-title-font-size: 8pt; --frame-font-size: 10pt; --frame-font-family: system-ui, sans-serif; --frame-font-color: white; --frame-font-blur-color: rgba(255, 255, 255, 0.6); /* Icons */ --frame-icon-size: 20px; /* Buttons */ --frame-button-width: 32px; --frame-button-hover-background: rgba(255, 255, 255, 0.1); --frame-button-active-background: rgba(255, 255, 255, 0.2); --frame-close-button-hover-background: #C42B1C; --frame-close-button-active-background: #B12719; /* Padding */ --frame-padding-x: 8px; --frame-gap: 6px; /* Menu */ --frame-menu-max-width: 200px; --frame-menu-gap: 4px; } `; }; /** * Returns the default CSS rules for the custom window frame layout and behavior. * * This CSS string styles the window borders, title bar, buttons, icons, menus, * and supports fullscreen and blur states. It relies on a function to generate * dynamic class selectors for component names. * * @param {Object} [settings={}] - Optional settings to customize the CSS output. * @param {(className?: string, extra?: string, extra2?: string) => string} [settings.getElementName] * Function that returns a valid CSS selector string based on provided class names or modifiers. * * @param {string} [settings.fullscreenClass] * Class name applied to `<body>` to indicate fullscreen mode, which affects frame visibility. * * @param {string} [settings.blurClass] * Class name applied to `<body>` to indicate blur mode, changing color schemes for certain elements. * * @param {string} [settings.maximizedClass] * Class name applied to `<body>` to indicate maximized mode, changing color schemes for certain elements. * * @returns {string} CSS string for the full custom window frame styling. * * @throws {Error} If any of the settings are invalid, such as missing `getElementName` or invalid class names. */ const getDefaultWindowFrameStyle = ({ getElementName, fullscreenClass, blurClass, maximizedClass, } = {}) => { if (typeof getElementName !== 'function') throw new Error( 'Invalid argument: "getElementName" must be a function that returns CSS selectors.', ); if (typeof fullscreenClass !== 'string') throw new Error('Invalid argument: "fullscreenClass" must be a string.'); if (typeof blurClass !== 'string') throw new Error('Invalid argument: "blurClass" must be a string.'); return ` /* Base */ body { height: 100%; width: 100%; } ${getElementName()} { position: fixed; inset: 0; display: flex; flex-direction: column; overflow: hidden; } /* Border */ ${getElementName('.custom-window-frame')} { border-top: var(--frame-border-size) solid var(--frame-border-color); border-left: var(--frame-border-size) solid var(--frame-border-color); border-right: var(--frame-border-size) solid var(--frame-border-color); } ${getElementName('.custom-window-frame')}, ${getElementName('.frame-top')} { border-top-left-radius: var(--frame-border-radius); border-top-right-radius: var(--frame-border-radius); } ${getElementName('.frame-top-left')} { border-top-left-radius: var(--frame-border-radius); } ${getElementName('.frame-top-right')} { border-top-right-radius: var(--frame-border-radius); } ${getElementName('.window-content')} { border-bottom: var(--frame-border-size) solid var(--frame-border-color); border-left: var(--frame-border-size) solid var(--frame-border-color); border-right: var(--frame-border-size) solid var(--frame-border-color); border-bottom-left-radius: var(--frame-border-radius); border-bottom-right-radius: var(--frame-border-radius); } /* Frame */ ${getElementName('.custom-window-frame')} { pointer-events: none; user-select: none; color: var(--frame-font-color); } ${getElementName('.window-content')} { flex: 1; overflow: auto; pointer-events: all; background: var(--frame-root-background); } ${getElementName('.frame-top')} { position: relative; width: 100%; height: var(--frame-height); display: block; background: var(--frame-background); pointer-events: all; -webkit-app-region: drag; } ${getElementName('.frame-top-left')}, ${getElementName('.frame-top-center')}, ${getElementName('.frame-top-right')} { display: flex; align-items: center; gap: var(--frame-gap); } ${getElementName('.frame-top-left')}, ${getElementName('.frame-top-right')} { flex: 0 0 auto; padding-top: 0px; padding-bottom: 0px; } ${getElementName('.frame-top-left')} { padding-right: var(--frame-padding-x); display: inline-flex; height: 100%; } ${getElementName('.frame-top-right')} { padding-left: var(--frame-padding-x); display: inline-flex; height: 100%; position: absolute; right: 0; } ${getElementName('.frame-top')} { position: relative; } ${getElementName('.frame-top-center')} { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); display: flex; align-items: center; justify-content: center; padding: 0 var(--frame-padding-x); overflow: hidden; white-space: nowrap; } /* Title */ ${getElementName('.frame-title')} { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-size: var(--frame-title-font-size); font-family: var(--frame-font-family); } /* Icon */ ${getElementName('.frame-icon')} { padding-left: var(--frame-padding-x); width: var(--frame-icon-size); height: var(--frame-icon-size); background-size: contain; background-repeat: no-repeat; background-position: center; } /* Buttons style base */ ${getElementName('.frame-menu > button')}, ${getElementName('.frame-buttons > button')}, ${getElementName('.frame-menu > button')}:focus, ${getElementName('.frame-menu > button')}:active, ${getElementName('.frame-menu > button')}:hover, ${getElementName('.frame-buttons > button')}:focus, ${getElementName('.frame-buttons > button')}:active, ${getElementName('.frame-buttons > button')}:hover, ${getElementName('.frame-menu > button')}:disabled, ${getElementName('.frame-buttons > button')}:disabled { outline: none; box-shadow: none; text-shadow: none; } ${getElementName('.frame-menu > button')}:disabled, ${getElementName('.frame-buttons > button')}:disabled { opacity: 0.5; } /* Buttons */ ${getElementName('.frame-menu > button')} { font-size: var(--frame-font-size); font-family: var(--frame-font-family); background-color: transparent; border: none; color: var(--frame-font-color); padding: 4px 8px; border-radius: 4px; cursor: default; } ${getElementName('.frame-buttons')} { height: 100%; } ${getElementName('.frame-buttons > button')} { font-size: var(--frame-font-size); font-family: var(--frame-font-family); background: transparent; border: none; color: var(--frame-font-color); width: var(--frame-button-width); height: 100%; cursor: default; -webkit-app-region: no-drag; } ${getElementName('.frame-buttons > button:hover')}, ${getElementName('.frame-menu > button:hover')} { background-color: var(--frame-button-hover-background); } ${getElementName('.frame-menu')} { max-width: var(--frame-menu-max-width); overflow-x: auto; display: flex; gap: var(--frame-menu-gap); -webkit-app-region: no-drag; } ${getElementName('.frame-top-left .frame-menu:first-child')} { padding-left: var(--frame-padding-x); } ${getElementName('.frame-top-right .frame-menu:last-child')} { padding-right: var(--frame-padding-x); } /* Buttons Dropdown */ ${getElementName('.menu-dropdown')} { background: var(--frame-background); border: 1px solid var(--frame-border-color); padding: 4px; border-radius: 4px; display: none; gap: 4px; z-index: 9999; min-width: 150px; } ${getElementName('.menu-item')} { background: none; color: var(--frame-font-color); border: none; text-align: left; padding: 4px 8px; border-radius: 4px; cursor: pointer; } ${getElementName('.menu-item:hover')} { background: var(--frame-button-hover-background); } ${getElementName('.has-submenu::after')} { content: '▸'; float: right; margin-left: 6px; } /* Hide effects */ ${getElementName('.frame-menu > button.hide')}, ${getElementName('.frame-icon.hide')} { opacity: 0; pointer-events: none; } /* Blur effects */ ${getElementName('.frame-title', '', `body.${blurClass}`)}, ${getElementName('.frame-menu > button', '', `body.${blurClass}`)}, ${getElementName('.frame-buttons > button', '', `body.${blurClass}`)} { color: var(--frame-font-blur-color); } ${getElementName('.frame-title:hover', '', `body.${blurClass}`)}, ${getElementName('.frame-menu > button:hover', '', `body.${blurClass}`)}, ${getElementName('.frame-buttons > button:hover', '', `body.${blurClass}`)} { color: var(--frame-font-color); } ${getElementName('.frame-top', '', `body.${blurClass}`)} { background: var(--frame-blur-background); } /* Active effects */ ${getElementName('.frame-buttons > button:active')}, ${getElementName('.frame-menu > button:active:not(.has-dropdown)')}, ${getElementName('.frame-menu > button.active')} { background-color: var(--frame-button-active-background); } /* Border last and first button */ ${getElementName('.frame-top-right .frame-buttons > button:last-child', '', `body:not(.${maximizedClass})`)} { border-top-right-radius: var(--frame-border-radius); } ${getElementName('.frame-top-left .frame-buttons:first-child > button:first-child', '', `body:not(.${maximizedClass})`)} { border-top-left-radius: var(--frame-border-radius); } /* Full Screen and Maximize */ ${getElementName('.custom-window-frame', '', `body.${fullscreenClass}`)} { display: none !important; } ${getElementName('.custom-window-frame', '', `body.${maximizedClass}`)} { border-top-left-radius: 0px !important; border-top-right-radius: 0px !important; border-top: 0px solid transparent !important; border-left: 0px solid transparent !important; border-right: 0px solid transparent !important; } ${getElementName('.window-content', '', `body.${fullscreenClass}`)}, ${getElementName('.window-content', '', `body.${maximizedClass}`)} { border-bottom: 0px solid transparent !important; border-left: 0px solid transparent !important; border-right: 0px solid transparent !important; border-bottom-left-radius: 0px !important; border-bottom-right-radius: 0px !important; } /* Close Button */ ${getElementName('.frame-buttons > .btn-close:hover')} { background-color: var(--frame-close-button-hover-background); } ${getElementName('.frame-buttons > .btn-close:active')} { background-color: var(--frame-close-button-active-background); } `; }; exports.getDefaultWindowFrameRoot = getDefaultWindowFrameRoot; exports.getDefaultWindowFrameStyle = getDefaultWindowFrameStyle; exports.saveCssFile = saveCssFile;