restructure presentation layer
- Move most GUI related code to /presentation - Move components to /components (separate from bootstrap and style) - Move shared components helpers to /components/shared - Rename Bootstrapping to bootstrapping to enforce same naming convention in /presentation
This commit is contained in:
55
src/presentation/components/Code/CodeButtons/Code.vue
Normal file
55
src/presentation/components/Code/CodeButtons/Code.vue
Normal 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>
|
||||
71
src/presentation/components/Code/CodeButtons/IconButton.vue
Normal file
71
src/presentation/components/Code/CodeButtons/IconButton.vue
Normal 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>
|
||||
@@ -0,0 +1,116 @@
|
||||
<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 Code from './Code.vue';
|
||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||
import { ApplicationFactory } from '@/application/ApplicationFactory';
|
||||
|
||||
@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.getAppAsync();
|
||||
this.appName = app.info.name;
|
||||
this.macOsDownloadUrl = app.info.getDownloadUrl(OperatingSystem.macOS);
|
||||
}
|
||||
}
|
||||
</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>
|
||||
170
src/presentation/components/Code/CodeButtons/TheCodeButtons.vue
Normal file
170
src/presentation/components/Code/CodeButtons/TheCodeButtons.vue
Normal file
@@ -0,0 +1,170 @@
|
||||
<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"
|
||||
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/components/Shared/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 { 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: {
|
||||
IconButton,
|
||||
MacOsInstructions,
|
||||
},
|
||||
})
|
||||
export default class TheCodeButtons extends StatefulVue {
|
||||
public readonly macOsModalName = 'macos-instructions';
|
||||
|
||||
public readonly isDesktopVersion = Environment.CurrentEnvironment.isDesktop;
|
||||
public canRun = false;
|
||||
public hasCode = false;
|
||||
public isMacOsCollection = false;
|
||||
public fileName = '';
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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;
|
||||
this.events.unsubscribeAll();
|
||||
this.events.register(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;
|
||||
}
|
||||
|
||||
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">
|
||||
@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>
|
||||
Reference in New Issue
Block a user