Merge branch 'main' into export-v2
This commit is contained in:
commit
bde85721ff
6
.prettierrc.json
Normal file
6
.prettierrc.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"tabWidth": 4,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": true,
|
||||||
|
"bracketSameLine": true
|
||||||
|
}
|
|
@ -52,6 +52,7 @@
|
||||||
"electron-builder-notarize": "^1.2.0",
|
"electron-builder-notarize": "^1.2.0",
|
||||||
"electron-download": "^4.1.1",
|
"electron-download": "^4.1.1",
|
||||||
"eslint": "^7.23.0",
|
"eslint": "^7.23.0",
|
||||||
|
"prettier": "2.5.1",
|
||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
37
src/main.ts
37
src/main.ts
|
@ -1,11 +1,4 @@
|
||||||
import {
|
import { app, BrowserWindow, Menu, Tray, dialog, nativeImage } from 'electron';
|
||||||
app,
|
|
||||||
BrowserWindow,
|
|
||||||
Menu,
|
|
||||||
Tray,
|
|
||||||
dialog,
|
|
||||||
nativeImage,
|
|
||||||
} from 'electron';
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as isDev from 'electron-is-dev';
|
import * as isDev from 'electron-is-dev';
|
||||||
import AppUpdater from './utils/appUpdater';
|
import AppUpdater from './utils/appUpdater';
|
||||||
|
@ -14,7 +7,6 @@ import setupIpcComs from './utils/ipcComms';
|
||||||
import { buildContextMenu, buildMenuBar } from './utils/menuUtil';
|
import { buildContextMenu, buildMenuBar } from './utils/menuUtil';
|
||||||
import initSentry from './utils/sentry';
|
import initSentry from './utils/sentry';
|
||||||
|
|
||||||
|
|
||||||
let tray: Tray;
|
let tray: Tray;
|
||||||
let mainWindow: BrowserWindow;
|
let mainWindow: BrowserWindow;
|
||||||
|
|
||||||
|
@ -24,49 +16,46 @@ let updateIsAvailable = false;
|
||||||
|
|
||||||
export const isAppQuitting = (): boolean => {
|
export const isAppQuitting = (): boolean => {
|
||||||
return appIsQuitting;
|
return appIsQuitting;
|
||||||
}
|
};
|
||||||
export const setIsAppQuitting = (value: boolean): void => {
|
export const setIsAppQuitting = (value: boolean): void => {
|
||||||
appIsQuitting = value;
|
appIsQuitting = value;
|
||||||
}
|
};
|
||||||
|
|
||||||
export const isUpdateAvailable = (): boolean => {
|
export const isUpdateAvailable = (): boolean => {
|
||||||
return updateIsAvailable;
|
return updateIsAvailable;
|
||||||
}
|
};
|
||||||
export const setIsUpdateAvailable = (value: boolean): void => {
|
export const setIsUpdateAvailable = (value: boolean): void => {
|
||||||
updateIsAvailable = value;
|
updateIsAvailable = value;
|
||||||
}
|
};
|
||||||
|
|
||||||
// Disable error dialogs by overriding
|
// Disable error dialogs by overriding
|
||||||
dialog.showErrorBox = function (title, content) {
|
dialog.showErrorBox = function (title, content) {
|
||||||
console.log(`${title}\n${content}`);
|
console.log(`${title}\n${content}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const gotTheLock = app.requestSingleInstanceLock();
|
const gotTheLock = app.requestSingleInstanceLock();
|
||||||
if (!gotTheLock) {
|
if (!gotTheLock) {
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
|
|
||||||
app.on('second-instance', () => {
|
app.on('second-instance', () => {
|
||||||
// Someone tried to run a second instance, we should focus our window.
|
// Someone tried to run a second instance, we should focus our window.
|
||||||
if (mainWindow) {
|
if (mainWindow) {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
if (mainWindow.isMinimized()) {
|
if (mainWindow.isMinimized()) {
|
||||||
mainWindow.restore()
|
mainWindow.restore();
|
||||||
}
|
}
|
||||||
mainWindow.focus()
|
mainWindow.focus();
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
// This method will be called when Electron has finished
|
// This method will be called when Electron has finished
|
||||||
// initialization and is ready to create browser windows.
|
// initialization and is ready to create browser windows.
|
||||||
// Some APIs can only be used after this event occurs.
|
// Some APIs can only be used after this event occurs.
|
||||||
app.on('ready', () => {
|
app.on('ready', () => {
|
||||||
initSentry();
|
initSentry();
|
||||||
setIsUpdateAvailable(false)
|
setIsUpdateAvailable(false);
|
||||||
mainWindow = createWindow();
|
mainWindow = createWindow();
|
||||||
Menu.setApplicationMenu(buildMenuBar())
|
Menu.setApplicationMenu(buildMenuBar());
|
||||||
|
|
||||||
app.on('activate', function () {
|
app.on('activate', function () {
|
||||||
// On macOS it's common to re-create a window in the app when the
|
// On macOS it's common to re-create a window in the app when the
|
||||||
|
@ -87,8 +76,4 @@ else {
|
||||||
AppUpdater.checkForUpdate(tray, mainWindow);
|
AppUpdater.checkForUpdate(tray, mainWindow);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,39 @@
|
||||||
import { BrowserWindow, dialog, Tray } from "electron"
|
import { BrowserWindow, dialog, Tray } from 'electron';
|
||||||
import { autoUpdater } from "electron-updater"
|
import { autoUpdater } from 'electron-updater';
|
||||||
import log from "electron-log"
|
import log from 'electron-log';
|
||||||
import { setIsAppQuitting, setIsUpdateAvailable } from "../main";
|
import { setIsAppQuitting, setIsUpdateAvailable } from '../main';
|
||||||
import { buildContextMenu } from "./menuUtil";
|
import { buildContextMenu } from './menuUtil';
|
||||||
|
|
||||||
class AppUpdater {
|
class AppUpdater {
|
||||||
constructor() {
|
constructor() {
|
||||||
log.transports.file.level = "debug"
|
log.transports.file.level = 'debug';
|
||||||
autoUpdater.logger = log;
|
autoUpdater.logger = log;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkForUpdate(tray: Tray, mainWindow: BrowserWindow) {
|
async checkForUpdate(tray: Tray, mainWindow: BrowserWindow) {
|
||||||
await autoUpdater.checkForUpdatesAndNotify()
|
await autoUpdater.checkForUpdatesAndNotify();
|
||||||
autoUpdater.on('update-downloaded', () => {
|
autoUpdater.on('update-downloaded', () => {
|
||||||
showUpdateDialog();
|
showUpdateDialog();
|
||||||
setIsUpdateAvailable(true);
|
setIsUpdateAvailable(true);
|
||||||
tray.setContextMenu(buildContextMenu(mainWindow));
|
tray.setContextMenu(buildContextMenu(mainWindow));
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new AppUpdater();
|
export default new AppUpdater();
|
||||||
|
|
||||||
|
export const showUpdateDialog = (): void => {
|
||||||
export const showUpdateDialog = ():void => {
|
dialog
|
||||||
dialog.showMessageBox({
|
.showMessageBox({
|
||||||
type: 'info',
|
type: 'info',
|
||||||
title: 'install update',
|
title: 'install update',
|
||||||
message: 'restart to update to the latest version of ente',
|
message: 'restart to update to the latest version of ente',
|
||||||
buttons: ['later', 'restart now']
|
buttons: ['later', 'restart now'],
|
||||||
}).then((buttonIndex) => {
|
})
|
||||||
if (buttonIndex.response === 1) {
|
.then((buttonIndex) => {
|
||||||
setIsAppQuitting(true); autoUpdater.quitAndInstall()
|
if (buttonIndex.response === 1) {
|
||||||
}
|
setIsAppQuitting(true);
|
||||||
})
|
autoUpdater.quitAndInstall();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
|
@ -1,8 +1,11 @@
|
||||||
import { BrowserWindow, dialog, ipcMain, Tray, Notification } from "electron";
|
import { BrowserWindow, dialog, ipcMain, Tray, Notification } from 'electron';
|
||||||
import { createWindow } from "./createWindow";
|
import { createWindow } from './createWindow';
|
||||||
import { buildContextMenu } from "./menuUtil";
|
import { buildContextMenu } from './menuUtil';
|
||||||
|
|
||||||
export default function setupIpcComs(tray: Tray, mainWindow: BrowserWindow): void {
|
export default function setupIpcComs(
|
||||||
|
tray: Tray,
|
||||||
|
mainWindow: BrowserWindow
|
||||||
|
): void {
|
||||||
ipcMain.on('select-dir', async (event) => {
|
ipcMain.on('select-dir', async (event) => {
|
||||||
const dialogWindow = new BrowserWindow({
|
const dialogWindow = new BrowserWindow({
|
||||||
width: 800,
|
width: 800,
|
||||||
|
@ -17,7 +20,9 @@ export default function setupIpcComs(tray: Tray, mainWindow: BrowserWindow): voi
|
||||||
properties: ['openDirectory'],
|
properties: ['openDirectory'],
|
||||||
});
|
});
|
||||||
const dir =
|
const dir =
|
||||||
result.filePaths && result.filePaths.length > 0 && result.filePaths[0];
|
result.filePaths &&
|
||||||
|
result.filePaths.length > 0 &&
|
||||||
|
result.filePaths[0];
|
||||||
dialogWindow.close();
|
dialogWindow.close();
|
||||||
event.returnValue = dir;
|
event.returnValue = dir;
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,66 +1,83 @@
|
||||||
import { Menu, app, shell, BrowserWindow, MenuItemConstructorOptions } from "electron";
|
import {
|
||||||
import { isUpdateAvailable, setIsAppQuitting } from "../main";
|
Menu,
|
||||||
import { showUpdateDialog } from "./appUpdater";
|
app,
|
||||||
|
shell,
|
||||||
|
BrowserWindow,
|
||||||
|
MenuItemConstructorOptions,
|
||||||
|
} from 'electron';
|
||||||
|
import { isUpdateAvailable, setIsAppQuitting } from '../main';
|
||||||
|
import { showUpdateDialog } from './appUpdater';
|
||||||
|
|
||||||
|
const isMac = process.platform === 'darwin';
|
||||||
|
|
||||||
const isMac = process.platform === 'darwin'
|
export function buildContextMenu(
|
||||||
|
mainWindow: BrowserWindow,
|
||||||
export function buildContextMenu(mainWindow: BrowserWindow, args: any = {}): Menu {
|
args: any = {}
|
||||||
const { export_progress, retry_export, paused } = args
|
): Menu {
|
||||||
|
const { export_progress, retry_export, paused } = args;
|
||||||
const contextMenu = Menu.buildFromTemplate([
|
const contextMenu = Menu.buildFromTemplate([
|
||||||
...(isUpdateAvailable() && [{
|
...(isUpdateAvailable() && [
|
||||||
label: 'update available',
|
{
|
||||||
click: () => showUpdateDialog()
|
label: 'update available',
|
||||||
}]),
|
click: () => showUpdateDialog(),
|
||||||
|
},
|
||||||
|
]),
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
...(export_progress
|
...(export_progress
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: export_progress,
|
label: export_progress,
|
||||||
click: () => mainWindow.show(),
|
click: () => mainWindow.show(),
|
||||||
},
|
},
|
||||||
...(paused ?
|
...(paused
|
||||||
[{
|
? [
|
||||||
label: 'resume export',
|
{
|
||||||
click: () => mainWindow.webContents.send('resume-export'),
|
label: 'resume export',
|
||||||
}] :
|
click: () =>
|
||||||
[{
|
mainWindow.webContents.send(
|
||||||
label: 'pause export',
|
'resume-export'
|
||||||
click: () => mainWindow.webContents.send('pause-export'),
|
),
|
||||||
},
|
},
|
||||||
{
|
]
|
||||||
label: 'stop export',
|
: [
|
||||||
click: () => mainWindow.webContents.send('stop-export'),
|
{
|
||||||
}]
|
label: 'pause export',
|
||||||
)
|
click: () =>
|
||||||
]
|
mainWindow.webContents.send('pause-export'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'stop export',
|
||||||
|
click: () =>
|
||||||
|
mainWindow.webContents.send('stop-export'),
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
]
|
||||||
: []),
|
: []),
|
||||||
...(retry_export
|
...(retry_export
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
label: 'export failed',
|
label: 'export failed',
|
||||||
click: null,
|
click: null,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'retry export',
|
label: 'retry export',
|
||||||
click: () => mainWindow.webContents.send('retry-export'),
|
click: () => mainWindow.webContents.send('retry-export'),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []
|
: []),
|
||||||
),
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'open ente',
|
label: 'open ente',
|
||||||
click: function () {
|
click: function () {
|
||||||
mainWindow.show();
|
mainWindow.show();
|
||||||
const isMac = process.platform === 'darwin'
|
const isMac = process.platform === 'darwin';
|
||||||
isMac && app.dock.show();
|
isMac && app.dock.show();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'quit ente',
|
label: 'quit ente',
|
||||||
click: function () {
|
click: function () {
|
||||||
setIsAppQuitting(true)
|
setIsAppQuitting(true);
|
||||||
app.quit();
|
app.quit();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -69,14 +86,16 @@ export function buildContextMenu(mainWindow: BrowserWindow, args: any = {}): Men
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildMenuBar(): Menu {
|
export function buildMenuBar(): Menu {
|
||||||
const template: MenuItemConstructorOptions[]=[
|
const template: MenuItemConstructorOptions[] = [
|
||||||
{
|
{
|
||||||
label:app.name,
|
label: app.name,
|
||||||
submenu:[...(isMac &&[
|
submenu: [
|
||||||
{
|
...((isMac && [
|
||||||
label: "about" ,
|
{
|
||||||
role: 'about'
|
label: 'about',
|
||||||
}]) as MenuItemConstructorOptions[],
|
role: 'about',
|
||||||
|
},
|
||||||
|
]) as MenuItemConstructorOptions[]),
|
||||||
{
|
{
|
||||||
label: 'faq',
|
label: 'faq',
|
||||||
click: () => shell.openExternal('https://ente.io/faq/'),
|
click: () => shell.openExternal('https://ente.io/faq/'),
|
||||||
|
@ -88,68 +107,83 @@ export function buildMenuBar(): Menu {
|
||||||
{
|
{
|
||||||
label: 'quit',
|
label: 'quit',
|
||||||
accelerator: 'CommandOrControl+Q',
|
accelerator: 'CommandOrControl+Q',
|
||||||
click() { setIsAppQuitting(true); app.quit(); }
|
click() {
|
||||||
}
|
setIsAppQuitting(true);
|
||||||
]
|
app.quit();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'edit',
|
label: 'edit',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'undo',label:"undo" },
|
{ role: 'undo', label: 'undo' },
|
||||||
{ role: 'redo' ,label:"redo"},
|
{ role: 'redo', label: 'redo' },
|
||||||
{ type: 'separator'},
|
|
||||||
{ role: 'cut' ,label:"cut"},
|
|
||||||
{ role: 'copy' ,label:"copy"},
|
|
||||||
{ role: 'paste' ,label:"paste"},
|
|
||||||
...(isMac ?[
|
|
||||||
{ role: 'pasteAndMatchStyle' ,label:"paste and match style"},
|
|
||||||
{ role: 'delete' ,label:"delete"},
|
|
||||||
{ role: 'selectAll' ,label:"select all"},
|
|
||||||
{ type: 'separator'},
|
|
||||||
{
|
|
||||||
label: 'speech',
|
|
||||||
submenu: [
|
|
||||||
{ role: 'startSpeaking' ,label:"start speaking"},
|
|
||||||
{ role: 'stopSpeaking' ,label:"stop speaking"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
] : [
|
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'selectAll',label:"select all" }
|
{ role: 'cut', label: 'cut' },
|
||||||
])as MenuItemConstructorOptions[]
|
{ role: 'copy', label: 'copy' },
|
||||||
]
|
{ role: 'paste', label: 'paste' },
|
||||||
|
...((isMac
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
role: 'pasteAndMatchStyle',
|
||||||
|
label: 'paste and match style',
|
||||||
|
},
|
||||||
|
{ role: 'delete', label: 'delete' },
|
||||||
|
{ role: 'selectAll', label: 'select all' },
|
||||||
|
{ type: 'separator' },
|
||||||
|
{
|
||||||
|
label: 'speech',
|
||||||
|
submenu: [
|
||||||
|
{
|
||||||
|
role: 'startSpeaking',
|
||||||
|
label: 'start speaking',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'stopSpeaking',
|
||||||
|
label: 'stop speaking',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{ type: 'separator' },
|
||||||
|
{ role: 'selectAll', label: 'select all' },
|
||||||
|
]) as MenuItemConstructorOptions[]),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
// { role: 'viewMenu' }
|
// { role: 'viewMenu' }
|
||||||
{
|
{
|
||||||
label: 'view',
|
label: 'view',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'reload',label:"reload" },
|
{ role: 'reload', label: 'reload' },
|
||||||
{ role: 'forceReload',label:"force reload" },
|
{ role: 'forceReload', label: 'force reload' },
|
||||||
{ role: 'toggleDevTools' ,label:"toggle devTools"},
|
{ role: 'toggleDevTools', label: 'toggle devTools' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'resetZoom',label:"reset zoom" },
|
{ role: 'resetZoom', label: 'reset zoom' },
|
||||||
{ role: 'zoomIn',label:"zoom in" },
|
{ role: 'zoomIn', label: 'zoom in' },
|
||||||
{ role: 'zoomOut' ,label:"zoom out"},
|
{ role: 'zoomOut', label: 'zoom out' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'togglefullscreen' ,label:"toggle fullscreen"}
|
{ role: 'togglefullscreen', label: 'toggle fullscreen' },
|
||||||
]
|
],
|
||||||
},
|
},
|
||||||
// { role: 'windowMenu' }
|
// { role: 'windowMenu' }
|
||||||
{
|
{
|
||||||
label: 'window',
|
label: 'window',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'minimize' ,label:"minimize"},
|
{ role: 'minimize', label: 'minimize' },
|
||||||
...(isMac ? [
|
...((isMac
|
||||||
{ type: 'separator'},
|
? [
|
||||||
{ role: 'front',label:"front" },
|
{ type: 'separator' },
|
||||||
{ type: 'separator' },
|
{ role: 'front', label: 'front' },
|
||||||
{ role: 'window' ,label:"window"}
|
{ type: 'separator' },
|
||||||
] : [
|
{ role: 'window', label: 'window' },
|
||||||
{ role: 'close' ,label:"close"}
|
]
|
||||||
])as MenuItemConstructorOptions[]
|
: [
|
||||||
]
|
{ role: 'close', label: 'close' },
|
||||||
},
|
]) as MenuItemConstructorOptions[]),
|
||||||
|
],
|
||||||
]
|
},
|
||||||
return Menu.buildFromTemplate(template)
|
];
|
||||||
|
return Menu.buildFromTemplate(template);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
import * as Sentry from "@sentry/electron/dist/main";
|
import * as Sentry from '@sentry/electron/dist/main';
|
||||||
|
|
||||||
const SENTRY_DSN="https://e9268b784d1042a7a116f53c58ad2165@sentry.ente.io/5";
|
const SENTRY_DSN = 'https://e9268b784d1042a7a116f53c58ad2165@sentry.ente.io/5';
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const version =require('../../package.json').version;
|
const version = require('../../package.json').version;
|
||||||
|
|
||||||
function initSentry():void{
|
function initSentry(): void {
|
||||||
Sentry.init({ dsn: SENTRY_DSN,release:version});
|
Sentry.init({ dsn: SENTRY_DSN, release: version });
|
||||||
}
|
}
|
||||||
|
|
||||||
export default initSentry;
|
export default initSentry;
|
||||||
|
|
|
@ -2727,6 +2727,11 @@ prepend-http@^2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
|
||||||
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
|
||||||
|
|
||||||
|
prettier@2.5.1:
|
||||||
|
version "2.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.5.1.tgz#fff75fa9d519c54cf0fce328c1017d94546bc56a"
|
||||||
|
integrity sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==
|
||||||
|
|
||||||
pretty-bytes@^1.0.2:
|
pretty-bytes@^1.0.2:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
|
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"
|
||||||
|
|
Loading…
Reference in a new issue