feat: add electron mode from VoTT project (#260)
This commit is contained in:
Родитель
c1c590c463
Коммит
2a3383d4a0
|
@ -0,0 +1,32 @@
|
|||
const path = require("path");
|
||||
|
||||
module.exports = {
|
||||
node: {
|
||||
__dirname: false,
|
||||
},
|
||||
target: "electron-main",
|
||||
entry: "./src/electron/main.ts",
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts?$/,
|
||||
use: [{
|
||||
loader: "ts-loader",
|
||||
options: {
|
||||
compilerOptions: {
|
||||
noEmit: false
|
||||
}
|
||||
}
|
||||
}],
|
||||
exclude: /node_modules/
|
||||
}
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".ts", ".js"]
|
||||
},
|
||||
output: {
|
||||
filename: "main.js",
|
||||
path: path.resolve(__dirname, "../build")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.common.js");
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: "development",
|
||||
devtool: "inline-source-map",
|
||||
})
|
|
@ -0,0 +1,7 @@
|
|||
const merge = require("webpack-merge");
|
||||
const common = require("./webpack.common.js");
|
||||
|
||||
module.exports = merge(common, {
|
||||
mode: "production",
|
||||
devtool: "cheap-module-source-map",
|
||||
})
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
24
package.json
24
package.json
|
@ -24,9 +24,9 @@
|
|||
"ol": "^5.3.3",
|
||||
"rc-align": "^2.4.5",
|
||||
"rc-checkbox": "^2.1.8",
|
||||
"react": "^16.12.0",
|
||||
"react": "^16.13.1",
|
||||
"react-color": "^2.17.3",
|
||||
"react-dom": "^16.12.0",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-jsonschema-form": "^1.3.0",
|
||||
"react-localization": "^1.0.15",
|
||||
"react-redux": "^7.1.3",
|
||||
|
@ -37,6 +37,7 @@
|
|||
"reactstrap": "^8.2.0",
|
||||
"redux": "^4.0.4",
|
||||
"redux-thunk": "^2.3.0",
|
||||
"rimraf": "^3.0.2",
|
||||
"serialize-javascript": "^3.0.0",
|
||||
"shortid": "^2.2.15",
|
||||
"utif": "^3.1.0",
|
||||
|
@ -49,6 +50,13 @@
|
|||
"react-start": "react-scripts start",
|
||||
"test": "react-scripts test --env=jsdom --silent",
|
||||
"eject": "react-scripts eject",
|
||||
"webpack:dev": "webpack --config ./config/webpack.dev.js",
|
||||
"webpack:prod": "webpack --config ./config/webpack.prod.js",
|
||||
"electron:run:dev": "yarn webpack:dev && electron . --remote-debugging-port=9223",
|
||||
"electron:run:prod": "yarn webpack:prod && electron . --remote-debugging-port=9223",
|
||||
"electron:start:dev": "yarn webpack:dev && yarn electron-start",
|
||||
"electron:start:prod": "yarn webpack:prod && yarn electron-start",
|
||||
"electron-start": "node src/electron/start",
|
||||
"tslint": "./node_modules/.bin/tslint 'src/**/*.ts*'",
|
||||
"tslintfix": "./node_modules/.bin/tslint 'src/**/*.ts*' --fix"
|
||||
},
|
||||
|
@ -83,6 +91,8 @@
|
|||
"@types/reactstrap": "^8.2.0",
|
||||
"@types/redux-logger": "^3.0.7",
|
||||
"acorn": "^7.1.1",
|
||||
"electron": "^8.3.0",
|
||||
"electron-builder": "^22.6.1",
|
||||
"enzyme": "^3.10.0",
|
||||
"enzyme-adapter-react-16": "^1.15.1",
|
||||
"eslint-utils": "^1.4.3",
|
||||
|
@ -91,13 +101,15 @@
|
|||
"minimist": "^1.2.2",
|
||||
"node-sass": "^4.14.0",
|
||||
"pdfjs-dist": "2.3.200",
|
||||
"react-scripts": "3.1.2",
|
||||
"react-scripts": "3.4.1",
|
||||
"redux-immutable-state-invariant": "^2.1.0",
|
||||
"redux-logger": "^3.0.6",
|
||||
"redux-mock-store": "^1.5.4",
|
||||
"ts-loader": "^6.2.1",
|
||||
"tslint": "^5.20.1",
|
||||
"typescript": "^3.8.2"
|
||||
"ts-loader": "^7.0.1",
|
||||
"tslint": "^6.1.1",
|
||||
"typescript": "^3.8.3",
|
||||
"webpack-cli": "^3.3.11",
|
||||
"webpack-merge": "^4.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.14.2",
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export interface IDeferred<T> {
|
||||
resolve(result?: T): void;
|
||||
reject(err?: any): void;
|
||||
then(value: T): Promise<T>;
|
||||
catch(err: any): Promise<T>;
|
||||
}
|
||||
|
||||
export class Deferred<T> implements IDeferred<T> {
|
||||
public promise: Promise<T>;
|
||||
|
||||
constructor() {
|
||||
this.promise = new Promise<T>((resolve, reject) => {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
});
|
||||
|
||||
this.then = this.promise.then.bind(this.promise) as (<T>(value: T) => Promise<T>);
|
||||
this.catch = this.promise.catch.bind(this.promise);
|
||||
}
|
||||
|
||||
// tslint:disable-next-line:no-empty
|
||||
public resolve = (result?: T) => {};
|
||||
// tslint:disable-next-line:no-empty
|
||||
public reject = (err?: any) => {};
|
||||
public then = (value: T): Promise<T> => { throw new Error("Not implemented yet"); };
|
||||
public catch = (err: any): Promise<T> => { throw new Error("Not implemented yet"); };
|
||||
}
|
|
@ -12,7 +12,7 @@ describe("Map Extensions", () => {
|
|||
beforeAll(registerMixins);
|
||||
|
||||
describe("forEachAsync", () => {
|
||||
const map = testArray.map((asset) => [asset.id, asset]) as Array<[string, IAsset]>;
|
||||
const map = testArray.map((asset) => [asset.id, asset]) as [string, IAsset][];
|
||||
const testMap = new Map<string, IAsset>(map);
|
||||
|
||||
const output = [];
|
||||
|
|
|
@ -17,10 +17,10 @@ export async function forEachAsync<K, V>(
|
|||
Guard.null(action);
|
||||
Guard.expression(batchSize, (value) => value > 0);
|
||||
|
||||
const all: Array<[K, V]> = [...this.entries()];
|
||||
const all: [K, V][] = [...this.entries()];
|
||||
|
||||
while (all.length > 0) {
|
||||
const batch: Array<[K, V]> = [];
|
||||
const batch: [K, V][] = [];
|
||||
|
||||
while (all.length > 0 && batch.length < batchSize) {
|
||||
batch.push(all.pop());
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
export const IpcEventNames = {
|
||||
Renderer: "ipc-renderer-proxy",
|
||||
Main: "ipc-main-proxy",
|
||||
};
|
||||
|
||||
export interface IIpcProxyMessage<TResult> {
|
||||
id: string;
|
||||
type: string;
|
||||
args?: any;
|
||||
error?: string;
|
||||
result?: TResult;
|
||||
debug?: string;
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import * as shortid from "shortid";
|
||||
import { IIpcProxyMessage, IpcEventNames } from "./ipcProxy";
|
||||
import { Deferred } from "./deferred";
|
||||
|
||||
export class IpcRendererProxy {
|
||||
|
||||
public static pending: { [id: string]: Deferred<any> } = {};
|
||||
|
||||
public static initialize() {
|
||||
if (IpcRendererProxy.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
IpcRendererProxy.ipcRenderer = (window as any).require("electron").ipcRenderer;
|
||||
IpcRendererProxy.ipcRenderer.on(IpcEventNames.Renderer, (sender: any, message: IIpcProxyMessage<any>) => {
|
||||
const deferred = IpcRendererProxy.pending[message.id];
|
||||
|
||||
if (!deferred) {
|
||||
throw new Error(`Cannot find deferred with id '${message.id}'`);
|
||||
}
|
||||
|
||||
if (message.error) {
|
||||
deferred.reject(message.error);
|
||||
} else {
|
||||
deferred.resolve(message.result);
|
||||
}
|
||||
|
||||
delete IpcRendererProxy.pending[message.id];
|
||||
});
|
||||
|
||||
IpcRendererProxy.initialized = true;
|
||||
}
|
||||
|
||||
public static send<TResult, TArgs>(type: string, args?: TArgs): Promise<TResult> {
|
||||
IpcRendererProxy.initialize();
|
||||
|
||||
const id = shortid.generate();
|
||||
const deferred = new Deferred<TResult>();
|
||||
IpcRendererProxy.pending[id] = deferred;
|
||||
|
||||
const outgoingArgs: IIpcProxyMessage<TArgs> = {
|
||||
id,
|
||||
type,
|
||||
args,
|
||||
};
|
||||
|
||||
IpcRendererProxy.ipcRenderer.send(IpcEventNames.Main, outgoingArgs);
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
private static ipcRenderer: any;
|
||||
private static initialized: boolean = false;
|
||||
}
|
|
@ -168,8 +168,8 @@ async function decryptString(str: string | ISecureString, secret) {
|
|||
}
|
||||
|
||||
export async function throttle<T>(max: number, arr: T[], worker: (payload: T) => Promise<any>) {
|
||||
const allPromises: Array<Promise<any>> = [];
|
||||
const runningPromises: Array<Promise<any>> = [];
|
||||
const allPromises: Promise<any>[] = [];
|
||||
const runningPromises: Promise<any>[] = [];
|
||||
let i = 0;
|
||||
while (i < arr.length) {
|
||||
const payload = arr[i];
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { BrowserWindow, IpcMain } from "electron";
|
||||
import { IIpcProxyMessage, IpcEventNames } from "../../common/ipcProxy";
|
||||
|
||||
export type IpcProxyHandler<T> = (sender: any, args: T) => any;
|
||||
|
||||
export class IpcMainProxy {
|
||||
|
||||
public handlers: { [type: string]: IpcProxyHandler<any> } = {};
|
||||
|
||||
constructor(private ipcMain: IpcMain, private browserWindow: BrowserWindow) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
public register<T>(type: string, handler: IpcProxyHandler<T>) {
|
||||
this.handlers[type] = handler;
|
||||
}
|
||||
|
||||
public registerProxy(proxyPrefix: string, provider: any) {
|
||||
Object.getOwnPropertyNames(provider.__proto__).forEach((memberName) => {
|
||||
if (typeof (provider[memberName]) === "function") {
|
||||
this.register(`${proxyPrefix}:${memberName}`, (sender: any, eventArgs: any[]) => {
|
||||
return provider[memberName].apply(provider, eventArgs);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public unregisterAll() {
|
||||
this.handlers = {};
|
||||
}
|
||||
|
||||
private init() {
|
||||
this.ipcMain.on(IpcEventNames.Main, (sender: any, message: IIpcProxyMessage<any>) => {
|
||||
const handler = this.handlers[message.type];
|
||||
if (!handler) {
|
||||
console.log(`No IPC proxy handler defined for event type '${message.type}'`);
|
||||
}
|
||||
|
||||
const returnArgs: IIpcProxyMessage<any> = {
|
||||
id: message.id,
|
||||
type: message.type,
|
||||
};
|
||||
|
||||
try {
|
||||
returnArgs.debug = JSON.stringify(message.args);
|
||||
|
||||
const handlerValue = handler(sender, message.args);
|
||||
if (handlerValue && handlerValue.then) {
|
||||
handlerValue.
|
||||
then((result: any) => {
|
||||
returnArgs.result = result;
|
||||
this.browserWindow.webContents.send(IpcEventNames.Renderer, returnArgs);
|
||||
})
|
||||
.catch((err: string) => {
|
||||
returnArgs.error = err;
|
||||
this.browserWindow.webContents.send(IpcEventNames.Renderer, returnArgs);
|
||||
});
|
||||
} else {
|
||||
returnArgs.result = handlerValue;
|
||||
this.browserWindow.webContents.send(IpcEventNames.Renderer, returnArgs);
|
||||
}
|
||||
} catch (err) {
|
||||
returnArgs.error = err;
|
||||
this.browserWindow.webContents.send(IpcEventNames.Renderer, returnArgs);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { app, ipcMain, BrowserWindow, BrowserWindowConstructorOptions } from "electron";
|
||||
import { IpcMainProxy } from "./common/ipcMainProxy";
|
||||
import LocalFileSystem from "./providers/storage/localFileSystem";
|
||||
|
||||
// Keep a global reference of the window object, if you don't, the window will
|
||||
// be closed automatically when the JavaScript object is garbage collected.
|
||||
let mainWindow: BrowserWindow | null;
|
||||
let ipcMainProxy: IpcMainProxy;
|
||||
|
||||
async function createWindow() {
|
||||
const windowOptions: BrowserWindowConstructorOptions = {
|
||||
width: 1024,
|
||||
height: 768,
|
||||
frame: process.platform === "linux",
|
||||
titleBarStyle: "hidden",
|
||||
backgroundColor: "#272B30",
|
||||
show: false,
|
||||
};
|
||||
|
||||
const staticUrl = process.env.ELECTRON_START_URL || `file:///${__dirname}/index.html`;
|
||||
if (process.env.ELECTRON_START_URL) {
|
||||
windowOptions.webPreferences = {
|
||||
nodeIntegration: true,
|
||||
webSecurity: false,
|
||||
};
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow(windowOptions);
|
||||
mainWindow.loadURL(staticUrl);
|
||||
mainWindow.maximize();
|
||||
|
||||
mainWindow.on("closed", () => {
|
||||
mainWindow = null;
|
||||
ipcMainProxy.unregisterAll();
|
||||
});
|
||||
|
||||
mainWindow.on("ready-to-show", () => {
|
||||
mainWindow!.show();
|
||||
});
|
||||
|
||||
if (!ipcMainProxy) {
|
||||
ipcMainProxy = new IpcMainProxy(ipcMain, mainWindow);
|
||||
|
||||
}
|
||||
|
||||
const localFileSystem = new LocalFileSystem(mainWindow);
|
||||
ipcMainProxy.registerProxy("LocalFileSystem", localFileSystem);
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on("ready", createWindow);
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on("window-all-closed", () => {
|
||||
// On OS X it is common for applications and their menu bar
|
||||
// to stay active until the user quits explicitly with Cmd + Q
|
||||
if (process.platform !== "darwin") {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on("activate", () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (mainWindow === null) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import rimraf from "rimraf";
|
||||
import { BrowserWindow, dialog } from "electron";
|
||||
import { IStorageProvider } from "../../../providers/storage/storageProviderFactory";
|
||||
import { IAsset, AssetState, AssetType, StorageType } from "../../../models/applicationState";
|
||||
import { AssetService } from "../../../services/assetService";
|
||||
import { constants } from "../../../common/constants";
|
||||
import { strings } from "../../../common/strings";
|
||||
|
||||
export default class LocalFileSystem implements IStorageProvider {
|
||||
|
||||
public storageType: StorageType.Local;
|
||||
|
||||
constructor(private browserWindow: BrowserWindow) {
|
||||
}
|
||||
|
||||
public selectContainer(): Promise<string> {
|
||||
return new Promise<string>(async (resolve, reject) => {
|
||||
const result = await dialog.showOpenDialog(this.browserWindow, {
|
||||
title: strings.connections.providers.local.selectFolder,
|
||||
buttonLabel: strings.connections.providers.local.chooseFolder,
|
||||
properties: ["openDirectory", "createDirectory"],
|
||||
});
|
||||
|
||||
if (!result.filePaths || result.filePaths.length !== 1) {
|
||||
return reject();
|
||||
}
|
||||
|
||||
resolve(result.filePaths[0]);
|
||||
});
|
||||
}
|
||||
|
||||
public readText(filePath: string): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
fs.readFile(path.normalize(filePath), (err: NodeJS.ErrnoException, data: Buffer) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(data.toString());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public readBinary(filePath: string): Promise<Buffer> {
|
||||
return new Promise<Buffer>((resolve, reject) => {
|
||||
fs.readFile(path.normalize(filePath), (err: NodeJS.ErrnoException, data: Buffer) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(data);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public writeBinary(filePath: string, contents: Buffer): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const containerName: fs.PathLike = path.normalize(path.dirname(filePath));
|
||||
const exists = fs.existsSync(containerName);
|
||||
if (!exists) {
|
||||
fs.mkdirSync(containerName);
|
||||
}
|
||||
|
||||
fs.writeFile(path.normalize(filePath), contents, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public writeText(filePath: string, contents: string): Promise<void> {
|
||||
const buffer = Buffer.from(contents);
|
||||
return this.writeBinary(filePath, buffer);
|
||||
}
|
||||
|
||||
public deleteFile(filePath: string): Promise<void> {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const exists = fs.existsSync(path.normalize(filePath));
|
||||
if (!exists) {
|
||||
resolve();
|
||||
}
|
||||
|
||||
fs.unlink(filePath, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public listFiles(folderPath: string): Promise<string[]> {
|
||||
return this.listItems(path.normalize(folderPath), (stats) => !stats.isDirectory());
|
||||
}
|
||||
|
||||
public listContainers(folderPath: string): Promise<string[]> {
|
||||
return this.listItems(path.normalize(folderPath), (stats) => stats.isDirectory());
|
||||
}
|
||||
|
||||
public createContainer(folderPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.exists(path.normalize(folderPath), (exists) => {
|
||||
if (exists) {
|
||||
resolve();
|
||||
} else {
|
||||
fs.mkdir(path.normalize(folderPath), (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public deleteContainer(folderPath: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.exists(path.normalize(folderPath), (exists) => {
|
||||
if (exists) {
|
||||
rimraf(path.normalize(folderPath), (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve();
|
||||
});
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async getAssets(folderPath?: string): Promise<IAsset[]> {
|
||||
const result: IAsset[] = [];
|
||||
const files = await this.listFiles(path.normalize(folderPath));
|
||||
for (const file of files) {
|
||||
const asset = await AssetService.createAssetFromFilePath(file);
|
||||
if (this.isSupportedAssetType(asset.type)) {
|
||||
const labelFileName = decodeURIComponent(`${asset.name}${constants.labelFileExtension}`);
|
||||
const ocrFileName = decodeURIComponent(`${asset.name}${constants.ocrFileExtension}`);
|
||||
|
||||
if (files.find((str) => str === labelFileName)) {
|
||||
asset.state = AssetState.Tagged;
|
||||
} else if (files.find((str) => str === ocrFileName)) {
|
||||
asset.state = AssetState.Visited;
|
||||
} else {
|
||||
asset.state = AssetState.NotVisited;
|
||||
}
|
||||
|
||||
result.push(asset);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of file system items matching the specified predicate within the folderPath
|
||||
* @param {string} folderPath
|
||||
* @param {(stats:fs.Stats)=>boolean} predicate
|
||||
* @returns {Promise} Resolved list of matching file system items
|
||||
*/
|
||||
private listItems(folderPath: string, predicate: (stats: fs.Stats) => boolean) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
fs.readdir(path.normalize(folderPath), async (err: NodeJS.ErrnoException, fileSystemItems: string[]) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
const getStatsTasks = fileSystemItems.map((name) => {
|
||||
const filePath = path.join(folderPath, name);
|
||||
return this.getStats(filePath);
|
||||
});
|
||||
|
||||
try {
|
||||
const statsResults = await Promise.all(getStatsTasks);
|
||||
const filteredItems = statsResults
|
||||
.filter((result) => predicate(result.stats))
|
||||
.map((result) => result.path);
|
||||
|
||||
resolve(filteredItems);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the node file system stats for the specified path
|
||||
* @param {string} path
|
||||
* @returns {Promise} Resolved path and stats
|
||||
*/
|
||||
private getStats(path: string): Promise<{ path: string, stats: fs.Stats }> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stats: fs.Stats) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
resolve({
|
||||
path,
|
||||
stats,
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private isSupportedAssetType(assetType: AssetType) {
|
||||
return assetType === AssetType.Image || assetType === AssetType.TIFF || assetType === AssetType.PDF;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,54 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
const net = require("net");
|
||||
const port = process.env.PORT ? (process.env.PORT - 100) : 3000;
|
||||
|
||||
process.env.ELECTRON_START_URL = `http://localhost:${port}`;
|
||||
|
||||
const client = new net.Socket();
|
||||
|
||||
let startedElectron = false;
|
||||
const tryConnection = () => client.connect({ port: port }, () => {
|
||||
client.end();
|
||||
if (!startedElectron) {
|
||||
console.log("starting electron");
|
||||
startedElectron = true;
|
||||
const exec = require("child_process").exec;
|
||||
const electron = exec("yarn electron:run:dev", (error, stdout, stderr) => {
|
||||
console.log("Electron Process Terminated");
|
||||
});
|
||||
|
||||
electron.stdout.on("data", (data) => {
|
||||
console.log(data);
|
||||
});
|
||||
|
||||
electron.on("message", (message, sendHandle) => {
|
||||
console.log(message);
|
||||
});
|
||||
|
||||
electron.on("error", (err) => {
|
||||
console.log(err);
|
||||
});
|
||||
|
||||
electron.on("exit", (code, signal) => {
|
||||
console.log(`Exit-Code: ${code}`);
|
||||
console.log(`Exit-Signal: ${signal}`);
|
||||
});
|
||||
|
||||
electron.on("close", (code, signal) => {
|
||||
console.log(`Close-Code: ${code}`);
|
||||
console.log(`Close-Signal: ${signal}`);
|
||||
});
|
||||
|
||||
electron.on("disconnect", () => {
|
||||
console.log("Electron Process Disconnect");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
tryConnection();
|
||||
|
||||
client.on("error", (error) => {
|
||||
setTimeout(tryConnection, 1000);
|
||||
});
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import { IpcRendererProxy } from "../../common/ipcRendererProxy";
|
||||
import { IStorageProvider } from "./storageProviderFactory";
|
||||
import { IAssetProvider } from "./assetProviderFactory";
|
||||
import { IAsset, StorageType } from "../../models/applicationState";
|
||||
|
||||
const PROXY_NAME = "LocalFileSystem";
|
||||
|
||||
/**
|
||||
* Options for Local File System
|
||||
* @member folderPath - Path to folder being used in provider
|
||||
*/
|
||||
export interface ILocalFileSystemProxyOptions {
|
||||
folderPath: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Storage Provider for Local File System. Only available in Electron application
|
||||
* Leverages the IpcRendererProxy
|
||||
*/
|
||||
export class LocalFileSystemProxy implements IStorageProvider, IAssetProvider {
|
||||
/**
|
||||
* @returns - StorageType.Local
|
||||
*/
|
||||
public storageType: StorageType.Local;
|
||||
constructor(private options?: ILocalFileSystemProxyOptions) {
|
||||
if (!this.options) {
|
||||
this.options = {
|
||||
folderPath: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Select container for use in provider
|
||||
*/
|
||||
public selectContainer(): Promise<string> {
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:selectContainer`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read text from file
|
||||
* @param fileName - Name of file from which to read text
|
||||
*/
|
||||
public readText(fileName: string): Promise<string> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:readText`, [filePath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Read buffer from file
|
||||
* @param fileName Name of file from which to read buffer
|
||||
*/
|
||||
public readBinary(fileName: string): Promise<Buffer> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:readBinary`, [filePath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file
|
||||
* @param fileName Name of file to delete
|
||||
*/
|
||||
public deleteFile(fileName: string): Promise<void> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:deleteFile`, [filePath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write text to file
|
||||
* @param fileName Name of target file
|
||||
* @param contents Contents to be written
|
||||
*/
|
||||
public writeText(fileName: string, contents: string): Promise<void> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:writeText`, [filePath, contents]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write buffer to file
|
||||
* @param fileName Name of target file
|
||||
* @param contents Contents to be written
|
||||
*/
|
||||
public writeBinary(fileName: string, contents: Buffer): Promise<void> {
|
||||
const filePath = [this.options.folderPath, fileName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:writeBinary`, [filePath, contents]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List files in directory
|
||||
* @param folderName - Name of folder from which to list files
|
||||
* @param ext - NOT CURRENTLY USED IN IMPLEMENTATION.
|
||||
*/
|
||||
public listFiles(folderName?: string, ext?: string): Promise<string[]> {
|
||||
const folderPath = folderName ? [this.options.folderPath, folderName].join("/") : this.options.folderPath;
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:listFiles`, [folderPath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* List directories inside another directory
|
||||
* @param folderName - Directory from which to list directories
|
||||
*/
|
||||
public listContainers(folderName?: string): Promise<string[]> {
|
||||
const folderPath = folderName ? [this.options.folderPath, folderName].join("/") : this.options.folderPath;
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:listContainers`, [folderPath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create local directory
|
||||
* @param folderName - Name of directory to create
|
||||
*/
|
||||
public createContainer(folderName: string): Promise<void> {
|
||||
const folderPath = [this.options.folderPath, folderName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:createContainer`, [folderPath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete directory
|
||||
* @param folderName - Name of directory to delete
|
||||
*/
|
||||
public deleteContainer(folderName: string): Promise<void> {
|
||||
const folderPath = [this.options.folderPath, folderName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:deleteContainer`, [folderPath]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve assets from directory
|
||||
* @param folderName - Directory containing assets
|
||||
*/
|
||||
public getAssets(folderName?: string): Promise<IAsset[]> {
|
||||
const folderPath = [this.options.folderPath, folderName].join("/");
|
||||
return IpcRendererProxy.send(`${PROXY_NAME}:getAssets`, [folderPath]);
|
||||
}
|
||||
}
|
|
@ -129,7 +129,7 @@ export default class Canvas extends React.Component<ICanvasProps, ICanvasState>
|
|||
|
||||
private selectedRegionIds: string[] = [];
|
||||
|
||||
private regionOrders: Array<Record<string, number>> = [];
|
||||
private regionOrders: Record<string, number>[] = [];
|
||||
|
||||
private regionOrderById: string[][] = [];
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче