add initial macOS support #40

This commit is contained in:
undergroundwires
2021-01-13 16:31:20 +01:00
parent 2428de23ee
commit 8a8b7319d5
99 changed files with 2663 additions and 1135 deletions

View File

@@ -0,0 +1,55 @@
<template>
<span class="code-wrapper">
<span class="dollar">$</span>
<code><slot></slot></code>
<font-awesome-icon
class="copy-button"
:icon="['fas', 'copy']"
@click="copyCode"
v-tooltip.top-center="'Copy'"
/>
</span>
</template>
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';
import { Clipboard } from '@/infrastructure/Clipboard';
@Component
export default class Code extends Vue {
public copyCode(): void {
const code = this.$slots.default[0].text;
Clipboard.copyText(code);
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
.code-wrapper {
white-space: nowrap;
justify-content: space-between;
font-family: $normal-font;
background-color: $slate;
color: $light-gray;
padding-left: 0.3rem;
padding-right: 0.3rem;
.dollar {
margin-right: 0.5rem;
font-size: 0.8rem;
user-select: none;
}
.copy-button {
margin-left: 1rem;
cursor: pointer;
&:hover {
opacity: 0.8;
}
}
code {
font-size: 1.2rem;
}
}
</style>

View File

@@ -0,0 +1,71 @@
<template>
<button class="button" @click="onClicked">
<font-awesome-icon
class="button__icon"
:icon="[iconPrefix, iconName]" size="2x" />
<div class="button__text">{{text}}</div>
</button>
</template>
<script lang="ts">
import { Component, Prop, Emit, Vue } from 'vue-property-decorator';
@Component
export default class IconButton extends Vue {
@Prop() public text!: number;
@Prop() public iconPrefix!: string;
@Prop() public iconName!: string;
@Emit('click')
public onClicked() {
return;
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
.button {
display: flex;
flex-direction: column;
align-items: center;
background-color: $accent;
border: none;
color: $white;
padding:20px;
transition-duration: 0.4s;
overflow: hidden;
box-shadow: 0 3px 9px $dark-slate;
border-radius: 4px;
cursor: pointer;
// border: 0.1em solid $slate;
// border-radius: 80px;
// padding: 0.5em;
width: 10%;
min-width: 90px;
&:hover {
background: $white;
box-shadow: 0px 2px 10px 5px $accent;
color: $black;
}
&:hover>&__text {
display: block;
}
&:hover>&__icon {
display: none;
}
&__text {
display: none;
font-family: $artistic-font;
font-size: 1.5em;
color: $gray;
font-weight: 500;
line-height: 1.1;
}
}
</style>

View File

@@ -0,0 +1,119 @@
<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 } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import Code from './Code.vue';
import { IApplication } from '@/domain/IApplication';
import { OperatingSystem } from '@/domain/OperatingSystem';
@Component({
components: {
Code,
},
})
export default class MacOsInstructions extends StatefulVue {
@Prop() public fileName: string;
public appName = '';
public macOsDownloadUrl = '';
protected initialize(app: IApplication): void {
this.appName = app.info.name;
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
}
protected handleCollectionState(): void {
return;
}
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
li {
margin: 10px 0;
}
.explanation {
margin-left: 0.5em;
}
</style>

View File

@@ -0,0 +1,161 @@
<template>
<div class="container" v-if="hasCode">
<IconButton
:text="this.isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCodeAsync"
icon-prefix="fas"
:icon-name="this.isDesktopVersion ? 'save' : 'file-download'">
</IconButton>
<IconButton
text="Copy"
v-on:click="copyCodeAsync"
icon-prefix="fas" icon-name="copy">
</IconButton>
<modal :name="macOsModalName" height="auto" :scrollable="true" :adaptive="true"
v-if="this.isMacOsCollection">
<div class="modal">
<div class="modal__content">
<MacOsInstructions :fileName="this.fileName" />
</div>
<div class="modal__close-button">
<font-awesome-icon :icon="['fas', 'times']" @click="$modal.hide(macOsModalName)"/>
</div>
</div>
</modal>
</div>
</template>
<script lang="ts">
import { Component } from 'vue-property-decorator';
import { StatefulVue } from '@/presentation/StatefulVue';
import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog';
import { Clipboard } from '@/infrastructure/Clipboard';
import IconButton from './IconButton.vue';
import MacOsInstructions from './MacOsInstructions.vue';
import { Environment } from '@/application/Environment/Environment';
import { ICategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IApplication } from '@/domain/IApplication';
import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode';
import { IEventSubscription } from '@/infrastructure/Events/ISubscription';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
@Component({
components: {
IconButton,
MacOsInstructions,
},
})
export default class TheCodeButtons extends StatefulVue {
public readonly macOsModalName = 'macos-instructions';
public hasCode = false;
public isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
public isMacOsCollection = false;
public fileName = '';
private codeListener: IEventSubscription;
public async copyCodeAsync() {
const code = await this.getCurrentCodeAsync();
Clipboard.copyText(code.current);
}
public async saveCodeAsync() {
const context = await this.getCurrentContextAsync();
saveCode(this.fileName, context.state);
if (this.isMacOsCollection) {
this.$modal.show(this.macOsModalName);
}
}
public destroyed() {
if (this.codeListener) {
this.codeListener.unsubscribe();
}
}
protected initialize(app: IApplication): void {
return;
}
protected handleCollectionState(newState: ICategoryCollectionState): void {
this.isMacOsCollection = newState.collection.os === OperatingSystem.macOS;
this.fileName = buildFileName(newState.collection.scripting);
this.react(newState.code);
}
private async getCurrentCodeAsync(): Promise<IApplicationCode> {
const context = await this.getCurrentContextAsync();
const code = context.state.code;
return code;
}
private async react(code: IApplicationCode) {
this.hasCode = code.current && code.current.length > 0;
if (this.codeListener) {
this.codeListener.unsubscribe();
}
this.codeListener = code.changed.on((newCode) => {
this.hasCode = newCode && newCode.code.length > 0;
});
}
}
function saveCode(fileName: string, state: ICategoryCollectionState) {
const content = state.code.current;
const type = getType(state.collection.scripting.language);
SaveFileDialog.saveFile(content, fileName, type);
}
function getType(language: ScriptingLanguage) {
switch (language) {
case ScriptingLanguage.batchfile:
return FileType.BatchFile;
case ScriptingLanguage.shellscript:
return FileType.ShellScript;
default:
throw new Error('unknown file type');
}
}
function buildFileName(scripting: IScriptingDefinition) {
const fileName = 'privacy-script';
if (scripting.fileExtension) {
return `${fileName}.${scripting.fileExtension}`;
}
return fileName;
}
</script>
<style scoped lang="scss">
@import "@/presentation/styles/colors.scss";
@import "@/presentation/styles/fonts.scss";
.container {
display: flex;
flex-direction: row;
justify-content: center;
}
.container > * + * {
margin-left: 30px;
}
.modal {
font-family: $normal-font;
margin-bottom: 10px;
display: flex;
flex-direction: row;
&__content {
width: 100%;
margin: 5%;
}
&__close-button {
width: auto;
font-size: 1.5em;
margin-right:0.25em;
align-self: flex-start;
cursor: pointer;
&:hover {
opacity: 0.9;
}
}
}
</style>