Improve manual execution instructions
- Simplify alternatives to run the script.
- Change style of code parts to be easier to the eye:
- Use the same font size as other texts in body.
- Add vertical padding.
- Align the contents (the code, copy button and dollar sign) in the
middle.
- Align information icon to center of context next to it.
- Fix minor typos and punctations.
- Refactor instruction list to be more generic to able to be used by
other operating systems.
- Make dialogs scrollable so instruction list on smaller screens can be
read until the end.
This commit is contained in:
@@ -28,13 +28,14 @@ export default class Code extends Vue {
|
|||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
.code-wrapper {
|
.code-wrapper {
|
||||||
|
display:flex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-family: $font-normal;
|
font-family: $font-normal;
|
||||||
background-color: $color-primary-darker;
|
background-color: $color-primary-darker;
|
||||||
color: $color-on-primary;
|
color: $color-on-primary;
|
||||||
padding-left: 0.3rem;
|
align-items: center;
|
||||||
padding-right: 0.3rem;
|
padding: 0.2rem;
|
||||||
.dollar {
|
.dollar {
|
||||||
margin-right: 0.5rem;
|
margin-right: 0.5rem;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
@@ -48,7 +49,7 @@ export default class Code extends Vue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
code {
|
code {
|
||||||
font-size: 1.2rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { IInstructionListData, IInstructionListStep } from '../InstructionListData';
|
||||||
|
|
||||||
|
export interface IInstructionsBuilderData {
|
||||||
|
readonly fileName: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type InstructionStepBuilderType = (data: IInstructionsBuilderData) => IInstructionListStep;
|
||||||
|
|
||||||
|
export class InstructionsBuilder {
|
||||||
|
private readonly stepBuilders = new Array<InstructionStepBuilderType>();
|
||||||
|
|
||||||
|
constructor(private readonly os: OperatingSystem) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public withStep(stepBuilder: InstructionStepBuilderType) {
|
||||||
|
if (!stepBuilder) { throw new Error('missing stepBuilder'); }
|
||||||
|
this.stepBuilders.push(stepBuilder);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public build(data: IInstructionsBuilderData): IInstructionListData {
|
||||||
|
if (!data) { throw new Error('missing data'); }
|
||||||
|
return {
|
||||||
|
operatingSystem: this.os,
|
||||||
|
steps: this.stepBuilders.map((stepBuilder) => stepBuilder(data)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { InstructionsBuilder } from './InstructionsBuilder';
|
||||||
|
|
||||||
|
export class MacOsInstructionsBuilder extends InstructionsBuilder {
|
||||||
|
constructor() {
|
||||||
|
super(OperatingSystem.macOS);
|
||||||
|
super
|
||||||
|
.withStep(() => ({
|
||||||
|
action: {
|
||||||
|
instruction: 'Download the file.',
|
||||||
|
details: 'You should have already been prompted to save the script file.'
|
||||||
|
+ '<br/>If this was not the case or you did not save the script when prompted,'
|
||||||
|
+ '<br/>please try to download your script file again.'
|
||||||
|
,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.withStep(() => ({
|
||||||
|
action: {
|
||||||
|
instruction: 'Open terminal.',
|
||||||
|
details: 'Type Terminal into Spotlight or open it from the Applications -> Utilities folder.',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.withStep(() => ({
|
||||||
|
action: {
|
||||||
|
instruction: 'Navigate to the folder where you downloaded the file e.g.:',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
instruction: 'cd ~/Downloads',
|
||||||
|
details: 'Press on <code>enter/return</code> key after running the command.'
|
||||||
|
+ '<br/>If the file is not downloaded on Downloads folder,'
|
||||||
|
+ '<br/>change <code>Downloads</code> to path where the file is downloaded.'
|
||||||
|
+ '<br/>'
|
||||||
|
+ '<br/>This command means:'
|
||||||
|
+ '<ul>'
|
||||||
|
+ '<li><code>cd</code> will change the current folder.</li>'
|
||||||
|
+ '<li><code>~</code> is the user home directory.</li>'
|
||||||
|
+ '</ul>',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.withStep((data) => ({
|
||||||
|
action: {
|
||||||
|
instruction: 'Give the file execute permissions:',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
instruction: `chmod +x ${data.fileName}`,
|
||||||
|
details: 'Press on <code>enter/return</code> key after running the command.<br/>'
|
||||||
|
+ 'It will make the file executable.'
|
||||||
|
,
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.withStep((data) => ({
|
||||||
|
action: {
|
||||||
|
instruction: 'Execute the file:',
|
||||||
|
},
|
||||||
|
code: {
|
||||||
|
instruction: `./${data.fileName}`,
|
||||||
|
details: 'Alternatively you can locate the file in <strong>Finder</strong> and double click on it.',
|
||||||
|
},
|
||||||
|
}))
|
||||||
|
.withStep(() => ({
|
||||||
|
action: {
|
||||||
|
instruction: 'If asked, enter your administrator password.',
|
||||||
|
details: 'As you type, your password will be hidden but the keys are still registered, so keep typing.'
|
||||||
|
+ '<br/>Press on <code>enter/return</code> key after typing your password.'
|
||||||
|
+ '<br/>Administrator privileges are required to configure OS.'
|
||||||
|
,
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
<template>
|
||||||
|
<div class="instructions">
|
||||||
|
<p>
|
||||||
|
You have two alternatives to apply your selection.
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
<strong>1. The easy alternative</strong>. Run your script without any manual steps by
|
||||||
|
<a :href="this.macOsDownloadUrl">downloading desktop version</a> of {{ this.appName }} on the
|
||||||
|
{{ this.osName }} system you wish to configure, and then click on the Run button. This is
|
||||||
|
recommended for most users.
|
||||||
|
</p>
|
||||||
|
<hr />
|
||||||
|
<p>
|
||||||
|
<strong>2. The hard (manual) alternative</strong>. This requires you to do additional manual
|
||||||
|
steps. If you are unsure how to follow the instructions, hover on information
|
||||||
|
(<font-awesome-icon :icon="['fas', 'info-circle']" />)
|
||||||
|
icons near the steps, or follow the easy alternative described above.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<ol>
|
||||||
|
<li
|
||||||
|
v-for='(step, index) in this.data.steps'
|
||||||
|
v-bind:key="index"
|
||||||
|
class="step"
|
||||||
|
>
|
||||||
|
<div class="step__action">
|
||||||
|
<span>{{ step.action.instruction }}</span>
|
||||||
|
<font-awesome-icon
|
||||||
|
v-if="step.action.details"
|
||||||
|
class="explanation"
|
||||||
|
:icon="['fas', 'info-circle']"
|
||||||
|
v-tooltip.top-center="step.action.details"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div v-if="step.code" class="step__code">
|
||||||
|
<Code>{{ step.code.instruction }}</Code>
|
||||||
|
<font-awesome-icon
|
||||||
|
v-if="step.code.details"
|
||||||
|
class="explanation"
|
||||||
|
:icon="['fas', 'info-circle']"
|
||||||
|
v-tooltip.top-center="step.code.details"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { Component, Prop, Vue } from 'vue-property-decorator';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||||
|
import Code from './Code.vue';
|
||||||
|
import { IInstructionListData } from './InstructionListData';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
components: {
|
||||||
|
Code,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
export default class InstructionList extends Vue {
|
||||||
|
public appName = '';
|
||||||
|
|
||||||
|
public macOsDownloadUrl = '';
|
||||||
|
|
||||||
|
public osName = '';
|
||||||
|
|
||||||
|
@Prop() public data: IInstructionListData;
|
||||||
|
|
||||||
|
public async created() {
|
||||||
|
if (!this.data) {
|
||||||
|
throw new Error('missing data');
|
||||||
|
}
|
||||||
|
const app = await ApplicationFactory.Current.getApp();
|
||||||
|
this.appName = app.info.name;
|
||||||
|
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
||||||
|
this.osName = renderOsName(this.data.operatingSystem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderOsName(os: OperatingSystem): string {
|
||||||
|
switch (os) {
|
||||||
|
case OperatingSystem.Windows: return 'Windows';
|
||||||
|
case OperatingSystem.macOS: return 'macOS';
|
||||||
|
case OperatingSystem.Linux: return 'Linux';
|
||||||
|
default: throw new RangeError(`Cannot render os name: ${OperatingSystem[os]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
|
.step {
|
||||||
|
margin: 10px 0;
|
||||||
|
&__action {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
&__code {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.explanation {
|
||||||
|
margin-left: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
|
||||||
|
export interface IInstructionListData {
|
||||||
|
readonly operatingSystem: OperatingSystem;
|
||||||
|
readonly steps: readonly IInstructionListStep[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInstructionListStep {
|
||||||
|
readonly action: IInstructionInfo;
|
||||||
|
readonly code?: IInstructionInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IInstructionInfo {
|
||||||
|
readonly instruction: string;
|
||||||
|
readonly details?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { InstructionsBuilder } from './Data/InstructionsBuilder';
|
||||||
|
import { MacOsInstructionsBuilder } from './Data/MacOsInstructionsBuilder';
|
||||||
|
import { IInstructionListData } from './InstructionListData';
|
||||||
|
|
||||||
|
const builders = new Map<OperatingSystem, InstructionsBuilder>([
|
||||||
|
[OperatingSystem.macOS, new MacOsInstructionsBuilder()],
|
||||||
|
]);
|
||||||
|
|
||||||
|
export function hasInstructions(os: OperatingSystem) {
|
||||||
|
return builders.has(os);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getInstructions(
|
||||||
|
os: OperatingSystem,
|
||||||
|
fileName: string,
|
||||||
|
): IInstructionListData {
|
||||||
|
return builders
|
||||||
|
.get(os)
|
||||||
|
.build({ fileName });
|
||||||
|
}
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="instructions">
|
|
||||||
<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>
|
|
||||||
<ol>
|
|
||||||
<li>
|
|
||||||
<span>Download the file</span>
|
|
||||||
<font-awesome-icon
|
|
||||||
class="explanation"
|
|
||||||
:icon="['fas', 'info-circle']"
|
|
||||||
v-tooltip.top-center="
|
|
||||||
'You should be prompted to save the script file now'
|
|
||||||
+ ', otherwise try to download it again'"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Open terminal</span>
|
|
||||||
<font-awesome-icon
|
|
||||||
class="explanation"
|
|
||||||
:icon="['fas', 'info-circle']"
|
|
||||||
v-tooltip.top-center="
|
|
||||||
'Type Terminal into Spotlight or open from the Applications -> Utilities folder'"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Navigate to the folder where you downloaded the file e.g.:</span>
|
|
||||||
<div>
|
|
||||||
<Code>cd ~/Downloads</Code>
|
|
||||||
<font-awesome-icon
|
|
||||||
class="explanation"
|
|
||||||
:icon="['fas', 'info-circle']"
|
|
||||||
v-tooltip.top-center="
|
|
||||||
'Press on Enter/Return key after running the command.<br/>'
|
|
||||||
+ 'If the file is not downloaded on Downloads folder, change'
|
|
||||||
+ '`Downloads` to path where the file is downloaded.<br/>'
|
|
||||||
+ '• `cd` will change the current folder.<br/>'
|
|
||||||
+ '• `~` is the user home directory.'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Give the file execute permissions:</span>
|
|
||||||
<div>
|
|
||||||
<Code>chmod +x {{ this.fileName }}</Code>
|
|
||||||
<font-awesome-icon
|
|
||||||
class="explanation"
|
|
||||||
:icon="['fas', 'info-circle']"
|
|
||||||
v-tooltip.top-center="
|
|
||||||
'Press on Enter/Return key after running the command.<br/>' +
|
|
||||||
'It will make the file executable.'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>Execute the file:</span>
|
|
||||||
<div>
|
|
||||||
<Code>./{{ this.fileName }}</Code>
|
|
||||||
<font-awesome-icon
|
|
||||||
class="explanation"
|
|
||||||
:icon="['fas', 'info-circle']"
|
|
||||||
v-tooltip.top-center="'Alternatively you can double click on the file'"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<span>If asked, enter your administrator password</span>
|
|
||||||
<font-awesome-icon
|
|
||||||
class="explanation"
|
|
||||||
:icon="['fas', 'info-circle']"
|
|
||||||
v-tooltip.top-center="
|
|
||||||
'Press on Enter/Return key after typing your password<br/>' +
|
|
||||||
'Your password will not be shown by default.<br/>' +
|
|
||||||
'Administor privileges are required to configure OS.'"
|
|
||||||
/>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Or download the <a :href="this.macOsDownloadUrl">offline version</a> to run your scripts
|
|
||||||
directly to skip these steps.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { Component, Prop, Vue } from 'vue-property-decorator';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
|
||||||
import Code from './Code.vue';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
components: {
|
|
||||||
Code,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
export default class MacOsInstructions extends Vue {
|
|
||||||
@Prop() public fileName: string;
|
|
||||||
|
|
||||||
public appName = '';
|
|
||||||
|
|
||||||
public macOsDownloadUrl = '';
|
|
||||||
|
|
||||||
public async created() {
|
|
||||||
const app = await ApplicationFactory.Current.getApp();
|
|
||||||
this.appName = app.info.name;
|
|
||||||
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
|
||||||
|
|
||||||
li {
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
.explanation {
|
|
||||||
margin-left: 0.5em;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -17,8 +17,8 @@
|
|||||||
v-on:click="copyCode"
|
v-on:click="copyCode"
|
||||||
icon-prefix="fas" icon-name="copy">
|
icon-prefix="fas" icon-name="copy">
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Dialog v-if="this.isMacOsCollection" ref="instructionsDialog">
|
<Dialog v-if="this.hasInstructions" ref="instructionsDialog">
|
||||||
<MacOsInstructions :fileName="this.fileName" />
|
<InstructionList :data="this.instructions" />
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -35,15 +35,18 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
|
|||||||
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
|
||||||
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { ICategoryCollection } from '@/domain/ICategoryCollection';
|
||||||
import { CodeRunner } from '@/infrastructure/CodeRunner';
|
import { CodeRunner } from '@/infrastructure/CodeRunner';
|
||||||
import { IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext';
|
import { IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext';
|
||||||
import MacOsInstructions from './MacOsInstructions.vue';
|
import InstructionList from './Instructions/InstructionList.vue';
|
||||||
import IconButton from './IconButton.vue';
|
import IconButton from './IconButton.vue';
|
||||||
|
import { IInstructionListData } from './Instructions/InstructionListData';
|
||||||
|
import { getInstructions, hasInstructions } from './Instructions/InstructionListDataFactory';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
components: {
|
components: {
|
||||||
IconButton,
|
IconButton,
|
||||||
MacOsInstructions,
|
InstructionList,
|
||||||
Dialog,
|
Dialog,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
@@ -54,7 +57,9 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
|
|
||||||
public hasCode = false;
|
public hasCode = false;
|
||||||
|
|
||||||
public isMacOsCollection = false;
|
public instructions: IInstructionListData | undefined;
|
||||||
|
|
||||||
|
public hasInstructions = false;
|
||||||
|
|
||||||
public fileName = '';
|
public fileName = '';
|
||||||
|
|
||||||
@@ -66,7 +71,7 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
public async saveCode() {
|
public async saveCode() {
|
||||||
const context = await this.getCurrentContext();
|
const context = await this.getCurrentContext();
|
||||||
saveCode(this.fileName, context.state);
|
saveCode(this.fileName, context.state);
|
||||||
if (this.isMacOsCollection) {
|
if (this.hasInstructions) {
|
||||||
(this.$refs.instructionsDialog as Dialog).show();
|
(this.$refs.instructionsDialog as Dialog).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -77,11 +82,9 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
|
protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void {
|
||||||
const isNewOs = (test: OperatingSystem) => newState.collection.os === test;
|
this.updateRunState(newState.os);
|
||||||
this.canRun = this.isDesktopVersion && isNewOs(Environment.CurrentEnvironment.os);
|
this.updateDownloadState(newState.collection);
|
||||||
this.isMacOsCollection = isNewOs(OperatingSystem.macOS);
|
this.updateCodeState(newState.code);
|
||||||
this.fileName = buildFileName(newState.collection.scripting);
|
|
||||||
this.react(newState.code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getCurrentCode(): Promise<IApplicationCode> {
|
private async getCurrentCode(): Promise<IApplicationCode> {
|
||||||
@@ -90,7 +93,20 @@ export default class TheCodeButtons extends StatefulVue {
|
|||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async react(code: IApplicationCode) {
|
private updateRunState(selectedOs: OperatingSystem) {
|
||||||
|
const isRunningOnSelectedOs = selectedOs === Environment.CurrentEnvironment.os;
|
||||||
|
this.canRun = this.isDesktopVersion && isRunningOnSelectedOs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateDownloadState(collection: ICategoryCollection) {
|
||||||
|
this.fileName = buildFileName(collection.scripting);
|
||||||
|
this.hasInstructions = hasInstructions(collection.os);
|
||||||
|
if (this.hasInstructions) {
|
||||||
|
this.instructions = getInstructions(collection.os, this.fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCodeState(code: IApplicationCode) {
|
||||||
this.hasCode = code.current && code.current.length > 0;
|
this.hasCode = code.current && code.current.length > 0;
|
||||||
this.events.unsubscribeAll();
|
this.events.unsubscribeAll();
|
||||||
this.events.register(code.changed.on((newCode) => {
|
this.events.register(code.changed.on((newCode) => {
|
||||||
@@ -131,7 +147,6 @@ async function executeCode(context: IReadOnlyApplicationContext) {
|
|||||||
/* fileExtension: */ context.state.collection.scripting.fileExtension,
|
/* fileExtension: */ context.state.collection.scripting.fileExtension,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<modal
|
<modal
|
||||||
:name="name"
|
:name="name"
|
||||||
:scrollable="true"
|
|
||||||
:adaptive="true"
|
:adaptive="true"
|
||||||
height="auto">
|
height="auto">
|
||||||
<div class="dialog">
|
<div class="dialog">
|
||||||
@@ -33,12 +32,18 @@ export default class Dialog extends Vue {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
|
@mixin scrollable() {
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
.dialog {
|
.dialog {
|
||||||
color: $color-surface;
|
color: $color-surface;
|
||||||
font-family: $font-normal;
|
font-family: $font-normal;
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
|
@include scrollable;
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
color: $color-on-surface;
|
color: $color-on-surface;
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { IInstructionsBuilderData, InstructionsBuilder, InstructionStepBuilderType } from '@/presentation/components/Code/CodeButtons/Instructions/Data/InstructionsBuilder';
|
||||||
|
import { itEachAbsentObjectValue } from '@tests/unit/shared/TestCases/AbsentTests';
|
||||||
|
import { IInstructionInfo, IInstructionListStep } from '@/presentation/components/Code/CodeButtons/Instructions/InstructionListData';
|
||||||
|
|
||||||
|
describe('InstructionsBuilder', () => {
|
||||||
|
describe('withStep', () => {
|
||||||
|
describe('throws when step is missing', () => {
|
||||||
|
itEachAbsentObjectValue((absentValue) => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'missing stepBuilder';
|
||||||
|
const data = absentValue;
|
||||||
|
const sut = new InstructionsBuilder(OperatingSystem.Linux);
|
||||||
|
// act
|
||||||
|
const act = () => sut.withStep(data);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('build', () => {
|
||||||
|
it('builds with given data', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedData = createMockData();
|
||||||
|
const actualData = Array<IInstructionsBuilderData>();
|
||||||
|
const builder = new InstructionsBuilder(OperatingSystem.Android);
|
||||||
|
const steps: readonly InstructionStepBuilderType[] = [createMockStep(), createMockStep()]
|
||||||
|
.map((step) => (data) => {
|
||||||
|
actualData.push(data);
|
||||||
|
return step;
|
||||||
|
});
|
||||||
|
for (const step of steps) {
|
||||||
|
builder.withStep(step);
|
||||||
|
}
|
||||||
|
// act
|
||||||
|
builder.build(expectedData);
|
||||||
|
// assert
|
||||||
|
expect(actualData.every((data) => data === expectedData));
|
||||||
|
});
|
||||||
|
it('builds with every step', () => {
|
||||||
|
// arrange
|
||||||
|
const expectedSteps = [
|
||||||
|
createMockStep('first'),
|
||||||
|
createMockStep('second'),
|
||||||
|
createMockStep('third'),
|
||||||
|
];
|
||||||
|
const builder = new InstructionsBuilder(OperatingSystem.Android);
|
||||||
|
const steps: readonly InstructionStepBuilderType[] = expectedSteps.map((step) => () => step);
|
||||||
|
for (const step of steps) {
|
||||||
|
builder.withStep(step);
|
||||||
|
}
|
||||||
|
// act
|
||||||
|
const data = builder.build(createMockData());
|
||||||
|
// assert
|
||||||
|
const actualSteps = data.steps;
|
||||||
|
expect(actualSteps).to.have.members(expectedSteps);
|
||||||
|
});
|
||||||
|
it('builds with expected OS', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = OperatingSystem.Linux;
|
||||||
|
const sut = new InstructionsBuilder(expected);
|
||||||
|
// act
|
||||||
|
const actual = sut.build(createMockData()).operatingSystem;
|
||||||
|
// assert
|
||||||
|
expect(true);
|
||||||
|
expect(actual).to.equal(expected);
|
||||||
|
});
|
||||||
|
describe('throws when data is missing', () => {
|
||||||
|
itEachAbsentObjectValue((absentValue) => {
|
||||||
|
// arrange
|
||||||
|
const expectedError = 'missing data';
|
||||||
|
const data = absentValue;
|
||||||
|
const sut = new InstructionsBuilder(OperatingSystem.Linux);
|
||||||
|
// act
|
||||||
|
const act = () => sut.build(data);
|
||||||
|
// assert
|
||||||
|
expect(act).to.throw(expectedError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockData(): IInstructionsBuilderData {
|
||||||
|
return {
|
||||||
|
fileName: 'instructions-file',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockStep(identifier = 'mock step'): IInstructionListStep {
|
||||||
|
return {
|
||||||
|
action: createMockInfo(`${identifier} | action`),
|
||||||
|
code: createMockInfo(`${identifier} | code`),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function createMockInfo(identifier = 'mock info'): IInstructionInfo {
|
||||||
|
return {
|
||||||
|
instruction: `${identifier} | mock instruction`,
|
||||||
|
details: `${identifier} | mock details`,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { MacOsInstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Instructions/Data/MacOsInstructionsBuilder';
|
||||||
|
import { runOsSpecificInstructionBuilderTests } from './OsSpecificInstructionBuilderTestRunner';
|
||||||
|
|
||||||
|
describe('MacOsInstructionsBuilder', () => {
|
||||||
|
runOsSpecificInstructionBuilderTests({
|
||||||
|
factory: () => new MacOsInstructionsBuilder(),
|
||||||
|
os: OperatingSystem.macOS,
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { expect } from 'chai';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Instructions/Data/InstructionsBuilder';
|
||||||
|
|
||||||
|
interface ITestData {
|
||||||
|
readonly factory: () => InstructionsBuilder;
|
||||||
|
readonly os: OperatingSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function runOsSpecificInstructionBuilderTests(data: ITestData) {
|
||||||
|
it('builds multiple steps', () => {
|
||||||
|
// arrange
|
||||||
|
const sut = data.factory();
|
||||||
|
// act
|
||||||
|
const result = sut.build({ fileName: 'test.file' });
|
||||||
|
// assert
|
||||||
|
expect(result.steps).to.have.length.greaterThan(0);
|
||||||
|
});
|
||||||
|
it(`operatingSystem return ${OperatingSystem[data.os]}`, () => {
|
||||||
|
// arrange
|
||||||
|
const expected = data.os;
|
||||||
|
const sut = data.factory();
|
||||||
|
// act
|
||||||
|
const result = sut.build({ fileName: 'test.file' });
|
||||||
|
// assert
|
||||||
|
expect(result.operatingSystem).to.equal(expected);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import 'mocha';
|
||||||
|
import { expect } from 'chai';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { getInstructions, hasInstructions } from '@/presentation/components/Code/CodeButtons/Instructions/InstructionListDataFactory';
|
||||||
|
import { getEnumValues } from '@/application/Common/Enum';
|
||||||
|
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Instructions/Data/InstructionsBuilder';
|
||||||
|
|
||||||
|
describe('InstructionListDataFactory', () => {
|
||||||
|
const supportedOsList = [OperatingSystem.macOS];
|
||||||
|
describe('hasInstructions', () => {
|
||||||
|
it('return true if OS is supported', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = true;
|
||||||
|
// act
|
||||||
|
const actualResults = supportedOsList.map((os) => hasInstructions(os));
|
||||||
|
// assert
|
||||||
|
expect(actualResults.every((result) => result === expected));
|
||||||
|
});
|
||||||
|
it('return false if OS is not supported', () => {
|
||||||
|
// arrange
|
||||||
|
const expected = false;
|
||||||
|
const unsupportedOses = getEnumValues(OperatingSystem)
|
||||||
|
.filter((value) => !supportedOsList.includes(value));
|
||||||
|
// act
|
||||||
|
const actualResults = unsupportedOses.map((os) => hasInstructions(os));
|
||||||
|
// assert
|
||||||
|
expect(actualResults.every((result) => result === expected));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
describe('getInstructions', () => {
|
||||||
|
it('returns expected if os is supported', () => {
|
||||||
|
// arrange
|
||||||
|
const fileName = 'test.file';
|
||||||
|
// act
|
||||||
|
const actualResults = supportedOsList.map((os) => getInstructions(os, fileName));
|
||||||
|
// assert
|
||||||
|
expect(actualResults.every((result) => result instanceof InstructionsBuilder));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user