Skip to content

Commit

Permalink
Fix copy button not working in instructions
Browse files Browse the repository at this point in the history
Fix by emitting `click` event in AppIcon.vue.

Other changes:

- Refactor `TheCodebuttons.vue` for simplicity, easier testing and
  maintainability:
  - Remove redundant `getCurrentCode` function.
  - Create a component for each button.
- Create `useClipboard` hook to use more Vue-dumatic way.
- Create `useCurrentCode` hook to use current code in more Vue-dumatic
  way.
- Create abstraction for `Clipboard`.
- Change clipboard implementation to use `navigator.clipboard` instead
  Of deprecated `document.execCommand`.
- Move Clipboard logic to presentation layer to better align with
  separation of concerns and domain-driven design.
  • Loading branch information
undergroundwires committed Nov 6, 2023
1 parent b2ffc90 commit 2dcafb2
Show file tree
Hide file tree
Showing 36 changed files with 799 additions and 183 deletions.
13 changes: 0 additions & 13 deletions src/infrastructure/Clipboard.ts

This file was deleted.

8 changes: 8 additions & 0 deletions src/presentation/bootstrapping/DependencyProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { useAutoUnsubscribedEvents } from '@/presentation/components/Shared/Hook
import { IApplicationContext } from '@/application/Context/IApplicationContext';
import { RuntimeEnvironment } from '@/infrastructure/RuntimeEnvironment/RuntimeEnvironment';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { useClipboard } from '../components/Shared/Hooks/Clipboard/UseClipboard';
import { useCurrentCode } from '../components/Shared/Hooks/UseCurrentCode';

export function provideDependencies(
context: IApplicationContext,
Expand All @@ -23,6 +25,12 @@ export function provideDependencies(
const { events } = api.inject(InjectionKeys.useAutoUnsubscribedEvents)();
return useCollectionState(context, events);
});
registerTransient(InjectionKeys.useClipboard, () => useClipboard());
registerTransient(InjectionKeys.useCurrentCode, () => {
const { events } = api.inject(InjectionKeys.useAutoUnsubscribedEvents)();
const state = api.inject(InjectionKeys.useCollectionState)();
return useCurrentCode(state, events);
});
}

export interface VueDependencyInjectionApi {
Expand Down
33 changes: 33 additions & 0 deletions src/presentation/components/Code/CodeButtons/CodeCopyButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<template>
<IconButton
text="Copy"
v-on:click="copyCode"
icon-name="copy"
/>
</template>

<script lang="ts">
import {
defineComponent, inject,
} from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import IconButton from './IconButton.vue';
export default defineComponent({
components: {
IconButton,
},
setup() {
const { copyText } = inject(InjectionKeys.useClipboard)();
const { currentCode } = inject(InjectionKeys.useCurrentCode)();
async function copyCode() {
await copyText(currentCode.value);
}
return {
copyCode,
};
},
});
</script>
59 changes: 59 additions & 0 deletions src/presentation/components/Code/CodeButtons/CodeRunButton.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<template>
<IconButton
v-if="canRun"
text="Run"
v-on:click="executeCode"
icon-name="play"
/>
</template>

<script lang="ts">
import {
defineComponent, computed, inject,
} from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { OperatingSystem } from '@/domain/OperatingSystem';
import { CodeRunner } from '@/infrastructure/CodeRunner';
import { IReadOnlyApplicationContext } from '@/application/Context/IApplicationContext';
import IconButton from './IconButton.vue';
export default defineComponent({
components: {
IconButton,
},
setup() {
const { currentState, currentContext } = inject(InjectionKeys.useCollectionState)();
const { os, isDesktop } = inject(InjectionKeys.useRuntimeEnvironment);
const canRun = computed<boolean>(() => getCanRunState(currentState.value.os, isDesktop, os));
async function executeCode() {
await runCode(currentContext);
}
return {
isDesktopVersion: isDesktop,
canRun,
executeCode,
};
},
});
function getCanRunState(
selectedOs: OperatingSystem,
isDesktopVersion: boolean,
hostOs: OperatingSystem,
): boolean {
const isRunningOnSelectedOs = selectedOs === hostOs;
return isDesktopVersion && isRunningOnSelectedOs;
}
async function runCode(context: IReadOnlyApplicationContext) {
const runner = new CodeRunner();
await runner.runCode(
/* code: */ context.state.code.current,
/* appName: */ context.app.info.name,
/* fileExtension: */ context.state.collection.scripting.fileExtension,
);
}
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<template>
<IconButton
:text="isDesktopVersion ? 'Save' : 'Download'"
v-on:click="saveCode"
:icon-name="isDesktopVersion ? 'floppy-disk' : 'file-arrow-down'"
/>
<!-- eslint-disable-next-line vue/no-multiple-template-root -->
<ModalDialog v-if="instructions" v-model="areInstructionsVisible">
<InstructionList :data="instructions" />
</ModalDialog>
</template>

<script lang="ts">
import {
defineComponent, ref, computed, inject,
} from 'vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
import { SaveFileDialog, FileType } from '@/infrastructure/SaveFileDialog';
import ModalDialog from '@/presentation/components/Shared/Modal/ModalDialog.vue';
import { IReadOnlyCategoryCollectionState } from '@/application/Context/State/ICategoryCollectionState';
import { ScriptingLanguage } from '@/domain/ScriptingLanguage';
import { IScriptingDefinition } from '@/domain/IScriptingDefinition';
import { OperatingSystem } from '@/domain/OperatingSystem';
import IconButton from '../IconButton.vue';
import InstructionList from './Instructions/InstructionList.vue';
import { IInstructionListData } from './Instructions/InstructionListData';
import { getInstructions, hasInstructions } from './Instructions/InstructionListDataFactory';
export default defineComponent({
components: {
IconButton,
InstructionList,
ModalDialog,
},
setup() {
const { currentState } = inject(InjectionKeys.useCollectionState)();
const { isDesktop } = inject(InjectionKeys.useRuntimeEnvironment);
const areInstructionsVisible = ref(false);
const fileName = computed<string>(() => buildFileName(currentState.value.collection.scripting));
const instructions = computed<IInstructionListData | undefined>(() => getDownloadInstructions(
currentState.value.collection.os,
fileName.value,
));
function saveCode() {
saveCodeToDisk(fileName.value, currentState.value);
areInstructionsVisible.value = true;
}
return {
isDesktopVersion: isDesktop,
instructions,
fileName,
areInstructionsVisible,
saveCode,
};
},
});
function getDownloadInstructions(
os: OperatingSystem,
fileName: string,
): IInstructionListData | undefined {
if (!hasInstructions(os)) {
return undefined;
}
return getInstructions(os, fileName);
}
function saveCodeToDisk(fileName: string, state: IReadOnlyCategoryCollectionState) {
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>
Original file line number Diff line number Diff line change
Expand Up @@ -16,20 +16,22 @@
</template>

<script lang="ts">
import { defineComponent, shallowRef } from 'vue';
import { Clipboard } from '@/infrastructure/Clipboard';
import { defineComponent, shallowRef, inject } from 'vue';
import TooltipWrapper from '@/presentation/components/Shared/TooltipWrapper.vue';
import AppIcon from '@/presentation/components/Shared/Icon/AppIcon.vue';
import { InjectionKeys } from '@/presentation/injectionSymbols';
export default defineComponent({
components: {
TooltipWrapper,
AppIcon,
},
setup() {
const { copyText } = inject(InjectionKeys.useClipboard)();
const codeElement = shallowRef<HTMLElement | undefined>();
function copyCode() {
async function copyCode() {
const element = codeElement.value;
if (!element) {
throw new Error('Code element could not be found.');
Expand All @@ -38,7 +40,7 @@ export default defineComponent({
if (!code) {
throw new Error('Code element does not contain any text.');
}
Clipboard.copyText(code);
await copyText(code);
}
return {
Expand Down
Loading

0 comments on commit 2dcafb2

Please sign in to comment.