From 56c5aa8a046b14005f735005f5c8a9838938119e Mon Sep 17 00:00:00 2001 From: Hakan Shehu Date: Thu, 18 Sep 2025 14:08:01 +0200 Subject: [PATCH] Improve assets loading --- .gitignore | 5 +- apps/desktop/src/main.ts | 10 +- apps/desktop/src/main/path-service.ts | 4 + apps/mobile/app.json | 5 +- apps/mobile/src/app.tsx | 103 ++++++++++++--- apps/mobile/src/lib/assets.ts | 51 ++++++++ apps/mobile/src/services/app-service.ts | 18 --- apps/mobile/src/services/kysely-service.ts | 11 +- apps/mobile/src/services/path-service.ts | 117 +++++++----------- apps/web/src/services/file-system.ts | 5 +- apps/web/src/services/path-service.ts | 4 + packages/client/src/services/asset-service.ts | 1 + packages/client/src/services/path-service.ts | 2 +- .../src/services/workspaces/file-service.ts | 17 ++- packages/ui/src/components/accounts/login.tsx | 2 +- packages/ui/src/components/app.tsx | 22 +++- scripts/src/postinstall/index.ts | 16 +-- 17 files changed, 252 insertions(+), 141 deletions(-) create mode 100644 apps/mobile/src/lib/assets.ts delete mode 100644 apps/mobile/src/services/app-service.ts diff --git a/.gitignore b/.gitignore index 0de0da1f..60476be0 100644 --- a/.gitignore +++ b/.gitignore @@ -155,8 +155,6 @@ src/scripts/icons/temp/ # Ignore desktop assets apps/desktop/assets/emojis.db apps/desktop/assets/icons.db -apps/desktop/assets/emojis.svg -apps/desktop/assets/icons.svg apps/desktop/assets/fonts/neotrax.otf apps/desktop/assets/colanode-logo-black.png apps/desktop/assets/colanode-logo-black.ico @@ -173,6 +171,9 @@ apps/web/public/assets/colanode-logo-black-512.png # Ignore mobile assets apps/mobile/assets/ui/index.html +apps/mobile/assets/emojis.db +apps/mobile/assets/icons.db +apps/mobile/assets/fonts/neotrax.otf .expo web-build/ diff --git a/apps/desktop/src/main.ts b/apps/desktop/src/main.ts index 57e56f88..7fc89f3f 100644 --- a/apps/desktop/src/main.ts +++ b/apps/desktop/src/main.ts @@ -7,6 +7,7 @@ import { globalShortcut, dialog, } from 'electron'; +import path from 'path'; import started from 'electron-squirrel-startup'; import { updateElectronApp, UpdateSourceType } from 'update-electron-app'; @@ -56,9 +57,9 @@ const createWindow = async () => { fullscreenable: true, minWidth: 800, minHeight: 600, - icon: app.path.join(app.path.assets, 'colanode-logo-black.png'), + icon: path.join(app.path.assets, 'colanode-logo-black.png'), webPreferences: { - preload: app.path.join(__dirname, 'preload.js'), + preload: path.join(__dirname, 'preload.js'), }, autoHideMenuBar: true, titleBarStyle: 'hiddenInset', @@ -104,10 +105,7 @@ const createWindow = async () => { // mainWindow.webContents.openDevTools(); } else { mainWindow.loadFile( - app.path.join( - __dirname, - `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html` - ) + path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`) ); } diff --git a/apps/desktop/src/main/path-service.ts b/apps/desktop/src/main/path-service.ts index 0826abdc..96931bda 100644 --- a/apps/desktop/src/main/path-service.ts +++ b/apps/desktop/src/main/path-service.ts @@ -154,4 +154,8 @@ export class DesktopPathService implements PathService { public get iconsDatabase(): string { return this.nativePath.join(this.getAssetsSourcePath(), 'icons.db'); } + + public font(name: string): string { + return this.nativePath.join(this.getAssetsSourcePath(), 'fonts', name); + } } diff --git a/apps/mobile/app.json b/apps/mobile/app.json index 179c6b87..ad35575c 100644 --- a/apps/mobile/app.json +++ b/apps/mobile/app.json @@ -26,9 +26,6 @@ "web": { "favicon": "./assets/favicon.png" }, - "plugins": [ - "expo-asset", - "expo-sqlite" - ] + "plugins": ["expo-asset", "expo-sqlite"] } } diff --git a/apps/mobile/src/app.tsx b/apps/mobile/src/app.tsx index ba0e4001..44a08f71 100644 --- a/apps/mobile/src/app.tsx +++ b/apps/mobile/src/app.tsx @@ -1,25 +1,31 @@ import { Asset } from 'expo-asset'; +import { modelName } from 'expo-device'; import { useCallback, useEffect, useRef, useState } from 'react'; import { View, ActivityIndicator, Platform } from 'react-native'; import { WebView, WebViewMessageEvent } from 'react-native-webview'; import { eventBus } from '@colanode/client/lib'; +import { AppMeta, AppService } from '@colanode/client/services'; import { generateId, IdType } from '@colanode/core'; +import { copyAssets, indexHtmlAsset } from '@colanode/mobile/lib/assets'; import { Message } from '@colanode/mobile/lib/types'; -import { app } from '@colanode/mobile/services/app-service'; - -import indexHtml from '../assets/ui/index.html'; +import { MobileFileSystem } from '@colanode/mobile/services/file-system'; +import { MobileKyselyService } from '@colanode/mobile/services/kysely-service'; +import { MobilePathService } from '@colanode/mobile/services/path-service'; export const App = () => { const windowId = useRef(generateId(IdType.Window)); + const webViewRef = useRef(null); + const app = useRef(null); + const appInitialized = useRef(false); + const [uri, setUri] = useState(null); const [baseDir, setBaseDir] = useState(null); - const webViewRef = useRef(null); useEffect(() => { (async () => { - const indexAsset = Asset.fromModule(indexHtml); - await indexAsset.downloadAsync(); // no-op in prod + const indexAsset = Asset.fromModule(indexHtmlAsset); + await indexAsset.downloadAsync(); const localUri = indexAsset.localUri ?? indexAsset.uri; const dir = localUri.replace(/index\.html$/, ''); setUri(localUri); @@ -27,28 +33,91 @@ export const App = () => { })(); }, []); + useEffect(() => { + (async () => { + try { + const paths = new MobilePathService(); + await copyAssets(paths); + + const appMeta: AppMeta = { + type: 'mobile', + platform: modelName ?? 'unknown', + }; + + app.current = new AppService( + appMeta, + new MobileFileSystem(), + new MobileKyselyService(), + paths + ); + + await app.current.migrate(); + await app.current.init(); + appInitialized.current = true; + } catch (error) { + console.error(error); + } + })(); + }, []); + const handleMessage = useCallback(async (e: WebViewMessageEvent) => { const message = JSON.parse(e.nativeEvent.data) as Message; if (message.type === 'console') { - console.log( - `[WebView ${message.level.toUpperCase()}] ${message.timestamp} ${message.message}` - ); + if (message.level === 'log') { + console.log( + `[WebView ${message.level.toUpperCase()}] ${message.timestamp} ${message.message}` + ); + } else if (message.level === 'warn') { + console.warn( + `[WebView ${message.level.toUpperCase()}] ${message.timestamp} ${message.message}` + ); + } else if (message.level === 'error') { + console.error( + `[WebView ${message.level.toUpperCase()}] ${message.timestamp} ${message.message}` + ); + } else if (message.level === 'info') { + console.info( + `[WebView ${message.level.toUpperCase()}] ${message.timestamp} ${message.message}` + ); + } else if (message.level === 'debug') { + console.debug( + `[WebView ${message.level.toUpperCase()}] ${message.timestamp} ${message.message}` + ); + } } else if (message.type === 'init') { - await app.migrate(); - await app.init(); + let count = 0; + while (!appInitialized.current) { + await new Promise((resolve) => setTimeout(resolve, 50)); + count++; + if (count > 100) { + throw new Error('App initialization timed out'); + } + } sendMessage({ type: 'init_result' }); } else if (message.type === 'mutation') { - const result = await app.mediator.executeMutation(message.input); + if (!app.current) { + return; + } + + const result = await app.current.mediator.executeMutation(message.input); sendMessage({ type: 'mutation_result', mutationId: message.mutationId, result, }); } else if (message.type === 'query') { - const result = await app.mediator.executeQuery(message.input); + if (!app.current) { + return; + } + + const result = await app.current.mediator.executeQuery(message.input); sendMessage({ type: 'query_result', queryId: message.queryId, result }); } else if (message.type === 'query_and_subscribe') { - const result = await app.mediator.executeQueryAndSubscribe( + if (!app.current) { + return; + } + + const result = await app.current.mediator.executeQueryAndSubscribe( message.key, message.windowId, message.input @@ -61,7 +130,11 @@ export const App = () => { result, }); } else if (message.type === 'query_unsubscribe') { - app.mediator.unsubscribeQuery(message.key, message.windowId); + if (!app.current) { + return; + } + + app.current.mediator.unsubscribeQuery(message.key, message.windowId); } else if (message.type === 'event') { eventBus.publish(message.event); } diff --git a/apps/mobile/src/lib/assets.ts b/apps/mobile/src/lib/assets.ts new file mode 100644 index 00000000..1172b3c6 --- /dev/null +++ b/apps/mobile/src/lib/assets.ts @@ -0,0 +1,51 @@ +import { Asset } from 'expo-asset'; +import { Directory, File } from 'expo-file-system'; + +import { PathService } from '@colanode/client/services'; + +import emojisDatabaseAsset from '../../assets/emojis.db'; +import neotraxFontAsset from '../../assets/fonts/neotrax.otf'; +import iconsDatabaseAsset from '../../assets/icons.db'; +import indexHtmlAsset from '../../assets/ui/index.html'; + +export { + indexHtmlAsset, + emojisDatabaseAsset, + iconsDatabaseAsset, + neotraxFontAsset, +}; + +export const copyAssets = async (paths: PathService) => { + try { + const assetsDir = new Directory(paths.assets); + assetsDir.create({ intermediates: true, idempotent: true }); + + const fontsDir = new Directory(paths.fonts); + fontsDir.create({ intermediates: true, idempotent: true }); + + await copyAsset( + Asset.fromModule(emojisDatabaseAsset), + paths.emojisDatabase + ); + await copyAsset(Asset.fromModule(iconsDatabaseAsset), paths.iconsDatabase); + await copyAsset( + Asset.fromModule(neotraxFontAsset), + paths.font('neotrax.otf') + ); + } catch (error) { + console.error(error); + } +}; + +export const copyAsset = async (asset: Asset, path: string) => { + await asset.downloadAsync(); + const localUri = asset.localUri ?? asset.uri; + + const dest = new File(path); + if (dest.exists) { + dest.delete(); + } + + const assetFile = new File(localUri); + assetFile.copy(dest); +}; diff --git a/apps/mobile/src/services/app-service.ts b/apps/mobile/src/services/app-service.ts deleted file mode 100644 index 0bd1741f..00000000 --- a/apps/mobile/src/services/app-service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { modelName } from 'expo-device'; - -import { AppMeta, AppService } from '@colanode/client/services'; -import { MobileFileSystem } from '@colanode/mobile/services/file-system'; -import { MobileKyselyService } from '@colanode/mobile/services/kysely-service'; -import { MobilePathService } from '@colanode/mobile/services/path-service'; - -const appMeta: AppMeta = { - type: 'mobile', - platform: modelName ?? 'unknown', -}; - -export const app = new AppService( - appMeta, - new MobileFileSystem(), - new MobileKyselyService(), - new MobilePathService() -); diff --git a/apps/mobile/src/services/kysely-service.ts b/apps/mobile/src/services/kysely-service.ts index f9d95e26..82966706 100644 --- a/apps/mobile/src/services/kysely-service.ts +++ b/apps/mobile/src/services/kysely-service.ts @@ -18,6 +18,7 @@ import { import { KyselyBuildOptions, KyselyService } from '@colanode/client/services'; import { MobileFileSystem } from '@colanode/mobile/services/file-system'; +import { MobilePathService } from '@colanode/mobile/services/path-service'; export class MobileKyselyService implements KyselyService { private readonly fs = new MobileFileSystem(); @@ -87,9 +88,17 @@ class ExpoSqliteDriver implements Driver { class ExpoSqliteConnection implements DatabaseConnection { private readonly database: SQLiteDatabase; private readonly options: KyselyBuildOptions; + private readonly paths: MobilePathService = new MobilePathService(); constructor(options: KyselyBuildOptions) { - this.database = openDatabaseSync(options.path); + const databaseName = this.paths.filename(options.path); + const databaseDirectory = this.paths.dirname(options.path); + + this.database = openDatabaseSync( + databaseName, + undefined, + databaseDirectory + ); this.options = options; } diff --git a/apps/mobile/src/services/path-service.ts b/apps/mobile/src/services/path-service.ts index a4375396..afbadae4 100644 --- a/apps/mobile/src/services/path-service.ts +++ b/apps/mobile/src/services/path-service.ts @@ -1,66 +1,65 @@ -import { Paths } from 'expo-file-system'; +import { Paths, File, Directory } from 'expo-file-system'; import { PathService } from '@colanode/client/services'; export class MobilePathService implements PathService { - private readonly appPath = Paths.document.uri; - private readonly appDatabasePath = this.join(this.appPath, 'app.db'); - private readonly accountsDirectoryPath = this.join(this.appPath, 'accounts'); + private readonly accountsDirectoryPath = new Directory( + Paths.document, + 'accounts' + ); private getAccountDirectoryPath(accountId: string): string { - return this.join(this.accountsDirectoryPath, accountId); + return new Directory(this.accountsDirectoryPath, accountId).uri; } private getWorkspaceDirectoryPath( accountId: string, workspaceId: string ): string { - return this.join( + return new Directory( this.getAccountDirectoryPath(accountId), 'workspaces', workspaceId - ); + ).uri; } private getWorkspaceFilesDirectoryPath( accountId: string, workspaceId: string ): string { - return this.join( + return new Directory( this.getWorkspaceDirectoryPath(accountId, workspaceId), 'files' - ); + ).uri; } private getAccountAvatarsDirectoryPath(accountId: string): string { - return this.join(this.getAccountDirectoryPath(accountId), 'avatars'); + return new Directory(this.getAccountDirectoryPath(accountId), 'avatars') + .uri; } private getAssetsSourcePath(): string { - // In React Native/Expo, we should copy bundled assets to document directory - // for file system access, or use Asset.fromModule for bundled assets - // For now, we'll use a path in the document directory where assets will be copied - return this.join(this.appPath, 'bundled-assets'); + return new Directory(Paths.document, 'assets').uri; } public get app(): string { - return this.appPath; + return Paths.document.uri; } public get appDatabase(): string { - return this.appDatabasePath; + return new File(Paths.document, 'app.db').uri; } public get accounts(): string { - return this.accountsDirectoryPath; + return this.accountsDirectoryPath.uri; } public get temp(): string { - return this.join(this.appPath, 'temp'); + return new Directory(Paths.document, 'temp').uri; } public tempFile(name: string): string { - return this.join(this.appPath, 'temp', name); + return new File(Paths.document, 'temp', name).uri; } public account(accountId: string): string { @@ -68,7 +67,7 @@ export class MobilePathService implements PathService { } public accountDatabase(accountId: string): string { - return this.join(this.getAccountDirectoryPath(accountId), 'account.db'); + return new File(this.getAccountDirectoryPath(accountId), 'account.db').uri; } public workspace(accountId: string, workspaceId: string): string { @@ -76,10 +75,10 @@ export class MobilePathService implements PathService { } public workspaceDatabase(accountId: string, workspaceId: string): string { - return this.join( + return new File( this.getWorkspaceDirectoryPath(accountId, workspaceId), 'workspace.db' - ); + ).uri; } public workspaceFiles(accountId: string, workspaceId: string): string { @@ -92,10 +91,10 @@ export class MobilePathService implements PathService { fileId: string, extension: string ): string { - return this.join( + return new File( this.getWorkspaceFilesDirectoryPath(accountId, workspaceId), fileId + extension - ); + ).uri; } public accountAvatars(accountId: string): string { @@ -103,62 +102,30 @@ export class MobilePathService implements PathService { } public accountAvatar(accountId: string, avatarId: string): string { - return this.join( + return new File( this.getAccountAvatarsDirectoryPath(accountId), avatarId + '.jpeg' - ); + ).uri; } - public dirname(dir: string): string { - // Remove trailing slash if present - const normalizedPath = dir.replace(/\/+$/, ''); - const lastSlashIndex = normalizedPath.lastIndexOf('/'); - if (lastSlashIndex === -1) { - return '.'; + public dirname(path: string): string { + const info = Paths.info(path); + if (info.isDirectory) { + return path; } - if (lastSlashIndex === 0) { - return '/'; - } - return normalizedPath.substring(0, lastSlashIndex); + + const file = new File(path); + return file.parentDirectory.uri; } - public filename(file: string): string { - const basename = file.substring(file.lastIndexOf('/') + 1); - const lastDotIndex = basename.lastIndexOf('.'); - if (lastDotIndex === -1 || lastDotIndex === 0) { - return basename; - } - return basename.substring(0, lastDotIndex); - } - - public join(...paths: string[]): string { - if (paths.length === 0) return '.'; - - // Filter out empty strings and normalize paths - const normalizedPaths = paths - .filter((path) => path && path.length > 0) - .map((path) => path.replace(/\/+$/, '')); // Remove trailing slashes - - if (normalizedPaths.length === 0) return '.'; - - // Join with single slashes - const result = normalizedPaths.join('/'); - - // Handle absolute paths (starting with /) - if (paths[0] && paths[0].startsWith('/')) { - return '/' + result.replace(/^\/+/, ''); - } - - return result.replace(/\/+/g, '/'); // Replace multiple slashes with single slash + public filename(path: string): string { + const file = new File(path); + return file.name; } public extension(name: string): string { - const basename = name.substring(name.lastIndexOf('/') + 1); - const lastDotIndex = basename.lastIndexOf('.'); - if (lastDotIndex === -1 || lastDotIndex === 0) { - return ''; - } - return basename.substring(lastDotIndex); + const file = new File(name); + return file.extension; } public get assets(): string { @@ -166,14 +133,18 @@ export class MobilePathService implements PathService { } public get fonts(): string { - return this.join(this.getAssetsSourcePath(), 'fonts'); + return new Directory(this.getAssetsSourcePath(), 'fonts').uri; } public get emojisDatabase(): string { - return this.join(this.getAssetsSourcePath(), 'emojis.db'); + return new File(this.getAssetsSourcePath(), 'emojis.db').uri; } public get iconsDatabase(): string { - return this.join(this.getAssetsSourcePath(), 'icons.db'); + return new File(this.getAssetsSourcePath(), 'icons.db').uri; + } + + public font(name: string): string { + return new File(this.getAssetsSourcePath(), 'fonts', name).uri; } } diff --git a/apps/web/src/services/file-system.ts b/apps/web/src/services/file-system.ts index f1f40c4c..56cc9e6a 100644 --- a/apps/web/src/services/file-system.ts +++ b/apps/web/src/services/file-system.ts @@ -72,8 +72,9 @@ export class WebFileSystem implements FileSystem { //Ensure the data type is compatible with the File Write API private ensureArrayBuffer(data: Uint8Array): ArrayBuffer { const arrayBuffer: ArrayBuffer = - data.buffer instanceof ArrayBuffer ? - data.buffer : new ArrayBuffer(data.byteLength); + data.buffer instanceof ArrayBuffer + ? data.buffer + : new ArrayBuffer(data.byteLength); if (!(data.buffer instanceof ArrayBuffer)) { const view = new Uint8Array(arrayBuffer); diff --git a/apps/web/src/services/path-service.ts b/apps/web/src/services/path-service.ts index f7b04f31..c211f82a 100644 --- a/apps/web/src/services/path-service.ts +++ b/apps/web/src/services/path-service.ts @@ -101,4 +101,8 @@ export class WebPathService implements PathService { const parts = path.split('.'); return parts.length > 1 ? '.' + parts[parts.length - 1] : ''; } + + public font(name: string): string { + return this.join(this.assetsSourcePath, 'fonts', name); + } } diff --git a/packages/client/src/services/asset-service.ts b/packages/client/src/services/asset-service.ts index 17bd832c..59e649d4 100644 --- a/packages/client/src/services/asset-service.ts +++ b/packages/client/src/services/asset-service.ts @@ -19,6 +19,7 @@ export class AssetService { path: this.app.path.emojisDatabase, readonly: true, }); + this.icons = this.app.kysely.build({ path: this.app.path.iconsDatabase, readonly: true, diff --git a/packages/client/src/services/path-service.ts b/packages/client/src/services/path-service.ts index a251a7d5..6fdd3e23 100644 --- a/packages/client/src/services/path-service.ts +++ b/packages/client/src/services/path-service.ts @@ -19,10 +19,10 @@ export interface PathService { accountAvatar: (accountId: string, avatarId: string) => string; dirname: (path: string) => string; filename: (path: string) => string; - join: (...paths: string[]) => string; extension: (path: string) => string; assets: string; fonts: string; emojisDatabase: string; iconsDatabase: string; + font: (name: string) => string; } diff --git a/packages/client/src/services/workspaces/file-service.ts b/packages/client/src/services/workspaces/file-service.ts index 4e7ba0da..20c58f65 100644 --- a/packages/client/src/services/workspaces/file-service.ts +++ b/packages/client/src/services/workspaces/file-service.ts @@ -389,7 +389,12 @@ export class FileService { } private buildFilePath(id: string, extension: string): string { - return this.app.path.join(this.filesDir, `${id}${extension}`); + return this.app.path.workspaceFile( + this.workspace.accountId, + this.workspace.id, + id, + extension + ); } public async cleanupFiles(): Promise { @@ -423,7 +428,15 @@ export class FileService { continue; } - const filePath = this.app.path.join(this.filesDir, fileIdMap[fileId]!); + const fsFile = fileIdMap[fileId]!; + const name = this.app.path.filename(fsFile); + const extension = this.app.path.extension(fsFile); + const filePath = this.app.path.workspaceFile( + this.workspace.accountId, + this.workspace.id, + name, + extension + ); await this.app.fs.delete(filePath); } } diff --git a/packages/ui/src/components/accounts/login.tsx b/packages/ui/src/components/accounts/login.tsx index 9a0c1aa7..6db45c10 100644 --- a/packages/ui/src/components/accounts/login.tsx +++ b/packages/ui/src/components/accounts/login.tsx @@ -16,7 +16,7 @@ export const Login = () => { return (
-
+

colanode

diff --git a/packages/ui/src/components/app.tsx b/packages/ui/src/components/app.tsx index 342f3c9b..6aaad5aa 100644 --- a/packages/ui/src/components/app.tsx +++ b/packages/ui/src/components/app.tsx @@ -18,13 +18,23 @@ export const App = ({ type }: AppProps) => { const [initialized, setInitialized] = useState(false); const [openLogin, setOpenLogin] = useState(false); - const appMetadataListQuery = useLiveQuery({ - type: 'app.metadata.list', - }); + const appMetadataListQuery = useLiveQuery( + { + type: 'app.metadata.list', + }, + { + enabled: initialized, + } + ); - const accountListQuery = useLiveQuery({ - type: 'account.list', - }); + const accountListQuery = useLiveQuery( + { + type: 'account.list', + }, + { + enabled: initialized, + } + ); useEffect(() => { window.colanode.init().then(() => { diff --git a/scripts/src/postinstall/index.ts b/scripts/src/postinstall/index.ts index c9de594b..cc5c41a1 100644 --- a/scripts/src/postinstall/index.ts +++ b/scripts/src/postinstall/index.ts @@ -20,6 +20,7 @@ const FONTS_OTF_PATH = path.resolve(FONTS_DIR, NEOTRAX_FONT_NAME); const DESKTOP_ASSETS_DIR = path.resolve('apps', 'desktop', 'assets'); const WEB_ASSETS_DIR = path.resolve('apps', 'web', 'public', 'assets'); +const MOBILE_ASSETS_DIR = path.resolve('apps', 'mobile', 'assets'); const copyFile = (source: string, target: string | string[]) => { if (!fs.existsSync(source)) { @@ -40,24 +41,19 @@ const copyFile = (source: string, target: string | string[]) => { const execute = () => { copyFile(EMOJIS_DB_PATH, path.resolve(DESKTOP_ASSETS_DIR, 'emojis.db')); + copyFile(EMOJIS_DB_PATH, path.resolve(MOBILE_ASSETS_DIR, 'emojis.db')); copyFile(EMOJIS_MIN_DB_PATH, path.resolve(WEB_ASSETS_DIR, 'emojis.db')); - - copyFile(EMOJI_SVG_PATH, [ - path.resolve(DESKTOP_ASSETS_DIR, 'emojis.svg'), - path.resolve(WEB_ASSETS_DIR, 'emojis.svg'), - ]); + copyFile(EMOJI_SVG_PATH, path.resolve(WEB_ASSETS_DIR, 'emojis.svg')); copyFile(ICONS_DB_PATH, path.resolve(DESKTOP_ASSETS_DIR, 'icons.db')); + copyFile(ICONS_DB_PATH, path.resolve(MOBILE_ASSETS_DIR, 'icons.db')); copyFile(ICONS_MIN_DB_PATH, path.resolve(WEB_ASSETS_DIR, 'icons.db')); - - copyFile(ICONS_SVG_PATH, [ - path.resolve(DESKTOP_ASSETS_DIR, 'icons.svg'), - path.resolve(WEB_ASSETS_DIR, 'icons.svg'), - ]); + copyFile(ICONS_SVG_PATH, path.resolve(WEB_ASSETS_DIR, 'icons.svg')); copyFile(FONTS_OTF_PATH, [ path.resolve(DESKTOP_ASSETS_DIR, 'fonts', NEOTRAX_FONT_NAME), path.resolve(WEB_ASSETS_DIR, 'fonts', NEOTRAX_FONT_NAME), + path.resolve(MOBILE_ASSETS_DIR, 'fonts', NEOTRAX_FONT_NAME), ]); copyFile(