mirror of
https://github.com/ivabus/gui
synced 2025-04-23 14:07:14 +03:00
add-confetti (#652)
This commit is contained in:
parent
f9031a59df
commit
543411433b
17 changed files with 232 additions and 104 deletions
|
@ -33,7 +33,8 @@ export async function loadPackageCache(): Promise<Packages> {
|
||||||
if (pkgs?.packages) {
|
if (pkgs?.packages) {
|
||||||
// Remove any temporary properties that may have been added to the package (like installation progress)
|
// Remove any temporary properties that may have been added to the package (like installation progress)
|
||||||
for (const [key, value] of Object.entries(pkgs.packages)) {
|
for (const [key, value] of Object.entries(pkgs.packages)) {
|
||||||
const { install_progress_percentage, isUninstalling, synced, ...rest } = value;
|
const { install_progress_percentage, isUninstalling, synced, displayState, ...rest } =
|
||||||
|
value;
|
||||||
pkgs.packages[key] = rest;
|
pkgs.packages[key] = rest;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@
|
||||||
"@testing-library/svelte": "^3.2.2",
|
"@testing-library/svelte": "^3.2.2",
|
||||||
"@testing-library/webdriverio": "^3.2.1",
|
"@testing-library/webdriverio": "^3.2.1",
|
||||||
"@types/bcryptjs": "^2.4.2",
|
"@types/bcryptjs": "^2.4.2",
|
||||||
|
"@types/canvas-confetti": "^1.6.0",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
"@types/mixpanel-browser": "^2.38.1",
|
"@types/mixpanel-browser": "^2.38.1",
|
||||||
"@types/testing-library__jest-dom": "^5.14.5",
|
"@types/testing-library__jest-dom": "^5.14.5",
|
||||||
|
@ -94,6 +95,7 @@
|
||||||
"axios": "^1.3.2",
|
"axios": "^1.3.2",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
|
"canvas-confetti": "^1.6.0",
|
||||||
"chokidar": "^3.5.3",
|
"chokidar": "^3.5.3",
|
||||||
"custom-electron-titlebar": "4.2.0-beta.0",
|
"custom-electron-titlebar": "4.2.0-beta.0",
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
|
|
|
@ -0,0 +1,63 @@
|
||||||
|
<script lang="ts">
|
||||||
|
import type { GUIPackage } from "$libs/types";
|
||||||
|
import confetti from "canvas-confetti";
|
||||||
|
import { afterUpdate } from "svelte";
|
||||||
|
import { packagesStore } from "$libs/stores";
|
||||||
|
import Button from "@tea/ui/button/button.svelte";
|
||||||
|
|
||||||
|
let root: HTMLElement | undefined;
|
||||||
|
|
||||||
|
export let pkg: GUIPackage;
|
||||||
|
|
||||||
|
$: isAnimating = false;
|
||||||
|
|
||||||
|
const playConfetti = async () => {
|
||||||
|
if (isAnimating) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (root) {
|
||||||
|
isAnimating = true;
|
||||||
|
packagesStore.resetPackageDisplayState(pkg);
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.style.width = "100%";
|
||||||
|
canvas.style.height = "100%";
|
||||||
|
root.appendChild(canvas);
|
||||||
|
|
||||||
|
const myConfetti = confetti.create(canvas, { resize: true });
|
||||||
|
await myConfetti({ particleCount: 500, spread: 360, startVelocity: 20, gravity: 0.5 });
|
||||||
|
|
||||||
|
root.removeChild(canvas);
|
||||||
|
isAnimating = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const retry = () => {
|
||||||
|
packagesStore.resetPackageDisplayState(pkg);
|
||||||
|
packagesStore.installPkg(pkg, pkg.displayState?.version);
|
||||||
|
};
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (pkg.displayState?.kind === "INSTALLED_SUCCESSFULLY") {
|
||||||
|
playConfetti();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div bind:this={root} class="z-60 pointer-events-none absolute left-0 top-0 h-full w-full">
|
||||||
|
{#if pkg.displayState?.kind === "INSTALLATION_ERROR"}
|
||||||
|
<div
|
||||||
|
class="pointer-events-auto flex h-full w-full flex-col items-center justify-center bg-black/90"
|
||||||
|
>
|
||||||
|
<div><i class="icon-exlamation-outline text-3xl text-[#FF4100]" /></div>
|
||||||
|
<div class="mb-2 font-mona">Install Failed</div>
|
||||||
|
<div class="mb-2 w-1/2">
|
||||||
|
<Button type="plain" color="blue" class="p-2 text-xs" onClick={retry}>RETRY</Button>
|
||||||
|
</div>
|
||||||
|
<button class="text-xs text-gray" on:click={() => packagesStore.resetPackageDisplayState(pkg)}
|
||||||
|
>cancel</button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
|
@ -16,6 +16,7 @@
|
||||||
import PackageVersionSelector from "$components/package-install-button/package-version-selector.svelte";
|
import PackageVersionSelector from "$components/package-install-button/package-version-selector.svelte";
|
||||||
import { fixPackageName } from "$libs/packages/pkg-utils";
|
import { fixPackageName } from "$libs/packages/pkg-utils";
|
||||||
import { semverCompare } from "$libs/packages/pkg-utils";
|
import { semverCompare } from "$libs/packages/pkg-utils";
|
||||||
|
import InstallResultOverlay from "$components/install-result-overlay/install-result-overlay.svelte";
|
||||||
|
|
||||||
export let pkg: GUIPackage;
|
export let pkg: GUIPackage;
|
||||||
let installing = false;
|
let installing = false;
|
||||||
|
@ -88,6 +89,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
<InstallResultOverlay {pkg} />
|
||||||
</figure>
|
</figure>
|
||||||
<article class="w-2/3 p-4 pt-8">
|
<article class="w-2/3 p-4 pt-8">
|
||||||
<div class="align-center flex items-center gap-2">
|
<div class="align-center flex items-center gap-2">
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
|
import PackageInstallButton from "$components/package-install-button/package-install-button.svelte";
|
||||||
import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte";
|
import PackageInstalledBadge from "$components/package-install-button/package-installed-badge.svelte";
|
||||||
import { fixPackageName } from "$libs/packages/pkg-utils";
|
import { fixPackageName } from "$libs/packages/pkg-utils";
|
||||||
|
import InstallResultOverlay from "$components/install-result-overlay/install-result-overlay.svelte";
|
||||||
|
|
||||||
export let pkg: GUIPackage;
|
export let pkg: GUIPackage;
|
||||||
export let link: string;
|
export let link: string;
|
||||||
|
@ -26,6 +27,7 @@
|
||||||
const preventPropagation = (evt: MouseEvent) => evt.stopPropagation();
|
const preventPropagation = (evt: MouseEvent) => evt.stopPropagation();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<div class="relative">
|
||||||
<section class="package-card relative h-auto border border-gray {layout}" class:active={isActive}>
|
<section class="package-card relative h-auto border border-gray {layout}" class:active={isActive}>
|
||||||
<BgImage class="absolute left-0 top-0 h-full w-full" {layout} {pkg} />
|
<BgImage class="absolute left-0 top-0 h-full w-full" {layout} {pkg} />
|
||||||
|
|
||||||
|
@ -94,6 +96,8 @@
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
<InstallResultOverlay {pkg} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
section {
|
section {
|
||||||
|
|
|
@ -62,7 +62,7 @@
|
||||||
? 'justify-between'
|
? 'justify-between'
|
||||||
: 'justify-center'}"
|
: 'justify-center'}"
|
||||||
>
|
>
|
||||||
<div class="flex items-center gap-x-2">
|
<div class="flex items-center gap-x-2" data-testid={`install-badge-${pkg?.slug}`}>
|
||||||
<div>{ctaLabel}</div>
|
<div>{ctaLabel}</div>
|
||||||
<div class="version-label {badgeClass[pkg.state]}">v{getVersion(pkg)}</div>
|
<div class="version-label {badgeClass[pkg.state]}">v{getVersion(pkg)}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
export let pkg: GUIPackage | null = null;
|
export let pkg: GUIPackage | null = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="container relative h-full">
|
<div class="container relative h-full" data-testid={`install-badge-${pkg?.slug}`}>
|
||||||
<div class="content flex items-center justify-center gap-2 p-2">
|
<div class="content flex items-center justify-center gap-2 p-2">
|
||||||
<i class="icon-check-circle-o flex text-sm text-[#00ffd0]" />
|
<i class="icon-check-circle-o flex text-sm text-[#00ffd0]" />
|
||||||
<div class="text-xs">INSTALLED</div>
|
<div class="text-xs">INSTALLED</div>
|
||||||
|
|
|
@ -226,23 +226,23 @@ const installPkg = async (pkg: GUIPackage, version?: string) => {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
updatePackage(pkg.full_name, { install_progress_percentage: 0.01 });
|
updatePackage(pkg.full_name, { install_progress_percentage: 0.01 });
|
||||||
|
|
||||||
await installPackage(pkg, versionToInstall);
|
await installPackage(pkg, versionToInstall);
|
||||||
trackInstall(pkg.full_name);
|
trackInstall(pkg.full_name);
|
||||||
|
|
||||||
await refreshSinglePackage(pkg.full_name);
|
await refreshSinglePackage(pkg.full_name);
|
||||||
|
if (pkg.state === PackageStates.AVAILABLE) {
|
||||||
notificationStore.add({
|
updatePackage(pkg.full_name, {
|
||||||
message: `Package ${pkg.full_name} v${versionToInstall} has been installed.`
|
displayState: { kind: "INSTALLED_SUCCESSFULLY", version: versionToInstall }
|
||||||
});
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
log.error(error);
|
log.error(error);
|
||||||
let message = "Unknown Error";
|
let message = "Unknown Error";
|
||||||
if (error instanceof Error) message = error.message;
|
if (error instanceof Error) message = error.message;
|
||||||
trackInstallFailed(pkg.full_name, message || "unknown");
|
trackInstallFailed(pkg.full_name, message || "unknown");
|
||||||
|
updatePackage(pkg.full_name, {
|
||||||
notificationStore.add({
|
displayState: { kind: "INSTALLATION_ERROR", errorMessage: message, version: versionToInstall }
|
||||||
message: `Package ${pkg.full_name} v${versionToInstall} failed to install.`,
|
|
||||||
type: NotificationType.ERROR
|
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
updatePackage(pkg.full_name, { install_progress_percentage: 100 });
|
updatePackage(pkg.full_name, { install_progress_percentage: 100 });
|
||||||
|
@ -358,6 +358,12 @@ const setBadgeCountFromPkgs = (pkgs: Packages) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetPackageDisplayState = (pkg: GUIPackage) => {
|
||||||
|
updatePackage(pkg.full_name, {
|
||||||
|
displayState: null
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
packageList,
|
packageList,
|
||||||
search: searchPackages,
|
search: searchPackages,
|
||||||
|
@ -367,5 +373,6 @@ export default {
|
||||||
syncPackageData,
|
syncPackageData,
|
||||||
deletePkg,
|
deletePkg,
|
||||||
destroy,
|
destroy,
|
||||||
cachePkgImage
|
cachePkgImage,
|
||||||
|
resetPackageDisplayState
|
||||||
};
|
};
|
||||||
|
|
|
@ -13,6 +13,16 @@ export enum PackageStates {
|
||||||
UPDATING = "UPDATING"
|
UPDATING = "UPDATING"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PackageDisplayState is for showing temporary UI elements when a package is installed or updated. It is not persisted.
|
||||||
|
export interface PackageDisplayState {
|
||||||
|
// INSTALLED_SUCCESSFULLY -> The pacakge was installed successfully show some confetti!
|
||||||
|
// UPDATED_SUCCESSFULLY -> The package was updated successfully change the badge temporarily
|
||||||
|
// INSTALLATION_ERROR -> The package failed to install show an error overlay temporarily
|
||||||
|
kind: "INSTALLED_SUCCESSFULLY" | "UPDATED_SUCCESSFULLY" | "INSTALLATION_ERROR";
|
||||||
|
errorMessage?: string;
|
||||||
|
version: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type Packages = {
|
export type Packages = {
|
||||||
version: string;
|
version: string;
|
||||||
packages: { [full_name: string]: GUIPackage };
|
packages: { [full_name: string]: GUIPackage };
|
||||||
|
@ -25,6 +35,8 @@ export type GUIPackage = Package & {
|
||||||
install_progress_percentage?: number;
|
install_progress_percentage?: number;
|
||||||
isUninstalling?: boolean;
|
isUninstalling?: boolean;
|
||||||
cached_image_url?: string;
|
cached_image_url?: string;
|
||||||
|
|
||||||
|
displayState?: PackageDisplayState | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Course = {
|
export type Course = {
|
||||||
|
|
|
@ -14,6 +14,8 @@ describe("basic smoke test", () => {
|
||||||
it("install brewkit from the made by tea tab", async () => {
|
it("install brewkit from the made by tea tab", async () => {
|
||||||
const { screen } = utils!;
|
const { screen } = utils!;
|
||||||
|
|
||||||
|
const slug = "tea_xyz_brewkit";
|
||||||
|
|
||||||
// app launches to discover screen by default - make sure Stable Diffusion is there
|
// app launches to discover screen by default - make sure Stable Diffusion is there
|
||||||
await expect(await screen.findByText("Stable Diffusion web UI")).toExist();
|
await expect(await screen.findByText("Stable Diffusion web UI")).toExist();
|
||||||
|
|
||||||
|
@ -22,7 +24,7 @@ describe("basic smoke test", () => {
|
||||||
btn.click();
|
btn.click();
|
||||||
|
|
||||||
// find the brewkit package
|
// find the brewkit package
|
||||||
const pkgCard = await utils.findPackageCardBySlug("tea_xyz_brewkit");
|
const pkgCard = await utils.findPackageCardBySlug(slug);
|
||||||
pkgCard.click();
|
pkgCard.click();
|
||||||
|
|
||||||
await utils.packageDetailsLoaded();
|
await utils.packageDetailsLoaded();
|
||||||
|
@ -30,14 +32,14 @@ describe("basic smoke test", () => {
|
||||||
// Be nice to devs running this over and over
|
// Be nice to devs running this over and over
|
||||||
await utils.uninstallPackageIfNeeded();
|
await utils.uninstallPackageIfNeeded();
|
||||||
|
|
||||||
await utils.installLatestVersion("tea_xyz_brewkit");
|
await utils.installLatestVersion(slug);
|
||||||
|
|
||||||
await utils.verifyAndCloseNotification(/^Package tea.xyz\/brewkit .* has been installed./);
|
await utils.verifyInstalledBadge(slug);
|
||||||
await expect(await screen.findByRole("button", { name: "OPEN IN TERMINAL" })).toExist();
|
await expect(await screen.findByRole("button", { name: "OPEN IN TERMINAL" })).toExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("search and install create-dmg", async () => {
|
it("search and install create-dmg", async () => {
|
||||||
const { screen, searchTerm } = utils!;
|
const { searchTerm } = utils!;
|
||||||
await searchTerm("create-dmg");
|
await searchTerm("create-dmg");
|
||||||
|
|
||||||
const packageFullname = "github.com/create-dmg/create-dmg";
|
const packageFullname = "github.com/create-dmg/create-dmg";
|
||||||
|
@ -49,24 +51,24 @@ describe("basic smoke test", () => {
|
||||||
await utils.packageDetailsLoaded();
|
await utils.packageDetailsLoaded();
|
||||||
await utils.uninstallPackageIfNeeded();
|
await utils.uninstallPackageIfNeeded();
|
||||||
|
|
||||||
await utils.installLatestVersion("github_com_create_dmg_create_dmg");
|
await utils.installLatestVersion(createDmgSlug);
|
||||||
|
await utils.verifyInstalledBadge(createDmgSlug);
|
||||||
await utils.verifyAndCloseNotification(
|
|
||||||
/^Package github.com\/create-dmg\/create-dmg .* has been installed./
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should be able to install specific version", async () => {
|
it("should be able to install specific version", async () => {
|
||||||
const { screen, searchTerm } = utils!;
|
const { screen, searchTerm } = utils!;
|
||||||
|
const slug = "gnu_org_grep";
|
||||||
|
|
||||||
await searchTerm("grep");
|
await searchTerm("grep");
|
||||||
const grepCard = await utils.findSearchPackageCardBySlug("gnu_org_grep");
|
const grepCard = await utils.findSearchPackageCardBySlug(slug);
|
||||||
await expect(grepCard).toExist();
|
await expect(grepCard).toExist();
|
||||||
grepCard.click();
|
grepCard.click();
|
||||||
|
|
||||||
await utils.uninstallPackageIfNeeded();
|
await utils.uninstallPackageIfNeeded();
|
||||||
await utils.installSpecificVersion("gnu_org_grep", "3.8.0");
|
await utils.installSpecificVersion(slug, "3.8.0");
|
||||||
|
|
||||||
await utils.verifyAndCloseNotification(/^Package gnu.org\/grep .* has been installed./);
|
// since we're installing an old version verify the badge says UPDATE
|
||||||
|
await utils.verifyInstalledBadge(slug, "UPDATE");
|
||||||
|
|
||||||
// Now test the update
|
// Now test the update
|
||||||
await utils.goHome();
|
await utils.goHome();
|
||||||
|
@ -81,6 +83,7 @@ describe("basic smoke test", () => {
|
||||||
await expect(updateBtn).toExist();
|
await expect(updateBtn).toExist();
|
||||||
updateBtn.click();
|
updateBtn.click();
|
||||||
|
|
||||||
await utils.verifyAndCloseNotification(/^Package gnu.org\/grep .* has been installed./);
|
// FIXME: This should test for the button saying "UPDATED", but that feature is not done yet so uncomment this when it is
|
||||||
|
//await utils.verifyInstalledBadge(slug, "UPDATED");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import * as testingLibraryWdio from "@testing-library/webdriverio";
|
import * as testingLibraryWdio from "@testing-library/webdriverio";
|
||||||
import { waitForNotExist } from "./waitutils.ts";
|
import { sleep, waitForNotExist } from "./waitutils.ts";
|
||||||
|
|
||||||
// work around for importing CommonJS module
|
// work around for importing CommonJS module
|
||||||
const { setupBrowser } = testingLibraryWdio;
|
const { setupBrowser } = testingLibraryWdio;
|
||||||
|
@ -13,11 +13,7 @@ export function setupUtils(browser: WebdriverIO.Browser) {
|
||||||
};
|
};
|
||||||
|
|
||||||
const findByTestId = async (testId: string) => {
|
const findByTestId = async (testId: string) => {
|
||||||
return (await screen.findByTestId(
|
return await screen.findByTestId(testId, {}, { interval: 1000, timeout: 10000 });
|
||||||
testId,
|
|
||||||
{},
|
|
||||||
{ interval: 1000, timeout: 10000 }
|
|
||||||
)) as unknown as HTMLElement;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -93,18 +89,25 @@ export function setupUtils(browser: WebdriverIO.Browser) {
|
||||||
await searchInput.setValue(term);
|
await searchInput.setValue(term);
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeNotification = async () => {
|
const verifyInstalledBadge = async (
|
||||||
console.log("Closing notification");
|
slug: string,
|
||||||
const closeNotificationBtn = await findByTestId("close-notification");
|
state: "INSTALLED" | "UPDATE" = "INSTALLED"
|
||||||
await expect(closeNotificationBtn).toExist();
|
) => {
|
||||||
closeNotificationBtn.click();
|
// wait 30 seconds for the badge to show up
|
||||||
await waitForNotExist(() => screen.queryByTestId("close-notification"));
|
for (let i = 0; i < 30; i++) {
|
||||||
};
|
const badge = await findByTestId(`install-badge-${slug}`);
|
||||||
|
expect(badge).toExist();
|
||||||
|
|
||||||
// verify a notification matching the regex is shown and then close it
|
const badgeText = (await badge.getText()).replace(/\n/g, " ");
|
||||||
const verifyAndCloseNotification = async (regex: RegExp) => {
|
console.log(`verifying installed badge for ${slug}: ${badgeText}`);
|
||||||
await expect(await screen.findByText(regex, {}, { timeout: 30000, interval: 1000 })).toExist();
|
|
||||||
await closeNotification();
|
if (badgeText.includes(state)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await sleep(1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`Failed to find installed badge for ${slug}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -119,7 +122,6 @@ export function setupUtils(browser: WebdriverIO.Browser) {
|
||||||
uninstallPackageIfNeeded,
|
uninstallPackageIfNeeded,
|
||||||
installLatestVersion,
|
installLatestVersion,
|
||||||
installSpecificVersion,
|
installSpecificVersion,
|
||||||
closeNotification,
|
verifyInstalledBadge
|
||||||
verifyAndCloseNotification
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
|
@ -68,4 +68,9 @@
|
||||||
<glyph glyph-name="link" unicode="6" d="M384 384l-69 0c24-16 47-45 54-64l15 0c32 0 64-32 64-64 0-32-33-64-64-64l-96 0c-32 0-64 32-64 64 0 11 3 22 9 32l-69 0c-2-11-4-21-4-32 0-64 64-128 128-128 64 0 32 0 96 0 64 0 128 64 128 128 0 64-64 128-128 128z m-240-192l-15 0c-33 0-64 32-64 64 0 32 32 64 64 64l96 0c31 0 64-32 64-64 0-12-4-23-9-32l68 0c3 10 4 21 4 32 0 64-63 128-127 128-64 0-33 0-97 0-64 0-128-64-128-128 0-64 64-128 128-128l69 0c-24 16-46 44-53 64z"/>
|
<glyph glyph-name="link" unicode="6" d="M384 384l-69 0c24-16 47-45 54-64l15 0c32 0 64-32 64-64 0-32-33-64-64-64l-96 0c-32 0-64 32-64 64 0 11 3 22 9 32l-69 0c-2-11-4-21-4-32 0-64 64-128 128-128 64 0 32 0 96 0 64 0 128 64 128 128 0 64-64 128-128 128z m-240-192l-15 0c-33 0-64 32-64 64 0 32 32 64 64 64l96 0c31 0 64-32 64-64 0-12-4-23-9-32l68 0c3 10 4 21 4 32 0 64-63 128-127 128-64 0-33 0-97 0-64 0-128-64-128-128 0-64 64-128 128-128l69 0c-24 16-46 44-53 64z"/>
|
||||||
<glyph glyph-name="pencil" unicode="7" d="M140 73l26 26-67 67-26-26 0-30 37 0 0-37z m150 265c0 4-2 7-7 7-1 0-3-1-4-2l-155-155c-2-2-2-3-2-5 0-4 2-6 6-6 2 0 4 0 5 2l155 154c1 2 2 3 2 5z m-16 55l119-119-238-237-118 0 0 118z m195-27c0-10-3-19-10-26l-48-47-118 118 47 48c7 7 15 10 26 10 10 0 18-3 26-10l67-67c7-8 10-16 10-26z"/>
|
<glyph glyph-name="pencil" unicode="7" d="M140 73l26 26-67 67-26-26 0-30 37 0 0-37z m150 265c0 4-2 7-7 7-1 0-3-1-4-2l-155-155c-2-2-2-3-2-5 0-4 2-6 6-6 2 0 4 0 5 2l155 154c1 2 2 3 2 5z m-16 55l119-119-238-237-118 0 0 118z m195-27c0-10-3-19-10-26l-48-47-118 118 47 48c7 7 15 10 26 10 10 0 18-3 26-10l67-67c7-8 10-16 10-26z"/>
|
||||||
<glyph glyph-name="share-1" unicode="8" d="M349 419l-33-33-37 37 0-260-46 0 0 260-37-37-33 33 93 93z m93-116l0-256c0-26-21-47-46-47l-280 0c-25 0-46 21-46 47l0 256c0 25 21 46 46 46l70 0 0-46-70 0 0-256 280 0 0 256-70 0 0 46 70 0c25 0 46-21 46-46z"/>
|
<glyph glyph-name="share-1" unicode="8" d="M349 419l-33-33-37 37 0-260-46 0 0 260-37-37-33 33 93 93z m93-116l0-256c0-26-21-47-46-47l-280 0c-25 0-46 21-46 47l0 256c0 25 21 46 46 46l70 0 0-46-70 0 0-256 280 0 0 256-70 0 0 46 70 0c25 0 46-21 46-46z"/>
|
||||||
|
<glyph glyph-name="chevron-right" unicode="9" d="M389 261l-212-212c-3-3-7-5-12-5-5 0-10 2-13 5l-48 48c-3 3-5 8-5 13 0 5 2 9 5 13l152 151-152 152c-3 4-5 8-5 13 0 5 2 9 5 13l48 47c3 4 8 6 13 6 5 0 9-2 12-6l212-212c4-3 6-8 6-13 0-5-2-9-6-13z"/>
|
||||||
|
<glyph glyph-name="right-open-mini" unicode="!" d="M195 169c0 0 81 87 81 87 0 0-81 88-81 88-9 9-9 17 0 25 9 9 17 9 24 0 0 0 99-100 99-100 8-9 8-17 0-25 0 0-99-100-99-100-7-8-15-8-24 0-9 8-9 16 0 25"/>
|
||||||
|
<glyph glyph-name="facebook" unicode=""" d="M384 509l0-76-45 0c-17 0-28-3-33-10-6-7-9-17-9-31l0-54 84 0-11-85-73 0 0-216-87 0 0 216-73 0 0 85 73 0 0 62c0 36 10 63 29 83 20 19 47 29 80 29 28 0 49-1 65-3z"/>
|
||||||
|
<glyph glyph-name="linkedin" unicode="#" d="M136 333l0-283-94 0 0 283z m6 88c0-14-4-26-14-35-10-9-23-14-39-14l0 0c-16 0-29 5-38 14-10 9-14 21-14 35 0 14 4 26 14 35 10 9 23 14 39 14 16 0 28-5 38-14 9-9 14-21 14-35z m333-208l0-163-94 0 0 152c0 20-3 35-11 47-8 11-20 17-36 17-12 0-22-4-30-10-9-7-15-15-19-25-2-5-3-13-3-23l0-158-94 0c1 76 1 138 1 185 0 47 0 76 0 85l-1 13 94 0 0-41 0 0c4 6 7 12 11 16 4 5 10 10 17 15 6 5 15 10 24 13 10 3 21 4 33 4 33 0 59-11 79-32 20-22 29-54 29-95z"/>
|
||||||
|
<glyph glyph-name="exlamation-outline" unicode="$" d="M256 0c-142 0-256 114-256 256 0 142 114 256 256 256 142 0 256-114 256-256 0-142-114-256-256-256z m0 482c-124 0-226-102-226-226 0-124 102-226 226-226 124 0 226 102 226 226 0 124-102 226-226 226z m-32-98l64 0 0-160-64 0z m0-191l64 0 0-63-64 0z"/>
|
||||||
</font></defs></svg>
|
</font></defs></svg>
|
||||||
|
|
Before Width: | Height: | Size: 30 KiB After Width: | Height: | Size: 32 KiB |
Binary file not shown.
Binary file not shown.
|
@ -220,3 +220,18 @@
|
||||||
.icon-share-1:before {
|
.icon-share-1:before {
|
||||||
content: "\38";
|
content: "\38";
|
||||||
}
|
}
|
||||||
|
.icon-chevron-right:before {
|
||||||
|
content: "\39";
|
||||||
|
}
|
||||||
|
.icon-right-open-mini:before {
|
||||||
|
content: "\21";
|
||||||
|
}
|
||||||
|
.icon-facebook:before {
|
||||||
|
content: "\22";
|
||||||
|
}
|
||||||
|
.icon-linkedin:before {
|
||||||
|
content: "\23";
|
||||||
|
}
|
||||||
|
.icon-exlamation-outline:before {
|
||||||
|
content: "\24";
|
||||||
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@ importers:
|
||||||
'@testing-library/svelte': ^3.2.2
|
'@testing-library/svelte': ^3.2.2
|
||||||
'@testing-library/webdriverio': ^3.2.1
|
'@testing-library/webdriverio': ^3.2.1
|
||||||
'@types/bcryptjs': ^2.4.2
|
'@types/bcryptjs': ^2.4.2
|
||||||
|
'@types/canvas-confetti': ^1.6.0
|
||||||
'@types/electron': ^1.6.10
|
'@types/electron': ^1.6.10
|
||||||
'@types/js-yaml': ^4.0.5
|
'@types/js-yaml': ^4.0.5
|
||||||
'@types/mixpanel-browser': ^2.38.1
|
'@types/mixpanel-browser': ^2.38.1
|
||||||
|
@ -49,6 +50,7 @@ importers:
|
||||||
axios: ^1.3.2
|
axios: ^1.3.2
|
||||||
bcryptjs: ^2.4.3
|
bcryptjs: ^2.4.3
|
||||||
buffer: ^6.0.3
|
buffer: ^6.0.3
|
||||||
|
canvas-confetti: ^1.6.0
|
||||||
chokidar: ^3.5.3
|
chokidar: ^3.5.3
|
||||||
concurrently: ^7.6.0
|
concurrently: ^7.6.0
|
||||||
cross-env: ^7.0.3
|
cross-env: ^7.0.3
|
||||||
|
@ -113,6 +115,7 @@ importers:
|
||||||
axios: 1.4.0
|
axios: 1.4.0
|
||||||
bcryptjs: 2.4.3
|
bcryptjs: 2.4.3
|
||||||
buffer: 6.0.3
|
buffer: 6.0.3
|
||||||
|
canvas-confetti: 1.6.0
|
||||||
chokidar: 3.5.3
|
chokidar: 3.5.3
|
||||||
custom-electron-titlebar: 4.2.0-beta.0_electron@22.1.0
|
custom-electron-titlebar: 4.2.0-beta.0_electron@22.1.0
|
||||||
dayjs: 1.11.7
|
dayjs: 1.11.7
|
||||||
|
@ -153,6 +156,7 @@ importers:
|
||||||
'@testing-library/svelte': 3.2.2_svelte@3.59.1
|
'@testing-library/svelte': 3.2.2_svelte@3.59.1
|
||||||
'@testing-library/webdriverio': 3.2.1
|
'@testing-library/webdriverio': 3.2.1
|
||||||
'@types/bcryptjs': 2.4.2
|
'@types/bcryptjs': 2.4.2
|
||||||
|
'@types/canvas-confetti': 1.6.0
|
||||||
'@types/js-yaml': 4.0.5
|
'@types/js-yaml': 4.0.5
|
||||||
'@types/mixpanel-browser': 2.38.1
|
'@types/mixpanel-browser': 2.38.1
|
||||||
'@types/testing-library__jest-dom': 5.14.5
|
'@types/testing-library__jest-dom': 5.14.5
|
||||||
|
@ -3766,6 +3770,10 @@ packages:
|
||||||
'@types/node': 20.1.1
|
'@types/node': 20.1.1
|
||||||
'@types/responselike': 1.0.0
|
'@types/responselike': 1.0.0
|
||||||
|
|
||||||
|
/@types/canvas-confetti/1.6.0:
|
||||||
|
resolution: {integrity: sha512-Yq6rIccwcco0TLD5SMUrIM7Fk7Fe/C0jmNRxJJCLtAF6gebDkPuUjK5EHedxecm69Pi/aA+It39Ux4OHmFhjRw==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/chai-subset/1.3.3:
|
/@types/chai-subset/1.3.3:
|
||||||
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
|
resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -5314,6 +5322,10 @@ packages:
|
||||||
/caniuse-lite/1.0.30001486:
|
/caniuse-lite/1.0.30001486:
|
||||||
resolution: {integrity: sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==}
|
resolution: {integrity: sha512-uv7/gXuHi10Whlj0pp5q/tsK/32J2QSqVRKQhs2j8VsDCjgyruAh/eEXHF822VqO9yT6iZKw3nRwZRSPBE9OQg==}
|
||||||
|
|
||||||
|
/canvas-confetti/1.6.0:
|
||||||
|
resolution: {integrity: sha512-ej+w/m8Jzpv9Z7W7uJZer14Ke8P2ogsjg4ZMGIuq4iqUOqY2Jq8BNW42iGmNfRwREaaEfFIczLuZZiEVSYNHAA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/catharsis/0.9.0:
|
/catharsis/0.9.0:
|
||||||
resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==}
|
resolution: {integrity: sha512-prMTQVpcns/tzFgFVkVp6ak6RykZyWb3gu8ckUpd6YkTlacOd3DXGJjIpD4Q6zJirizvaiAjSSHlOsA+6sNh2A==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
|
|
Loading…
Reference in a new issue