Skip to content

Commit

Permalink
Merge pull request #331 from holochain-apps/refactor/no-duck-typing
Browse files Browse the repository at this point in the history
Refactor/no duck typing
  • Loading branch information
mattyg committed Dec 18, 2024
1 parent 60430f3 commit 4b0b464
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 53 deletions.
91 changes: 61 additions & 30 deletions ui/src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import { platform } from "@tauri-apps/plugin-os";
import { setModeCurrent } from "@skeletonlabs/skeleton";
import { open } from "@tauri-apps/plugin-shell";
import linkifyStr from "linkify-string";
import { writeText } from "@tauri-apps/plugin-clipboard-manager";

/**
* Sanitize user-inputted HTML before we render it to prevent XSS attacks
*
* @param html
* @returns
*/
export function sanitizeHTML(html: string) {
return DOMPurify.sanitize(html);
}
Expand All @@ -29,38 +36,41 @@ export const linkify = (text: string): string =>
target: "_blank",
});

export async function shareText(text: string | Promise<string>) {
if (typeof text === "string") {
if (text && text.trim().length > 0) {
return sharesheetShareText(text);
}
} else {
const t = await text;
return sharesheetShareText(t);
}
/**
* Share text via sharesheet
*
* @param text
* @returns
*/
export function shareText(text: string): Promise<void> {
if (!isMobile()) throw Error("Sharesheet is only supported on mobile");

const normalized = text.trim();
if (normalized.length === 0) throw Error("Text is empty");

return sharesheetShareText(normalized);
}

export async function copyToClipboard(text: string | Promise<string>) {
if (typeof text === "string") {
if (text && text.trim().length > 0) {
console.log("Copying to clipboard", text);
return navigator.clipboard.writeText(text);
}
} else {
const t = await text;
console.log("Copying to clipboard", text);

if (typeof ClipboardItem && navigator.clipboard.write) {
const item = new ClipboardItem({
"text/plain": new Blob([t], { type: "text/plain" }),
});
return navigator.clipboard.write([item]);
} else {
return navigator.clipboard.writeText(t);
}
}
/**
* Copy text to clipboard
*
* @param text
* @returns
*/
export function copyToClipboard(text: string): Promise<void> {
const normalized = text.trim();
if (normalized.length === 0) throw Error("Text is empty");

return writeText(text);
}

/**
* Send a system notification
* If permissions have not been granted for sending notifications, request them.
*
* @param title
* @param body
*/
export async function enqueueNotification(title: string, body: string) {
try {
const hasPermission = await isPermissionGranted();
Expand All @@ -75,11 +85,22 @@ export async function enqueueNotification(title: string, body: string) {
}
}

/**
* Is app running on mobile?
*
* @returns
*/
export function isMobile(): boolean {
const val = platform();
return val === "android" || val === "ios";
}

/**
* Convert file to data url
*
* @param file
* @returns
*/
export async function fileToDataUrl(file: File): Promise<string> {
const reader = new FileReader();
reader.readAsDataURL(file);
Expand All @@ -98,8 +119,6 @@ export async function fileToDataUrl(file: File): Promise<string> {
});
}

// To change from light mode to dark mode based on system settings
// XXX: not using the built in skeleton autoModeWatcher() because it doesn't set modeCurrent in JS which we use
function setLightDarkMode(value: boolean) {
const elemHtmlClasses = document.documentElement.classList;
const classDark = `dark`;
Expand All @@ -109,6 +128,12 @@ function setLightDarkMode(value: boolean) {
setModeCurrent(value);
}

/**
* Toggle dark mode to mirror system settings.
* We are not using skeleton's autoModeWatcher() because it doesn't update modeCurrent.
* @param value
*/

export function initLightDarkModeSwitcher() {
const mql = window.matchMedia("(prefers-color-scheme: light)");

Expand Down Expand Up @@ -137,6 +162,12 @@ export function handleLinkClick(e: MouseEvent) {
open(anchor.getAttribute("href") as string);
}

/**
* Convert a base64 encoded data URI to a Uint8Array of the decoded bytes.
*
* @param dataURI
* @returns
*/
export function convertDataURIToUint8Array(dataURI: string): Uint8Array {
const BASE64_MARKER = ";base64,";
const base64Index = dataURI.indexOf(BASE64_MARKER) + BASE64_MARKER.length;
Expand Down
49 changes: 36 additions & 13 deletions ui/src/routes/contacts/ContactEditor.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -209,10 +209,9 @@
<button
on:click={async () => {
try {
if (contact?.publicKeyB64) {
await copyToClipboard(contact.publicKeyB64);
toast.success(`${$t("common.copy_success")}`);
}
if (!contact?.publicKeyB64) throw new Error("Contact Public Key not found");
await copyToClipboard(contact.publicKeyB64);
toast.success(`${$t("common.copy_success")}`);
} catch (e) {
toast.error(`${$t("common.copy_error")}: ${e.message}`);
}
Expand All @@ -221,7 +220,16 @@
<SvgIcon icon="copy" size="20" color="%23999" />
</button>
{#if isMobile()}
<button on:click={() => contact?.publicKeyB64 && shareText(contact.publicKeyB64)}>
<button
on:click={async () => {
try {
if (!contact?.publicKeyB64) throw new Error("Contact Pub Key not found");
await shareText(contact.publicKeyB64);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
>
<SvgIcon icon="share" size="20" color="%23999" />
</button>
{/if}
Expand All @@ -242,21 +250,36 @@
<div class="flex justify-center">
<Button
moreClasses="bg-surface-100 text-sm text-secondary-500 dark:text-tertiary-100 font-bold dark:bg-secondary-900"
on:click={() =>
copyToClipboard(
contact?.privateConversation?.inviteCodeForAgent(contact?.publicKeyB64) || "",
)}
on:click={async () => {
try {
const inviteCode = await contact?.privateConversation?.inviteCodeForAgent(
contact?.publicKeyB64,
);
if (!inviteCode) throw new Error("Failed to generate invite code");
await copyToClipboard(inviteCode);
toast.success(`${$t("common.copy_success")}`);
} catch (e) {
toast.error(`${$t("common.copy_error")}: ${e.message}`);
}
}}
>
<SvgIcon icon="copy" size="20" color="%23FD3524" moreClasses="mr-2" />
{$t("contacts.copy_invite_code")}
</Button>
{#if isMobile()}
<Button
moreClasses="bg-surface-100 text-sm text-secondary-500 dark:text-tertiary-100 font-bold dark:bg-secondary-900"
on:click={() =>
shareText(
contact?.privateConversation?.inviteCodeForAgent(contact?.publicKeyB64) || "",
)}
on:click={async () => {
try {
const inviteCode = await contact?.privateConversation?.inviteCodeForAgent(
contact?.publicKeyB64,
);
if (!inviteCode) throw new Error("Failed to generate invite code");
await shareText(inviteCode);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
>
<SvgIcon icon="copy" size="20" color="%23FD3524" moreClasses="mr-2" />
<strong>{$t("contacts.share_invite_code")}</strong>
Expand Down
25 changes: 19 additions & 6 deletions ui/src/routes/conversations/[id]/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,10 @@
moreClasses="bg-surface-100 text-sm text-secondary-500 dark:text-tertiary-100 font-bold dark:bg-secondary-900"
on:click={async () => {
try {
const inviteCode = conversation.inviteCodeForAgent(
const inviteCode = await conversation.inviteCodeForAgent(
conversation.allMembers[0]?.publicKeyB64,
);
if (!inviteCode) throw new Error("Failed to generate invite code");
await copyToClipboard(inviteCode);
toast.success(`${$t("common.copy_success")}`);
} catch (e) {
Expand All @@ -440,10 +441,16 @@
{#if isMobile()}
<Button
moreClasses="bg-surface-100 text-sm text-secondary-500 dark:text-tertiary-100 font-bold dark:bg-secondary-900"
on:click={() => {
shareText(
conversation.inviteCodeForAgent(conversation.allMembers[0]?.publicKeyB64),
);
on:click={async () => {
try {
const inviteCode = await conversation.inviteCodeForAgent(
conversation.allMembers[0]?.publicKeyB64,
);
if (!inviteCode) throw new Error("Failed to generate invite code");
await shareText(inviteCode);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
>
<SvgIcon icon="share" size="20" color="%23FD3524" moreClasses="mr-2" />
Expand Down Expand Up @@ -485,7 +492,13 @@
</Button>
{#if isMobile()}
<Button
on:click={() => shareText(conversation.publicInviteCode)}
on:click={async () => {
try {
await shareText(conversation.publicInviteCode);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
moreClasses="w-64 justify-center variant-filled-tertiary"
>
<SvgIcon icon="share" size="18" color="%23FD3524" />
Expand Down
23 changes: 20 additions & 3 deletions ui/src/routes/conversations/[id]/details/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,13 @@
{#if isMobile()}
<button
class="bg-surface-500 text-secondary-500 mr-1 flex items-center justify-center rounded-full px-2 py-2 text-xs font-bold"
on:click={() => shareText(conversation.publicInviteCode)}
on:click={async () => {
try {
await shareText(conversation.publicInviteCode);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
>
<SvgIcon icon="share" size="14" color="%23FD3524" moreClasses="mr-1" />
</button>
Expand All @@ -232,7 +238,8 @@
class="variant-filled-tertiary flex items-center justify-center rounded-2xl p-2 px-3 text-sm font-bold"
on:click={async () => {
try {
await copyToClipboard(conversation.inviteCodeForAgent(contact.publicKeyB64));
const inviteCode = await conversation.inviteCodeForAgent(contact.publicKeyB64);
await copyToClipboard(inviteCode);
toast.success(`${$t("common.copy_success")}`);
} catch (e) {
toast.error(`${$t("common.copy_error")}: ${e.message}`);
Expand All @@ -245,7 +252,17 @@
{#if isMobile()}
<button
class="variant-filled-tertiary flex items-center justify-center rounded-2xl p-2 px-3 text-sm font-bold"
on:click={() => shareText(conversation.inviteCodeForAgent(contact.publicKeyB64))}
on:click={async () => {
try {
const inviteCode = await conversation.inviteCodeForAgent(
contact.publicKeyB64,
);
if (!inviteCode) throw new Error("Failed to generate invite code");
await shareText(inviteCode);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
>
<SvgIcon icon="share" size="18" color="%23FD3524" moreClasses="mr-2" />
</button>
Expand Down
11 changes: 10 additions & 1 deletion ui/src/routes/conversations/[id]/invite/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,16 @@
>
</Button>
{#if isMobile()}
<Button on:click={() => shareText(conversation.publicInviteCode)} moreClasses="w-64">
<Button
on:click={async () => {
try {
await shareText(conversation.publicInviteCode);
} catch (e) {
toast.error(`${$t("common.share_code_error")}: ${e.message}`);
}
}}
moreClasses="w-64"
>
<p class="w-64 overflow-hidden text-ellipsis text-nowrap">
{conversation.publicInviteCode}
</p>
Expand Down

0 comments on commit 4b0b464

Please sign in to comment.