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 @@
-
+