Skip to content

Commit

Permalink
Feature: Page Transition and Component Animation (#9)
Browse files Browse the repository at this point in the history
* feat: add view transition for page transition, add install btns animation

* feat(desktop): add extension store logo cross-page transition with gasp Flip
  • Loading branch information
HuakunShen authored Nov 4, 2024
1 parent ad83e89 commit 11cc796
Show file tree
Hide file tree
Showing 21 changed files with 252 additions and 70 deletions.
2 changes: 2 additions & 0 deletions apps/desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
},
"license": "MIT",
"dependencies": {
"@formkit/auto-animate": "^0.8.2",
"@kksh/extension": "workspace:*",
"@kksh/supabase": "workspace:*",
"@kksh/ui": "workspace:*",
Expand All @@ -22,6 +23,7 @@
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-shell": "^2",
"bits-ui": "1.0.0-next.36",
"gsap": "^3.12.5",
"lucide-svelte": "^0.454.0",
"lz-string": "^1.5.0",
"mode-watcher": "^0.4.1",
Expand Down
34 changes: 34 additions & 0 deletions apps/desktop/src/lib/components/dance/dance-transition.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<script lang="ts">
import { Layouts } from "@kksh/ui"
import { cn } from "@kksh/ui/utils"
import { onMount } from "svelte"
import { fade } from "svelte/transition"
import Dance from "./dance.svelte"
let {
duration = 400,
class: className,
delay = 0,
show = $bindable(true),
autoHide = true
}: {
duration?: number
class?: string
delay?: number
show?: boolean
autoHide?: boolean
} = $props()
onMount(() => {
setTimeout(() => {
if (autoHide) show = false
}, delay ?? 0)
})
</script>

{#if show}
<div out:fade={{ duration, delay }}>
<Layouts.Center class={cn("bg-background absolute h-screen w-screen", className)} hidden={true}>
<Dance />
</Layouts.Center>
</div>
{/if}
2 changes: 2 additions & 0 deletions apps/desktop/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
updateTheme,
type ThemeConfig
} from "@kksh/svelte5"
import { ViewTransition } from "@kksh/ui"
import type { UnlistenFn } from "@tauri-apps/api/event"
import { attachConsole } from "@tauri-apps/plugin-log"
import { onDestroy, onMount } from "svelte"
Expand All @@ -32,6 +33,7 @@
})
</script>

<ViewTransition />
<ModeWatcher />
<Toaster richColors />
<AppContext {appConfig} {appState}>
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/dance/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
<script lang="ts">
import Dance from "@/components/dance/dance.svelte"
import { goBackOnEscape } from "@/utils/key"
import { goBack } from "@/utils/route"
import { Button } from "@kksh/svelte5"
import { Layouts } from "@kksh/ui"
import Dance from "$lib/components/dance.svelte"
import ArrowLeft from "svelte-radix/ArrowLeft.svelte"
</script>

Expand Down
31 changes: 31 additions & 0 deletions apps/desktop/src/routes/extension/store/+layout.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<script lang="ts">
import { Constants } from "@kksh/ui"
import { afterNavigate, beforeNavigate } from "$app/navigation"
import { gsap } from "gsap"
import { Flip } from "gsap/Flip"
gsap.registerPlugin(Flip)
let flipState: Flip.FlipState
beforeNavigate(() => {
flipState = Flip.getState(`.${Constants.CLASSNAMES.EXT_LOGO}`)
})
afterNavigate(() => {
if (!flipState) {
return
}
Flip.from(flipState, {
targets: ".kk-ext-logo",
duration: 0.5,
absolute: true,
scale: true,
ease: "ease-out"
})
})
const { children } = $props()
</script>

{@render children?.()}
2 changes: 1 addition & 1 deletion apps/desktop/src/routes/extension/store/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@
}
async function onExtItemInstall(ext: SBExt) {
console.log("onExtItemInstall", ext)
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
if (res.error)
return toast.error("Fail to get latest extension", {
Expand Down Expand Up @@ -77,5 +76,6 @@
{onExtItemUpgrade}
{onExtItemInstall}
{isUpgradable}
bind:searchTerm={$appState.searchTerm}
onGoBack={goBack}
/>
1 change: 0 additions & 1 deletion apps/desktop/src/routes/extension/store/+page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ export const load: PageLoad = async (): Promise<{
})
)
)
console.log(get(upgradableExpsMap))

return {
storeExtList,
Expand Down
67 changes: 52 additions & 15 deletions apps/desktop/src/routes/extension/store/[identifier]/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,27 +1,55 @@
<script lang="ts">
import { getExtensionsFolder } from "@/constants.js"
import { appConfig } from "@/stores/appConfig.js"
import { extensions, installedStoreExts } from "@/stores/extensions.js"
import { supabase, supabaseAPI, supabaseExtensionsStorage } from "@/supabase"
import { goBackOnEscape } from "@/utils/key"
import { supabaseAPI } from "@/supabase"
import { goBack } from "@/utils/route.js"
import { isExtPathInDev } from "@kksh/extension"
import { installTarballUrl } from "@kksh/extension/install"
import { Button } from "@kksh/svelte5"
import { StoreExtDetail } from "@kksh/ui/extension"
import * as path from "@tauri-apps/api/path"
import { greaterThan, parse as parseSemver } from "@std/semver"
import { error } from "@tauri-apps/plugin-log"
import { ArrowLeftIcon } from "lucide-svelte"
import { onMount } from "svelte"
import { toast } from "svelte-sonner"
import { get, derived as storeDerived } from "svelte/store"
import * as v from "valibot"
const { data } = $props()
let { ext, manifest } = data
const installedExt = storeDerived(installedStoreExts, ($e) => {
return $e.find((e) => e.kunkun.identifier === ext.identifier)
})
let btnLoading = $state(false)
const isUpgradable = $derived(
$installedExt
? greaterThan(parseSemver(ext.version), parseSemver($installedExt.version))
: false
)
$effect(() => {
console.log("isUpgradable", isUpgradable)
if (isUpgradable) {
showBtn.upgrade = true
showBtn.install = false
showBtn.uninstall = true
}
})
onMount(() => {
showBtn = {
install: !installedExt,
upgrade: isUpgradable,
uninstall: !!installedExt
}
})
let loading = $state({
install: false,
uninstall: false,
upgrade: false
})
let showBtn = $state({
install: false,
upgrade: false,
uninstall: false
})
let imageDialogOpen = $state(false)
let delayedImageDialogOpen = $state(false)
$effect(() => {
Expand All @@ -36,7 +64,7 @@
)
async function onInstallSelected() {
btnLoading = true
loading.install = true
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
const installDir = await getExtensionsFolder()
return extensions
Expand All @@ -52,12 +80,14 @@
toast.error("Fail to install tarball", { description: err })
})
.finally(() => {
btnLoading = false
loading.install = false
showBtn.install = false
showBtn.uninstall = true
})
}
function onUpgradeSelected() {
btnLoading = true
loading.upgrade = true
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
return extensions
.upgradeStoreExtension(ext.identifier, tarballUrl)
Expand All @@ -68,12 +98,16 @@
toast.error("Fail to upgrade extension", { description: err })
})
.finally(() => {
btnLoading = false
setTimeout(() => {
loading.upgrade = false
showBtn.upgrade = false
showBtn.uninstall = true
}, 2000)
})
}
function onUninstallSelected() {
btnLoading = true
loading.uninstall = true
return extensions
.uninstallStoreExtensionByIdentifier(ext.identifier)
.then((uninstalledExt) => {
Expand All @@ -84,7 +118,9 @@
error(`Fail to uninstall store extension (${ext.identifier}): ${err}`)
})
.finally(() => {
btnLoading = false
loading.uninstall = false
showBtn.uninstall = false
showBtn.install = true
})
}
Expand All @@ -111,7 +147,8 @@
{manifest}
installedExt={$installedExt}
{demoImages}
bind:btnLoading
{showBtn}
{loading}
{onInstallSelected}
{onUpgradeSelected}
{onUninstallSelected}
Expand Down
15 changes: 6 additions & 9 deletions apps/desktop/src/routes/extension/ui-iframe/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import Dance from "@/components/dance.svelte"
import DanceTransition from "@/components/dance/dance-transition.svelte"
import Dance from "@/components/dance/dance.svelte"
import { appConfig, winExtMap } from "@/stores"
import { goBackOnEscape } from "@/utils/key"
import { goHome } from "@/utils/route"
Expand Down Expand Up @@ -205,16 +206,12 @@
{/if}

<main class="h-screen">
{#if !uiControl.iframeLoaded}
<div class="bg-background absolute h-screen w-screen" out:fade>
<Layouts.Center class="h-full w-full" hidden={true}>
<Dance />
</Layouts.Center>
</div>
{/if}
<DanceTransition delay={300} autoHide={false} show={!uiControl.iframeLoaded} />
<iframe
bind:this={iframeRef}
class="h-full"
class={cn("h-full", {
hidden: !uiControl.iframeLoaded
})}
onload={onIframeLoaded}
width="100%"
height="100%"
Expand Down
6 changes: 4 additions & 2 deletions packages/ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@
"lint": "eslint ."
},
"devDependencies": {
"@types/bun": "latest",
"@kksh/api": "workspace:*",
"@types/bun": "latest",
"bits-ui": "1.0.0-next.36",
"clsx": "^2.1.1",
"lucide-svelte": "^0.454.0",
Expand All @@ -50,6 +50,8 @@
"tailwindcss-animate": "^1.0.7"
},
"dependencies": {
"@std/semver": "npm:@jsr/std__semver@^1.0.3"
"@formkit/auto-animate": "^0.8.2",
"@std/semver": "npm:@jsr/std__semver@^1.0.3",
"gsap": "^3.12.5"
}
}
18 changes: 11 additions & 7 deletions packages/ui/src/components/common/IconMultiplexer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,25 @@
import { Button } from "@kksh/svelte5"
import { cn } from "@kksh/ui/utils"
const { icon, class: className }: { icon: TIcon; class?: string } = $props()
const {
icon,
class: className,
...restProps
}: { icon: TIcon; class?: string; [key: string]: any } = $props()
</script>

{#if icon.type === IconEnum.RemoteUrl}
<img loading="lazy" class={cn("", className)} src={icon.value} alt="" />
<img loading="lazy" class={cn("", className)} src={icon.value} alt="" {...restProps} />
{:else if icon.type === IconEnum.Iconify}
<Icon icon={icon.value} class={cn("", className)} />
<Icon icon={icon.value} class={cn("", className)} {...restProps} />
{:else if icon.type === IconEnum.Base64PNG}
<img loading="lazy" src="data:image/png;base64, {icon.value}" alt="" />
<img loading="lazy" src="data:image/png;base64, {icon.value}" alt="" {...restProps} />
{:else if icon.type === IconEnum.Text}
<Button class={cn("shrink-0 text-center", className)} size="icon">
<Button class={cn("shrink-0 text-center", className)} size="icon" {...restProps}>
{icon.value}
</Button>
{:else if icon.type === IconEnum.Svg}
<span>{@html icon.value}</span>
<span {...restProps}>{@html icon.value}</span>
{:else}
<Icon icon="mingcute:appstore-fill" class={cn("", className)} />
<Icon icon="mingcute:appstore-fill" class={cn("", className)} {...restProps} />
{/if}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
</script>

<CommandPrimitive.Group
class={cn("text-foreground overflow-hidden p-1", className)}
class={cn("text-foreground overflow-hidden p-1 select-none", className)}
bind:ref
{...restProps}
>
Expand Down
Loading

0 comments on commit 11cc796

Please sign in to comment.