diff --git a/src/infrastructure/Clipboard.ts b/src/infrastructure/Clipboard.ts deleted file mode 100644 index ae7f8299..00000000 --- a/src/infrastructure/Clipboard.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class Clipboard { - public static copyText(text: string): void { - const el = document.createElement('textarea'); - el.value = text; - el.setAttribute('readonly', ''); // to avoid focus - el.style.position = 'absolute'; - el.style.left = '-9999px'; - document.body.appendChild(el); - el.select(); - document.execCommand('copy'); - document.body.removeChild(el); - } -} diff --git a/src/presentation/bootstrapping/DependencyProvider.ts b/src/presentation/bootstrapping/DependencyProvider.ts index 01e9a50f..dc392c02 100644 --- a/src/presentation/bootstrapping/DependencyProvider.ts +++ b/src/presentation/bootstrapping/DependencyProvider.ts @@ -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, @@ -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 { diff --git a/src/presentation/components/Code/CodeButtons/CodeCopyButton.vue b/src/presentation/components/Code/CodeButtons/CodeCopyButton.vue new file mode 100644 index 00000000..ab20237e --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/CodeCopyButton.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/CodeRunButton.vue b/src/presentation/components/Code/CodeButtons/CodeRunButton.vue new file mode 100644 index 00000000..97b04a40 --- /dev/null +++ b/src/presentation/components/Code/CodeButtons/CodeRunButton.vue @@ -0,0 +1,59 @@ + + + diff --git a/src/presentation/components/Code/CodeButtons/IconButton.vue b/src/presentation/components/Code/CodeButtons/IconButton.vue index f404be32..49983ce0 100644 --- a/src/presentation/components/Code/CodeButtons/IconButton.vue +++ b/src/presentation/components/Code/CodeButtons/IconButton.vue @@ -1,15 +1,17 @@ diff --git a/src/presentation/components/Code/CodeButtons/Instructions/CodeInstruction.vue b/src/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue similarity index 86% rename from src/presentation/components/Code/CodeButtons/Instructions/CodeInstruction.vue rename to src/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue index 802b6bb0..6d1131e1 100644 --- a/src/presentation/components/Code/CodeButtons/Instructions/CodeInstruction.vue +++ b/src/presentation/components/Code/CodeButtons/Save/Instructions/CodeInstruction.vue @@ -16,10 +16,10 @@ diff --git a/src/presentation/components/Shared/Hooks/Clipboard/BrowserClipboard.ts b/src/presentation/components/Shared/Hooks/Clipboard/BrowserClipboard.ts new file mode 100644 index 00000000..8211b37f --- /dev/null +++ b/src/presentation/components/Shared/Hooks/Clipboard/BrowserClipboard.ts @@ -0,0 +1,15 @@ +import { Clipboard } from './Clipboard'; + +export type NavigatorClipboard = typeof globalThis.navigator.clipboard; + +export class BrowserClipboard implements Clipboard { + constructor( + private readonly navigatorClipboard: NavigatorClipboard = globalThis.navigator.clipboard, + ) { + + } + + public async copyText(text: string): Promise { + await this.navigatorClipboard.writeText(text); + } +} diff --git a/src/presentation/components/Shared/Hooks/Clipboard/Clipboard.ts b/src/presentation/components/Shared/Hooks/Clipboard/Clipboard.ts new file mode 100644 index 00000000..a4dc64a7 --- /dev/null +++ b/src/presentation/components/Shared/Hooks/Clipboard/Clipboard.ts @@ -0,0 +1,3 @@ +export interface Clipboard { + copyText(text: string): Promise; +} diff --git a/src/presentation/components/Shared/Hooks/Clipboard/UseClipboard.ts b/src/presentation/components/Shared/Hooks/Clipboard/UseClipboard.ts new file mode 100644 index 00000000..3b15acb7 --- /dev/null +++ b/src/presentation/components/Shared/Hooks/Clipboard/UseClipboard.ts @@ -0,0 +1,13 @@ +import { FunctionKeys } from '@/TypeHelpers'; +import { BrowserClipboard } from './BrowserClipboard'; +import { Clipboard } from './Clipboard'; + +export function useClipboard(clipboard: Clipboard = new BrowserClipboard()) { + // Bind functions for direct use from destructured assignments such as `const { .. } = ...`. + const functionKeys: readonly FunctionKeys[] = ['copyText']; + functionKeys.forEach((functionName) => { + const fn = clipboard[functionName]; + clipboard[functionName] = fn.bind(clipboard); + }); + return clipboard; +} diff --git a/src/presentation/components/Shared/Hooks/UseCurrentCode.ts b/src/presentation/components/Shared/Hooks/UseCurrentCode.ts new file mode 100644 index 00000000..6b326713 --- /dev/null +++ b/src/presentation/components/Shared/Hooks/UseCurrentCode.ts @@ -0,0 +1,32 @@ +import { ref } from 'vue'; +import { IApplicationCode } from '@/application/Context/State/Code/IApplicationCode'; +import { IEventSubscriptionCollection } from '@/infrastructure/Events/IEventSubscriptionCollection'; +import { useCollectionState } from './UseCollectionState'; + +export function useCurrentCode( + state: ReturnType, + events: IEventSubscriptionCollection, +) { + const { onStateChange } = state; + + const currentCode = ref(''); + + onStateChange((newState) => { + updateCurrentCode(newState.code.current); + subscribeToCodeChanges(newState.code); + }, { immediate: true }); + + function subscribeToCodeChanges(code: IApplicationCode) { + events.unsubscribeAllAndRegister([ + code.changed.on((newCode) => updateCurrentCode(newCode.code)), + ]); + } + + function updateCurrentCode(newCode: string) { + currentCode.value = newCode; + } + + return { + currentCode, + }; +} diff --git a/src/presentation/components/Shared/Icon/AppIcon.vue b/src/presentation/components/Shared/Icon/AppIcon.vue index a1a1a73d..b9ce644f 100644 --- a/src/presentation/components/Shared/Icon/AppIcon.vue +++ b/src/presentation/components/Shared/Icon/AppIcon.vue @@ -1,5 +1,9 @@