diff --git a/src/presentation/components/Code/CodeButtons/Code.vue b/src/presentation/components/Code/CodeButtons/Instructions/Code.vue similarity index 93% rename from src/presentation/components/Code/CodeButtons/Code.vue rename to src/presentation/components/Code/CodeButtons/Instructions/Code.vue index a1926c00..8852562f 100644 --- a/src/presentation/components/Code/CodeButtons/Code.vue +++ b/src/presentation/components/Code/CodeButtons/Instructions/Code.vue @@ -28,13 +28,14 @@ export default class Code extends Vue { @use "@/presentation/assets/styles/main" as *; .code-wrapper { + display:flex; white-space: nowrap; justify-content: space-between; font-family: $font-normal; background-color: $color-primary-darker; color: $color-on-primary; - padding-left: 0.3rem; - padding-right: 0.3rem; + align-items: center; + padding: 0.2rem; .dollar { margin-right: 0.5rem; font-size: 0.8rem; @@ -48,7 +49,7 @@ export default class Code extends Vue { } } code { - font-size: 1.2rem; + font-size: 1rem; } } diff --git a/src/presentation/components/Code/CodeButtons/Instructions/Data/InstructionsBuilder.ts b/src/presentation/components/Code/CodeButtons/Instructions/Data/InstructionsBuilder.ts new file mode 100644 index 00000000..74e7a3ee --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Instructions/Data/InstructionsBuilder.ts @@ -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(); + + 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)), + }; + } +} diff --git a/src/presentation/components/Code/CodeButtons/Instructions/Data/MacOsInstructionsBuilder.ts b/src/presentation/components/Code/CodeButtons/Instructions/Data/MacOsInstructionsBuilder.ts new file mode 100644 index 00000000..b3a60860 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Instructions/Data/MacOsInstructionsBuilder.ts @@ -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.' + + '
If this was not the case or you did not save the script when prompted,' + + '
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 enter/return key after running the command.' + + '
If the file is not downloaded on Downloads folder,' + + '
change Downloads to path where the file is downloaded.' + + '
' + + '
This command means:' + + '', + }, + })) + .withStep((data) => ({ + action: { + instruction: 'Give the file execute permissions:', + }, + code: { + instruction: `chmod +x ${data.fileName}`, + details: 'Press on enter/return key after running the command.
' + + '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 Finder 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.' + + '
Press on enter/return key after typing your password.' + + '
Administrator privileges are required to configure OS.' + , + }, + })); + } +} diff --git a/src/presentation/components/Code/CodeButtons/Instructions/InstructionList.vue b/src/presentation/components/Code/CodeButtons/Instructions/InstructionList.vue new file mode 100644 index 00000000..357c5608 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Instructions/InstructionList.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/presentation/components/Code/CodeButtons/Instructions/InstructionListData.ts b/src/presentation/components/Code/CodeButtons/Instructions/InstructionListData.ts new file mode 100644 index 00000000..ee314548 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Instructions/InstructionListData.ts @@ -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; +} diff --git a/src/presentation/components/Code/CodeButtons/Instructions/InstructionListDataFactory.ts b/src/presentation/components/Code/CodeButtons/Instructions/InstructionListDataFactory.ts new file mode 100644 index 00000000..2c50d544 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/Instructions/InstructionListDataFactory.ts @@ -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.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 }); +} diff --git a/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue b/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue deleted file mode 100644 index f55eae3b..00000000 --- a/src/presentation/components/Code/CodeButtons/MacOsInstructions.vue +++ /dev/null @@ -1,123 +0,0 @@ - - - - - diff --git a/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue b/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue index f23ee145..d20bfec0 100644 --- a/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue +++ b/src/presentation/components/Code/CodeButtons/TheCodeButtons.vue @@ -17,8 +17,8 @@ v-on:click="copyCode" icon-prefix="fas" icon-name="copy"> - - + + @@ -35,15 +35,18 @@ import { ScriptingLanguage } from '@/domain/ScriptingLanguage'; import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode'; import { IScriptingDefinition } from '@/domain/IScriptingDefinition'; import { OperatingSystem } from '@/domain/OperatingSystem'; +import { ICategoryCollection } from '@/domain/ICategoryCollection'; import { CodeRunner } from '@/infrastructure/CodeRunner'; import { IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext'; -import MacOsInstructions from './MacOsInstructions.vue'; +import InstructionList from './Instructions/InstructionList.vue'; import IconButton from './IconButton.vue'; +import { IInstructionListData } from './Instructions/InstructionListData'; +import { getInstructions, hasInstructions } from './Instructions/InstructionListDataFactory'; @Component({ components: { IconButton, - MacOsInstructions, + InstructionList, Dialog, }, }) @@ -54,7 +57,9 @@ export default class TheCodeButtons extends StatefulVue { public hasCode = false; - public isMacOsCollection = false; + public instructions: IInstructionListData | undefined; + + public hasInstructions = false; public fileName = ''; @@ -66,7 +71,7 @@ export default class TheCodeButtons extends StatefulVue { public async saveCode() { const context = await this.getCurrentContext(); saveCode(this.fileName, context.state); - if (this.isMacOsCollection) { + if (this.hasInstructions) { (this.$refs.instructionsDialog as Dialog).show(); } } @@ -77,11 +82,9 @@ export default class TheCodeButtons extends StatefulVue { } protected handleCollectionState(newState: IReadOnlyCategoryCollectionState): void { - const isNewOs = (test: OperatingSystem) => newState.collection.os === test; - this.canRun = this.isDesktopVersion && isNewOs(Environment.CurrentEnvironment.os); - this.isMacOsCollection = isNewOs(OperatingSystem.macOS); - this.fileName = buildFileName(newState.collection.scripting); - this.react(newState.code); + this.updateRunState(newState.os); + this.updateDownloadState(newState.collection); + this.updateCodeState(newState.code); } private async getCurrentCode(): Promise { @@ -90,7 +93,20 @@ export default class TheCodeButtons extends StatefulVue { 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.events.unsubscribeAll(); this.events.register(code.changed.on((newCode) => { @@ -131,7 +147,6 @@ async function executeCode(context: IReadOnlyApplicationContext) { /* fileExtension: */ context.state.collection.scripting.fileExtension, ); } -