Add Windows save instructions UI and fix URL #296
- Add Windows instruction dialog when saving scripts for Windows. - Fix incorrect macOS download URL given for Linux instructions. - Refactor UI rendering, eleminating the use of `v-html` and JavaScript variables to hold HTML code.
This commit is contained in:
@@ -137,7 +137,7 @@ For a detailed comparison of features between the desktop and web versions of pr
|
|||||||
- **Transparent**. Have full visibility into what the tweaks do as you enable them.
|
- **Transparent**. Have full visibility into what the tweaks do as you enable them.
|
||||||
- **Reversible**. Revert if something feels wrong.
|
- **Reversible**. Revert if something feels wrong.
|
||||||
- **Accessible**. No need to run any compiled software on your computer with web version.
|
- **Accessible**. No need to run any compiled software on your computer with web version.
|
||||||
- **Secure**: Security is a top priority at privacy.sexy with [comprehensive safeguards](./SECURITY.md#application-security) in place.
|
- **Secure**: Security is a top priority at privacy.sexy with [comprehensive safeguards](./SECURITY.md#security-practices) in place.
|
||||||
- **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
|
- **Open**. What you see as code in this repository is what you get. The application itself, its infrastructure and deployments are open-source and automated thanks to [bump-everywhere](https://github.com/undergroundwires/bump-everywhere).
|
||||||
- **Tested**. A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features.
|
- **Tested**. A lot of tests. Automated and manual. Community-testing and verification. Stability improvements comes before new features.
|
||||||
- **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md).
|
- **Extensible**. Effortlessly [extend scripts](./CONTRIBUTING.md#extend-scripts) with a custom designed [templating language](./docs/templating.md).
|
||||||
|
|||||||
@@ -5,8 +5,8 @@
|
|||||||
:icon-name="isRunningAsDesktopApplication ? 'floppy-disk' : 'file-arrow-down'"
|
:icon-name="isRunningAsDesktopApplication ? 'floppy-disk' : 'file-arrow-down'"
|
||||||
@click="saveCode"
|
@click="saveCode"
|
||||||
/>
|
/>
|
||||||
<ModalDialog v-if="instructions" v-model="areInstructionsVisible">
|
<ModalDialog v-model="areInstructionsVisible">
|
||||||
<InstructionList :data="instructions" />
|
<RunInstructions :filename="filename" />
|
||||||
</ModalDialog>
|
</ModalDialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -22,14 +22,12 @@ import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
|
|||||||
import { ScriptFilename } from '@/application/CodeRunner/ScriptFilename';
|
import { ScriptFilename } from '@/application/CodeRunner/ScriptFilename';
|
||||||
import { Dialog, FileType } from '@/presentation/common/Dialog';
|
import { Dialog, FileType } from '@/presentation/common/Dialog';
|
||||||
import IconButton from '../IconButton.vue';
|
import IconButton from '../IconButton.vue';
|
||||||
import InstructionList from './Instructions/InstructionList.vue';
|
import RunInstructions from './RunInstructions/RunInstructions.vue';
|
||||||
import { IInstructionListData } from './Instructions/InstructionListData';
|
|
||||||
import { getInstructions } from './Instructions/InstructionListDataFactory';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
components: {
|
components: {
|
||||||
IconButton,
|
IconButton,
|
||||||
InstructionList,
|
RunInstructions,
|
||||||
ModalDialog,
|
ModalDialog,
|
||||||
},
|
},
|
||||||
setup() {
|
setup() {
|
||||||
@@ -39,10 +37,6 @@ export default defineComponent({
|
|||||||
|
|
||||||
const areInstructionsVisible = ref(false);
|
const areInstructionsVisible = ref(false);
|
||||||
const filename = computed<string>(() => buildFilename(currentState.value.collection.scripting));
|
const filename = computed<string>(() => buildFilename(currentState.value.collection.scripting));
|
||||||
const instructions = computed<IInstructionListData | undefined>(() => getInstructions(
|
|
||||||
currentState.value.collection.os,
|
|
||||||
filename.value,
|
|
||||||
));
|
|
||||||
|
|
||||||
async function saveCode() {
|
async function saveCode() {
|
||||||
const { success, error } = await dialog.saveFile(
|
const { success, error } = await dialog.saveFile(
|
||||||
@@ -59,8 +53,8 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
isRunningAsDesktopApplication,
|
isRunningAsDesktopApplication,
|
||||||
instructions,
|
|
||||||
areInstructionsVisible,
|
areInstructionsVisible,
|
||||||
|
filename,
|
||||||
saveCode,
|
saveCode,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
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) {
|
|
||||||
this.stepBuilders.push(stepBuilder);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public build(data: IInstructionsBuilderData): IInstructionListData {
|
|
||||||
return {
|
|
||||||
operatingSystem: this.os,
|
|
||||||
steps: this.stepBuilders.map((stepBuilder) => stepBuilder(data)),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { InstructionsBuilder } from './InstructionsBuilder';
|
|
||||||
|
|
||||||
export class LinuxInstructionsBuilder extends InstructionsBuilder {
|
|
||||||
constructor() {
|
|
||||||
super(OperatingSystem.Linux);
|
|
||||||
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:
|
|
||||||
'Opening terminal changes based on the distro you run.'
|
|
||||||
+ '<br/>You may search for "Terminal" in your application launcher to find it.'
|
|
||||||
+ '<br/>'
|
|
||||||
+ '<br/>Alternatively use terminal shortcut for your distro if it has one by default:'
|
|
||||||
+ '<ul>'
|
|
||||||
+ '<li><code>Ctrl-Alt-T</code>: Ubuntu, CentOS, Linux Mint, Elementary OS, ubermix, Kali…</li>'
|
|
||||||
+ '<li><code>Super-T</code>: Pop!_OS…</li>'
|
|
||||||
+ '<li><code>Alt-T</code>: Parrot OS…</li>'
|
|
||||||
+ '<li><code>Ctrl-Alt-Insert</code>: Bodhi Linux…</li>'
|
|
||||||
+ '</ul>'
|
|
||||||
,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.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. <br/>'
|
|
||||||
+ 'If you use desktop environment you can alternatively (instead of running the command):'
|
|
||||||
+ '<ol>'
|
|
||||||
+ '<li>Locate the file using your file manager.</li>'
|
|
||||||
+ '<li>Right click on the file, select "Properties".</li>'
|
|
||||||
+ '<li>Go to "Permissions" and check "Allow executing file as program".</li>'
|
|
||||||
+ '</ol>'
|
|
||||||
+ '<br/>These GUI steps and name of options may change depending on your file manager.'
|
|
||||||
,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.withStep((data) => ({
|
|
||||||
action: {
|
|
||||||
instruction: 'Execute the file:',
|
|
||||||
},
|
|
||||||
code: {
|
|
||||||
instruction: `./${data.fileName}`,
|
|
||||||
details:
|
|
||||||
'If you have desktop environment, instead of running this command you can alternatively:'
|
|
||||||
+ '<ol>'
|
|
||||||
+ '<li>Locate the file using your file manager.</li>'
|
|
||||||
+ '<li>Right click on the file, select "Run as program".</li>'
|
|
||||||
+ '</ol>'
|
|
||||||
,
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
.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.',
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
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.'
|
|
||||||
,
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<template>
|
|
||||||
<TooltipWrapper>
|
|
||||||
<AppIcon icon="circle-info" />
|
|
||||||
<template #tooltip>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
</TooltipWrapper>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import { defineComponent } from 'vue';
|
|
||||||
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
|
||||||
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
TooltipWrapper,
|
|
||||||
AppIcon,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
@@ -1,119 +0,0 @@
|
|||||||
<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="macOsDownloadUrl">downloading desktop version</a> of {{ appName }} on the
|
|
||||||
{{ 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, tap or hover on information
|
|
||||||
(<InfoTooltip>Engage with icons like this for extra wisdom!</InfoTooltip>)
|
|
||||||
icons near the steps, or follow the easy alternative described above.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<ol>
|
|
||||||
<li
|
|
||||||
v-for="(step, index) in data.steps"
|
|
||||||
:key="index"
|
|
||||||
class="step"
|
|
||||||
>
|
|
||||||
<div class="step__action">
|
|
||||||
<span>{{ step.action.instruction }}</span>
|
|
||||||
<div v-if="step.action.details" class="details-container">
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
|
||||||
<InfoTooltip><div v-html="step.action.details" /></InfoTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div v-if="step.code" class="step__code">
|
|
||||||
<CodeInstruction>{{ step.code.instruction }}</CodeInstruction>
|
|
||||||
<div v-if="step.code.details" class="details-container">
|
|
||||||
<!-- eslint-disable vue/no-v-html -->
|
|
||||||
<InfoTooltip><div v-html="step.code.details" /></InfoTooltip>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</li>
|
|
||||||
</ol>
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
import {
|
|
||||||
defineComponent, PropType, computed,
|
|
||||||
} from 'vue';
|
|
||||||
import { injectKey } from '@/presentation/injectionSymbols';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import CodeInstruction from './CodeInstruction.vue';
|
|
||||||
import { IInstructionListData } from './InstructionListData';
|
|
||||||
import InfoTooltip from './InfoTooltip.vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
|
||||||
components: {
|
|
||||||
CodeInstruction,
|
|
||||||
InfoTooltip,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
data: {
|
|
||||||
type: Object as PropType<IInstructionListData>,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props) {
|
|
||||||
const { info } = injectKey((keys) => keys.useApplication);
|
|
||||||
|
|
||||||
const appName = computed<string>(() => info.name);
|
|
||||||
|
|
||||||
const macOsDownloadUrl = computed<string>(
|
|
||||||
() => info.getDownloadUrl(OperatingSystem.macOS),
|
|
||||||
);
|
|
||||||
|
|
||||||
const osName = computed<string>(
|
|
||||||
() => renderOsName(props.data.operatingSystem),
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
appName,
|
|
||||||
macOsDownloadUrl,
|
|
||||||
osName,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.details-container {
|
|
||||||
margin-left: 0.5em; // Do not style icon itself to ensure correct tooltip alignment
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { InstructionsBuilder } from './Data/InstructionsBuilder';
|
|
||||||
import { MacOsInstructionsBuilder } from './Data/MacOsInstructionsBuilder';
|
|
||||||
import { IInstructionListData } from './InstructionListData';
|
|
||||||
import { LinuxInstructionsBuilder } from './Data/LinuxInstructionsBuilder';
|
|
||||||
|
|
||||||
const builders = new Map<OperatingSystem, InstructionsBuilder>([
|
|
||||||
[OperatingSystem.macOS, new MacOsInstructionsBuilder()],
|
|
||||||
[OperatingSystem.Linux, new LinuxInstructionsBuilder()],
|
|
||||||
]);
|
|
||||||
|
|
||||||
export function getInstructions(
|
|
||||||
os: OperatingSystem,
|
|
||||||
fileName: string,
|
|
||||||
): IInstructionListData | undefined {
|
|
||||||
return builders
|
|
||||||
.get(os)
|
|
||||||
?.build({ fileName });
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div class="info-container">
|
||||||
|
<TooltipWrapper>
|
||||||
|
<AppIcon icon="circle-info" />
|
||||||
|
<template #tooltip>
|
||||||
|
<slot />
|
||||||
|
</template>
|
||||||
|
</TooltipWrapper>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
|
||||||
|
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
TooltipWrapper,
|
||||||
|
AppIcon,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.info-container {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.15em; // Do not style icon itself to ensure correct tooltip alignment
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<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="downloadUrl">downloading desktop version</a> of {{ appName }} on the
|
||||||
|
{{ 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, tap or hover on information
|
||||||
|
<InfoTooltip>Engage with icons like this for extra wisdom!</InfoTooltip>
|
||||||
|
icons near the steps, or follow the easy alternative described above.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<PlatformInstructionSteps :filename="filename" />
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, computed } from 'vue';
|
||||||
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import InfoTooltip from './InfoTooltip.vue';
|
||||||
|
import PlatformInstructionSteps from './Steps/PlatformInstructionSteps.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
InfoTooltip,
|
||||||
|
PlatformInstructionSteps,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
||||||
|
|
||||||
|
const { info } = injectKey((keys) => keys.useApplication);
|
||||||
|
|
||||||
|
const operatingSystem = computed<OperatingSystem>(() => currentState.value.os);
|
||||||
|
|
||||||
|
const appName = computed<string>(() => info.name);
|
||||||
|
|
||||||
|
const downloadUrl = computed<string>(
|
||||||
|
() => info.getDownloadUrl(operatingSystem.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
const osName = computed<string>(
|
||||||
|
() => renderOsName(operatingSystem.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
appName,
|
||||||
|
downloadUrl,
|
||||||
|
osName,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -27,10 +27,10 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const { copyText } = injectKey((keys) => keys.useClipboard);
|
const { copyText } = injectKey((keys) => keys.useClipboard);
|
||||||
|
|
||||||
const codeElement = shallowRef<HTMLElement | undefined>();
|
const codeElementRef = shallowRef<HTMLElement | undefined>();
|
||||||
|
|
||||||
async function copyCode() {
|
async function copyCode() {
|
||||||
const element = codeElement.value;
|
const element = codeElementRef.value;
|
||||||
if (!element) {
|
if (!element) {
|
||||||
throw new Error('Code element could not be found.');
|
throw new Error('Code element could not be found.');
|
||||||
}
|
}
|
||||||
@@ -43,7 +43,7 @@ export default defineComponent({
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
copyCode,
|
copyCode,
|
||||||
codeElement,
|
codeElement: codeElementRef,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@@ -53,7 +53,7 @@ export default defineComponent({
|
|||||||
@use "@/presentation/assets/styles/main" as *;
|
@use "@/presentation/assets/styles/main" as *;
|
||||||
|
|
||||||
.code-wrapper {
|
.code-wrapper {
|
||||||
display:flex;
|
display: inline-flex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
font-family: $font-normal;
|
font-family: $font-normal;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<template>
|
||||||
|
<li class="step">
|
||||||
|
<slot />
|
||||||
|
</li>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.step {
|
||||||
|
margin: 10px 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<template>
|
||||||
|
<ol>
|
||||||
|
<slot />
|
||||||
|
</ol>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'InstructionSteps', // Define component name for empty component for Vue build and ESLint compatibility.
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<template>
|
||||||
|
<component
|
||||||
|
:is="component"
|
||||||
|
v-if="component"
|
||||||
|
:filename="filename"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, computed } from 'vue';
|
||||||
|
import { injectKey } from '@/presentation/injectionSymbols';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import MacOsInstructions from './Platforms/MacOsInstructions.vue';
|
||||||
|
import LinuxInstructions from './Platforms/LinuxInstructions.vue';
|
||||||
|
import WindowsInstructions from './Platforms/WindowsInstructions.vue';
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup() {
|
||||||
|
const { currentState } = injectKey((keys) => keys.useCollectionState);
|
||||||
|
|
||||||
|
const component = computed<Component>(() => getInstructionsComponent(
|
||||||
|
currentState.value.collection.os,
|
||||||
|
));
|
||||||
|
|
||||||
|
return {
|
||||||
|
component,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
function getInstructionsComponent(operatingSystem: OperatingSystem): Component {
|
||||||
|
switch (operatingSystem) {
|
||||||
|
case OperatingSystem.macOS:
|
||||||
|
return MacOsInstructions;
|
||||||
|
case OperatingSystem.Linux:
|
||||||
|
return LinuxInstructions;
|
||||||
|
case OperatingSystem.Windows:
|
||||||
|
return WindowsInstructions;
|
||||||
|
default:
|
||||||
|
throw new Error(`No instructions for the operating system: ${OperatingSystem[operatingSystem]}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
<template>
|
||||||
|
<InstructionSteps>
|
||||||
|
<InstructionStep>
|
||||||
|
Download the file.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
You should have already been prompted to save the script file.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If this was not the case or you did not save the script when prompted,
|
||||||
|
please try to download your script file again.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
Open terminal.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Opening terminal changes based on the distro you run.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
You may search for "Terminal" in your application launcher to find it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Alternatively use terminal shortcut for your distro if it has one by default:
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<code>Ctrl-Alt-T</code>:
|
||||||
|
Ubuntu, CentOS, Linux Mint, Elementary OS, ubermix, Kali…
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>Super-T</code>: Pop!_OS…
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>Alt-T</code>: Parrot OS…
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<code>Ctrl-Alt-Insert</code>: Bodhi Linux…
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<p>
|
||||||
|
Navigate to the folder where you downloaded the file e.g.:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<CopyableCommand>
|
||||||
|
cd ~/Downloads
|
||||||
|
</CopyableCommand>
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Press on <code>enter/return</code> key after running the command.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If the file is not downloaded on Downloads folder,
|
||||||
|
change <code>Downloads</code> to path where the file is downloaded.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This command means:
|
||||||
|
<ul>
|
||||||
|
<li><code>cd</code> will change the current folder.</li>
|
||||||
|
<li><code>~</code> is the user home directory.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<p>
|
||||||
|
Give the file execute permissions:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<CopyableCommand>
|
||||||
|
chmod +x {{ filename }}
|
||||||
|
</CopyableCommand>
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Press on <code>enter/return</code> key after running the command.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It will make the file executable.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you use desktop environment you can alternatively (instead of running the command):
|
||||||
|
<ol>
|
||||||
|
<li>Locate the file using your file manager.</li>
|
||||||
|
<li>Right click on the file, select "Properties".</li>
|
||||||
|
<li>Go to "Permissions" and check "Allow executing file as program".</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
These GUI steps and name of options may change depending on your file manager.'
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<p>
|
||||||
|
Execute the file:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<CopyableCommand>
|
||||||
|
./{{ filename }}
|
||||||
|
</CopyableCommand>
|
||||||
|
<InfoTooltip>
|
||||||
|
If you have desktop environment, instead of running this command you can alternatively:
|
||||||
|
<ol>
|
||||||
|
<li>Locate the file using your file manager.</li>
|
||||||
|
<li>Right click on the file, select "Run as program".</li>
|
||||||
|
</ol>
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
If asked, enter your administrator password.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
As you type, your password will be hidden but the keys are still
|
||||||
|
registered, so keep typing.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Press on <code>enter/return</code> key after typing your password.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Administrator privileges are required to configure OS.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
</InstructionSteps>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import InstructionSteps from '../InstructionSteps.vue';
|
||||||
|
import InstructionStep from '../InstructionStep.vue';
|
||||||
|
import InfoTooltip from '../../InfoTooltip.vue';
|
||||||
|
import CopyableCommand from '../CopyableCommand.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
CopyableCommand,
|
||||||
|
InfoTooltip,
|
||||||
|
InstructionSteps,
|
||||||
|
InstructionStep,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
<template>
|
||||||
|
<InstructionSteps>
|
||||||
|
<InstructionStep>
|
||||||
|
Download the file.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
You should have already been prompted to save the script file.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If this was not the case or you did not save the script when prompted,
|
||||||
|
please try to download your script file again.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
Open terminal.
|
||||||
|
<InfoTooltip>
|
||||||
|
Type Terminal into Spotlight or open it from the Applications -> Utilities folder.
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<p>
|
||||||
|
Navigate to the folder where you downloaded the file e.g.:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<CopyableCommand>
|
||||||
|
cd ~/Downloads
|
||||||
|
</CopyableCommand>
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Press on <code>enter/return</code> key after running the command.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If the file is not downloaded on Downloads folder,
|
||||||
|
change <code>Downloads</code> to path where the file is downloaded.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This command means:
|
||||||
|
<ul>
|
||||||
|
<li><code>cd</code> will change the current folder.</li>
|
||||||
|
<li><code>~</code> is the user home directory.</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<p>
|
||||||
|
Give the file execute permissions:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<CopyableCommand>
|
||||||
|
chmod +x {{ filename }}
|
||||||
|
</CopyableCommand>
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Press on <code>enter/return</code> key after running the command.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
It will make the file executable.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<p>
|
||||||
|
Execute the file:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<CopyableCommand>
|
||||||
|
./{{ filename }}
|
||||||
|
</CopyableCommand>
|
||||||
|
<InfoTooltip>
|
||||||
|
Alternatively you can locate the file in <strong>Finder</strong> and double click on it.
|
||||||
|
</InfoTooltip>
|
||||||
|
</p>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
If asked, enter your administrator password.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
As you type, your password will be hidden but the keys are
|
||||||
|
still registered, so keep typing.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Press on <code>enter/return</code> key after typing your password.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Administrator privileges are required to configure OS.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
</InstructionSteps>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import InstructionSteps from '../InstructionSteps.vue';
|
||||||
|
import InstructionStep from '../InstructionStep.vue';
|
||||||
|
import InfoTooltip from '../../InfoTooltip.vue';
|
||||||
|
import CopyableCommand from '../CopyableCommand.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
CopyableCommand,
|
||||||
|
InfoTooltip,
|
||||||
|
InstructionSteps,
|
||||||
|
InstructionStep,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,154 @@
|
|||||||
|
<template>
|
||||||
|
<InstructionSteps>
|
||||||
|
<InstructionStep>
|
||||||
|
Download the file.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>If a save prompt doesn't appear, try downloading the script again.</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
If warned by your browser, keep the file.
|
||||||
|
<InfoTooltip>
|
||||||
|
<!--
|
||||||
|
Tests (15/01/2023):
|
||||||
|
- Edge (Defender activated): "filename isn't commonly downloaded..."
|
||||||
|
- Chrome: No warning, downloads directly.
|
||||||
|
- Firefox: No warning, downloads directly.
|
||||||
|
-->
|
||||||
|
<p>
|
||||||
|
Browsers may warn you when downloading scripts.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
privacy.sexy scripts are verified to be safe and are dedicated to securing your privacy.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
In <strong>Edge</strong>:
|
||||||
|
<ol>
|
||||||
|
<li>Select <strong>Keep</strong> from the downloads section.</li>
|
||||||
|
<li>Click <strong>Show more</strong> on the next warning.</li>
|
||||||
|
<li>Select <strong>Keep anyway</strong>.</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
For <strong>Firefox</strong> and <strong>Chrome</strong>, typically no additional
|
||||||
|
action is needed.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
If your antivirus (e.g., Defender) alerts you, address the warning.
|
||||||
|
<InfoTooltip>
|
||||||
|
<!--
|
||||||
|
Tests (15/01/2023):
|
||||||
|
- Edge (Defender activated): "Couldn't download - Virus detected"
|
||||||
|
- Chrome: "Virus detected"
|
||||||
|
- Firefox: Does not trigger false-positive on Defender somehow.
|
||||||
|
-->
|
||||||
|
<p>
|
||||||
|
Depending on the script, antivirus software may incorrectly flag the download as a threat.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
These false positives are common for scripts that modify system settings.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
To handle false warnings in Microsoft Defender:
|
||||||
|
<ol>
|
||||||
|
<li>Open <strong>Virus & threat protection</strong> from the Start menu.</li>
|
||||||
|
<li>
|
||||||
|
Locate the event in <strong>Protection history</strong>
|
||||||
|
that pertains to the script.
|
||||||
|
</li>
|
||||||
|
<li>In the event details, select <strong>Actions</strong> > <strong>Allow</strong>.</li>
|
||||||
|
<li>If the script was deleted, please re-download it.</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
<blockquote>
|
||||||
|
<strong>Caution:</strong> For your security, remember to:
|
||||||
|
<ul>
|
||||||
|
<li>Only allow scripts from trusted sources.</li>
|
||||||
|
<li>Avoid broad exclusions in your antivirus settings.</li>
|
||||||
|
<li>Keep real-time protection enabled whenever possible.</li>
|
||||||
|
</ul>
|
||||||
|
</blockquote>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
<!--
|
||||||
|
Tests (15/01/2023):
|
||||||
|
- Edge (Defender activated): No prompts
|
||||||
|
- Chrome: No prompts
|
||||||
|
- Firefox: "filename is executable file. Executable files may contain..?" OK/Cancel
|
||||||
|
-->
|
||||||
|
Open the downloaded file.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Confirm any browser prompts to open the file.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This standard security measure ensures that you are aware of the script execution.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Firefox</strong> users, click <strong>OK</strong> to acknowledge the
|
||||||
|
executable file warning.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<strong>Edge</strong> and <strong>Chrome</strong> users usually will not
|
||||||
|
encounter additional prompts.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
If prompted, confirm SmartScreen warnings.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
Windows SmartScreen might display a cautionary message.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This happens since privacy.sexy scripts are not recognized
|
||||||
|
by Microsoft's certification process.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<ol>
|
||||||
|
<li>Select <strong>More info</strong>.</li>
|
||||||
|
<li>Select <strong>Run anyway</strong>.</li>
|
||||||
|
</ol>
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
<InstructionStep>
|
||||||
|
If administrative permissions are requested, grant them.
|
||||||
|
<InfoTooltip>
|
||||||
|
<p>
|
||||||
|
The script may request administrative rights to apply changes.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
This is necessary for the script to apply the intended privacy settings.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Click <strong>Yes</strong> to authorize and run the script.
|
||||||
|
</p>
|
||||||
|
</InfoTooltip>
|
||||||
|
</InstructionStep>
|
||||||
|
</InstructionSteps>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
import InstructionSteps from '../InstructionSteps.vue';
|
||||||
|
import InstructionStep from '../InstructionStep.vue';
|
||||||
|
import InfoTooltip from '../../InfoTooltip.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: {
|
||||||
|
InfoTooltip,
|
||||||
|
InstructionSteps,
|
||||||
|
InstructionStep,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { IInstructionsBuilderData, InstructionsBuilder, InstructionStepBuilderType } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder';
|
|
||||||
import { IInstructionInfo, IInstructionListStep } from '@/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListData';
|
|
||||||
|
|
||||||
describe('InstructionsBuilder', () => {
|
|
||||||
describe('withStep', () => {
|
|
||||||
it('returns itself', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = new InstructionsBuilder(OperatingSystem.Android);
|
|
||||||
const step = () => createMockStep();
|
|
||||||
// act
|
|
||||||
const actual = expected.withStep(step);
|
|
||||||
// assert
|
|
||||||
expect(actual).to.equal(expected);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { describe } from 'vitest';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { MacOsInstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/MacOsInstructionsBuilder';
|
|
||||||
import { runOsSpecificInstructionBuilderTests } from './OsSpecificInstructionBuilderTestRunner';
|
|
||||||
|
|
||||||
describe('MacOsInstructionsBuilder', () => {
|
|
||||||
runOsSpecificInstructionBuilderTests({
|
|
||||||
factory: () => new MacOsInstructionsBuilder(),
|
|
||||||
os: OperatingSystem.macOS,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
import { it, expect } from 'vitest';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/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);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
|
||||||
import { OperatingSystem } from '@/domain/OperatingSystem';
|
|
||||||
import { getInstructions } from '@/presentation/components/Code/CodeButtons/Save/Instructions/InstructionListDataFactory';
|
|
||||||
import { getEnumValues } from '@/application/Common/Enum';
|
|
||||||
import { InstructionsBuilder } from '@/presentation/components/Code/CodeButtons/Save/Instructions/Data/InstructionsBuilder';
|
|
||||||
import { AllSupportedOperatingSystems } from '@tests/shared/TestCases/SupportedOperatingSystems';
|
|
||||||
|
|
||||||
describe('InstructionListDataFactory', () => {
|
|
||||||
describe('getInstructions', () => {
|
|
||||||
it('returns expected if os is supported', () => {
|
|
||||||
// arrange
|
|
||||||
const fileName = 'test.file';
|
|
||||||
// act
|
|
||||||
const actualResults = AllSupportedOperatingSystems.map((os) => getInstructions(os, fileName));
|
|
||||||
// assert
|
|
||||||
expect(actualResults.every((result) => result instanceof InstructionsBuilder));
|
|
||||||
});
|
|
||||||
it('return undefined if OS is not supported', () => {
|
|
||||||
// arrange
|
|
||||||
const expected = undefined;
|
|
||||||
const fileName = 'test.file';
|
|
||||||
const unsupportedOses = getEnumValues(OperatingSystem)
|
|
||||||
.filter((value) => !AllSupportedOperatingSystems.includes(value));
|
|
||||||
// act
|
|
||||||
const actualResults = unsupportedOses.map((os) => getInstructions(os, fileName));
|
|
||||||
// assert
|
|
||||||
expect(actualResults.every((result) => result === expected));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect } from 'vitest';
|
import { describe, it, expect } from 'vitest';
|
||||||
import { shallowMount } from '@vue/test-utils';
|
import { shallowMount } from '@vue/test-utils';
|
||||||
import CodeInstruction from '@/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue';
|
import CodeInstruction from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/CopyableCommand.vue';
|
||||||
import { expectThrowsAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
|
import { expectThrowsAsync } from '@tests/shared/Assertions/ExpectThrowsAsync';
|
||||||
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||||
import { Clipboard } from '@/presentation/components/Shared/Hooks/Clipboard/Clipboard';
|
import { Clipboard } from '@/presentation/components/Shared/Hooks/Clipboard/Clipboard';
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import PlatformInstructionSteps from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/PlatformInstructionSteps.vue';
|
||||||
|
import { useCollectionState } from '@/presentation/components/Shared/Hooks/UseCollectionState';
|
||||||
|
import { InjectionKeys } from '@/presentation/injectionSymbols';
|
||||||
|
import { UseCollectionStateStub } from '@tests/unit/shared/Stubs/UseCollectionStateStub';
|
||||||
|
import { AllSupportedOperatingSystems, SupportedOperatingSystem } from '@tests/shared/TestCases/SupportedOperatingSystems';
|
||||||
|
import { OperatingSystem } from '@/domain/OperatingSystem';
|
||||||
|
import { CategoryCollectionStateStub } from '@tests/unit/shared/Stubs/CategoryCollectionStateStub';
|
||||||
|
import WindowsInstructions from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/WindowsInstructions.vue';
|
||||||
|
import MacOsInstructions from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/MacOsInstructions.vue';
|
||||||
|
import LinuxInstructions from '@/presentation/components/Code/CodeButtons/Save/RunInstructions/Steps/Platforms/LinuxInstructions.vue';
|
||||||
|
import type { Component } from 'vue';
|
||||||
|
|
||||||
|
describe('PlatformInstructionSteps', () => {
|
||||||
|
const testScenarios: Record<SupportedOperatingSystem, Component> = {
|
||||||
|
[OperatingSystem.Windows]: WindowsInstructions,
|
||||||
|
[OperatingSystem.macOS]: MacOsInstructions,
|
||||||
|
[OperatingSystem.Linux]: LinuxInstructions,
|
||||||
|
};
|
||||||
|
AllSupportedOperatingSystems.forEach((operatingSystem) => {
|
||||||
|
it(`renders the correct component for ${OperatingSystem[operatingSystem]}`, () => {
|
||||||
|
// arrange
|
||||||
|
const expectedComponent = testScenarios[operatingSystem];
|
||||||
|
const useCollectionStateStub = new UseCollectionStateStub()
|
||||||
|
.withState(new CategoryCollectionStateStub().withOs(operatingSystem));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
useCollectionState: useCollectionStateStub.get(),
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
expect(wrapper.findComponent(expectedComponent).exists()).to.equal(true);
|
||||||
|
});
|
||||||
|
it(`binds the correct filename for ${OperatingSystem[operatingSystem]}`, () => {
|
||||||
|
// arrange
|
||||||
|
const expectedFilename = 'expected-file-name.bat';
|
||||||
|
const wrappedComponent = testScenarios[operatingSystem];
|
||||||
|
const useCollectionStateStub = new UseCollectionStateStub()
|
||||||
|
.withState(new CategoryCollectionStateStub().withOs(operatingSystem));
|
||||||
|
|
||||||
|
// act
|
||||||
|
const wrapper = mountComponent({
|
||||||
|
useCollectionState: useCollectionStateStub.get(),
|
||||||
|
filename: expectedFilename,
|
||||||
|
});
|
||||||
|
|
||||||
|
// assert
|
||||||
|
const componentWrapper = wrapper.findComponent(wrappedComponent);
|
||||||
|
expect(componentWrapper.props('filename')).to.equal(expectedFilename);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
function mountComponent(options?: {
|
||||||
|
readonly useCollectionState?: ReturnType<typeof useCollectionState>;
|
||||||
|
readonly filename?: string;
|
||||||
|
}) {
|
||||||
|
return shallowMount(PlatformInstructionSteps, {
|
||||||
|
global: {
|
||||||
|
provide: {
|
||||||
|
[InjectionKeys.useCollectionState.key]:
|
||||||
|
() => options?.useCollectionState ?? new UseCollectionStateStub().get(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
filename: options?.filename === undefined ? 'privacy-test-script.bat' : options.filename,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user