add option to run script directly in desktop app

This commit is contained in:
undergroundwires
2021-02-18 09:34:38 +01:00
parent 7661575573
commit 9a6b903b92
7 changed files with 353 additions and 11 deletions

View File

@@ -1,4 +1,3 @@
export class Clipboard {
public static copyText(text: string): void {
const el = document.createElement('textarea');

View File

@@ -0,0 +1,69 @@
import { Environment } from '@/application/Environment/Environment';
import os from 'os';
import path from 'path';
import fs from 'fs';
import child_process from 'child_process';
import { OperatingSystem } from '@/domain/OperatingSystem';
export async function runCodeAsync(
code: string, folderName: string, fileExtension: string,
node = getNodeJs(), environment = Environment.CurrentEnvironment): Promise<void> {
const dir = node.path.join(node.os.tmpdir(), folderName);
await node.fs.promises.mkdir(dir, {recursive: true});
const filePath = node.path.join(dir, `run.${fileExtension}`);
await node.fs.promises.writeFile(filePath, code);
await node.fs.promises.chmod(filePath, '755');
const command = getExecuteCommand(filePath, environment);
node.child_process.exec(command);
}
function getExecuteCommand(scriptPath: string, environment: Environment): string {
switch (environment.os) {
case OperatingSystem.macOS:
return `open -a Terminal.app ${scriptPath}`;
// Another option with graphical sudo would be
// `osascript -e "do shell script \\"${scriptPath}\\" with administrator privileges"`
// However it runs in background
case OperatingSystem.Windows:
return scriptPath;
default:
throw Error('undefined os');
}
}
function getNodeJs(): INodeJs {
return { os, path, fs, child_process };
}
export interface INodeJs {
os: INodeOs;
path: INodePath;
fs: INodeFs;
child_process: INodeChildProcess;
}
export interface INodeOs {
tmpdir(): string;
}
export interface INodePath {
join(...paths: string[]): string;
}
export interface INodeChildProcess {
exec(command: string): void;
}
export interface INodeFs {
readonly promises: INodeFsPromises;
}
interface INodeFsPromisesMakeDirectoryOptions {
recursive?: boolean;
}
interface INodeFsPromises { // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/node/v13/fs.d.ts
chmod(path: string, mode: string | number): Promise<void>;
mkdir(path: string, options: INodeFsPromisesMakeDirectoryOptions): Promise<string>;
writeFile(path: string, data: string): Promise<void>;
}

View File

@@ -7,8 +7,7 @@ import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faFolderOpen, faFolder, faSmile } from '@fortawesome/free-regular-svg-icons';
/** SOLID ICONS (PREFIX: fas (default)) */
import { faTimes, faFileDownload, faCopy, faSearch, faInfoCircle, faUserSecret, faDesktop,
faTag, faGlobe, faSave, faBatteryFull, faBatteryHalf } from '@fortawesome/free-solid-svg-icons';
faTag, faGlobe, faSave, faBatteryFull, faBatteryHalf, faPlay } from '@fortawesome/free-solid-svg-icons';
export class IconBootstrapper implements IVueBootstrapper {
public bootstrap(vue: VueConstructor): void {
@@ -24,6 +23,7 @@ export class IconBootstrapper implements IVueBootstrapper {
faTimes,
faFileDownload, faSave,
faCopy,
faPlay,
faSearch,
faBatteryFull, faBatteryHalf,
faInfoCircle);

View File

@@ -1,8 +1,8 @@
<template>
<div class="instructions">
<!-- <p>
<p>
Since you're using online version of {{ this.appName }}, you will need to do additional steps after downloading the file to execute your script on macOS:
</p> -->
</p>
<p>
<ol>
<li>
@@ -73,9 +73,9 @@
</li>
</ol>
</p>
<!-- <p>
<p>
Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts directly to skip these steps.
</p> -->
</p>
</div>
</template>

View File

@@ -1,5 +1,11 @@
<template>
<div class="container" v-if="hasCode">
<IconButton
v-if="this.canRun"
text="Run"
v-on:click="executeCodeAsync"
icon-prefix="fas" icon-name="play">
</IconButton>
<IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCodeAsync"
@@ -38,6 +44,8 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { runCodeAsync } from '@/infrastructure/CodeRunner';
import { IApplicationContext } from '@/application/Context/IApplicationContext';
@Component({
components: {
@@ -48,8 +56,9 @@ import { OperatingSystem } from '@/domain/OperatingSystem';
export default class TheCodeButtons extends StatefulVue {
public readonly macOsModalName = 'macos-instructions';
public readonly isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public canRun = false;
public hasCode = false;
public isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public isMacOsCollection = false;
public fileName = '';
@@ -64,8 +73,13 @@ export default class TheCodeButtons extends StatefulVue {
this.$modal.show(this.macOsModalName);
}
}
public async executeCodeAsync() {
const context = await this.getCurrentContextAsync();
await executeCodeAsync(context);
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.canRun = this.isDesktopVersion && newState.collection.os === Environment.CurrentEnvironment.os;
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
this.fileName = buildFileName(newState.collection.scripting);
this.react(newState.code);
@@ -108,6 +122,15 @@ function buildFileName(scripting: IScriptingDefinition) {
}
return fileName;
}
async function executeCodeAsync(context: IApplicationContext) {
await runCodeAsync(
/*code*/ context.state.code.current,
/*appName*/ context.app.info.name,
/*fileExtension*/ context.state.collection.scripting.fileExtension,
);
}
</script>
<style scoped lang="scss">