mirror of
https://github.com/kunkunsh/kunkun.git
synced 2025-04-20 05:29:17 +00:00
Feature: Page Transition and Component Animation (#9)
* feat: add view transition for page transition, add install btns animation * feat(desktop): add extension store logo cross-page transition with gasp Flip
This commit is contained in:
parent
ad83e89e52
commit
11cc79627d
@ -14,6 +14,7 @@
|
|||||||
},
|
},
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@formkit/auto-animate": "^0.8.2",
|
||||||
"@kksh/extension": "workspace:*",
|
"@kksh/extension": "workspace:*",
|
||||||
"@kksh/supabase": "workspace:*",
|
"@kksh/supabase": "workspace:*",
|
||||||
"@kksh/ui": "workspace:*",
|
"@kksh/ui": "workspace:*",
|
||||||
@ -22,6 +23,7 @@
|
|||||||
"@tauri-apps/api": "^2",
|
"@tauri-apps/api": "^2",
|
||||||
"@tauri-apps/plugin-shell": "^2",
|
"@tauri-apps/plugin-shell": "^2",
|
||||||
"bits-ui": "1.0.0-next.36",
|
"bits-ui": "1.0.0-next.36",
|
||||||
|
"gsap": "^3.12.5",
|
||||||
"lucide-svelte": "^0.454.0",
|
"lucide-svelte": "^0.454.0",
|
||||||
"lz-string": "^1.5.0",
|
"lz-string": "^1.5.0",
|
||||||
"mode-watcher": "^0.4.1",
|
"mode-watcher": "^0.4.1",
|
||||||
|
@ -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}
|
@ -11,6 +11,7 @@
|
|||||||
updateTheme,
|
updateTheme,
|
||||||
type ThemeConfig
|
type ThemeConfig
|
||||||
} from "@kksh/svelte5"
|
} from "@kksh/svelte5"
|
||||||
|
import { ViewTransition } from "@kksh/ui"
|
||||||
import type { UnlistenFn } from "@tauri-apps/api/event"
|
import type { UnlistenFn } from "@tauri-apps/api/event"
|
||||||
import { attachConsole } from "@tauri-apps/plugin-log"
|
import { attachConsole } from "@tauri-apps/plugin-log"
|
||||||
import { onDestroy, onMount } from "svelte"
|
import { onDestroy, onMount } from "svelte"
|
||||||
@ -32,6 +33,7 @@
|
|||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<ViewTransition />
|
||||||
<ModeWatcher />
|
<ModeWatcher />
|
||||||
<Toaster richColors />
|
<Toaster richColors />
|
||||||
<AppContext {appConfig} {appState}>
|
<AppContext {appConfig} {appState}>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import Dance from "@/components/dance/dance.svelte"
|
||||||
import { goBackOnEscape } from "@/utils/key"
|
import { goBackOnEscape } from "@/utils/key"
|
||||||
import { goBack } from "@/utils/route"
|
import { goBack } from "@/utils/route"
|
||||||
import { Button } from "@kksh/svelte5"
|
import { Button } from "@kksh/svelte5"
|
||||||
import { Layouts } from "@kksh/ui"
|
import { Layouts } from "@kksh/ui"
|
||||||
import Dance from "$lib/components/dance.svelte"
|
|
||||||
import ArrowLeft from "svelte-radix/ArrowLeft.svelte"
|
import ArrowLeft from "svelte-radix/ArrowLeft.svelte"
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
31
apps/desktop/src/routes/extension/store/+layout.svelte
Normal file
31
apps/desktop/src/routes/extension/store/+layout.svelte
Normal file
@ -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?.()}
|
@ -46,7 +46,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function onExtItemInstall(ext: SBExt) {
|
async function onExtItemInstall(ext: SBExt) {
|
||||||
console.log("onExtItemInstall", ext)
|
|
||||||
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
|
const res = await supabaseAPI.getLatestExtPublish(ext.identifier)
|
||||||
if (res.error)
|
if (res.error)
|
||||||
return toast.error("Fail to get latest extension", {
|
return toast.error("Fail to get latest extension", {
|
||||||
@ -77,5 +76,6 @@
|
|||||||
{onExtItemUpgrade}
|
{onExtItemUpgrade}
|
||||||
{onExtItemInstall}
|
{onExtItemInstall}
|
||||||
{isUpgradable}
|
{isUpgradable}
|
||||||
|
bind:searchTerm={$appState.searchTerm}
|
||||||
onGoBack={goBack}
|
onGoBack={goBack}
|
||||||
/>
|
/>
|
||||||
|
@ -33,7 +33,6 @@ export const load: PageLoad = async (): Promise<{
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
console.log(get(upgradableExpsMap))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
storeExtList,
|
storeExtList,
|
||||||
|
@ -1,27 +1,55 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { getExtensionsFolder } from "@/constants.js"
|
import { getExtensionsFolder } from "@/constants.js"
|
||||||
import { appConfig } from "@/stores/appConfig.js"
|
|
||||||
import { extensions, installedStoreExts } from "@/stores/extensions.js"
|
import { extensions, installedStoreExts } from "@/stores/extensions.js"
|
||||||
import { supabase, supabaseAPI, supabaseExtensionsStorage } from "@/supabase"
|
import { supabaseAPI } from "@/supabase"
|
||||||
import { goBackOnEscape } from "@/utils/key"
|
|
||||||
import { goBack } from "@/utils/route.js"
|
import { goBack } from "@/utils/route.js"
|
||||||
import { isExtPathInDev } from "@kksh/extension"
|
|
||||||
import { installTarballUrl } from "@kksh/extension/install"
|
|
||||||
import { Button } from "@kksh/svelte5"
|
import { Button } from "@kksh/svelte5"
|
||||||
import { StoreExtDetail } from "@kksh/ui/extension"
|
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 { error } from "@tauri-apps/plugin-log"
|
||||||
import { ArrowLeftIcon } from "lucide-svelte"
|
import { ArrowLeftIcon } from "lucide-svelte"
|
||||||
|
import { onMount } from "svelte"
|
||||||
import { toast } from "svelte-sonner"
|
import { toast } from "svelte-sonner"
|
||||||
import { get, derived as storeDerived } from "svelte/store"
|
import { get, derived as storeDerived } from "svelte/store"
|
||||||
import * as v from "valibot"
|
|
||||||
|
|
||||||
const { data } = $props()
|
const { data } = $props()
|
||||||
let { ext, manifest } = data
|
let { ext, manifest } = data
|
||||||
const installedExt = storeDerived(installedStoreExts, ($e) => {
|
const installedExt = storeDerived(installedStoreExts, ($e) => {
|
||||||
return $e.find((e) => e.kunkun.identifier === ext.identifier)
|
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 imageDialogOpen = $state(false)
|
||||||
let delayedImageDialogOpen = $state(false)
|
let delayedImageDialogOpen = $state(false)
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@ -36,7 +64,7 @@
|
|||||||
)
|
)
|
||||||
|
|
||||||
async function onInstallSelected() {
|
async function onInstallSelected() {
|
||||||
btnLoading = true
|
loading.install = true
|
||||||
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
|
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
|
||||||
const installDir = await getExtensionsFolder()
|
const installDir = await getExtensionsFolder()
|
||||||
return extensions
|
return extensions
|
||||||
@ -52,12 +80,14 @@
|
|||||||
toast.error("Fail to install tarball", { description: err })
|
toast.error("Fail to install tarball", { description: err })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
btnLoading = false
|
loading.install = false
|
||||||
|
showBtn.install = false
|
||||||
|
showBtn.uninstall = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUpgradeSelected() {
|
function onUpgradeSelected() {
|
||||||
btnLoading = true
|
loading.upgrade = true
|
||||||
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
|
const tarballUrl = supabaseAPI.translateExtensionFilePathToUrl(ext.tarball_path)
|
||||||
return extensions
|
return extensions
|
||||||
.upgradeStoreExtension(ext.identifier, tarballUrl)
|
.upgradeStoreExtension(ext.identifier, tarballUrl)
|
||||||
@ -68,12 +98,16 @@
|
|||||||
toast.error("Fail to upgrade extension", { description: err })
|
toast.error("Fail to upgrade extension", { description: err })
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
btnLoading = false
|
setTimeout(() => {
|
||||||
|
loading.upgrade = false
|
||||||
|
showBtn.upgrade = false
|
||||||
|
showBtn.uninstall = true
|
||||||
|
}, 2000)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function onUninstallSelected() {
|
function onUninstallSelected() {
|
||||||
btnLoading = true
|
loading.uninstall = true
|
||||||
return extensions
|
return extensions
|
||||||
.uninstallStoreExtensionByIdentifier(ext.identifier)
|
.uninstallStoreExtensionByIdentifier(ext.identifier)
|
||||||
.then((uninstalledExt) => {
|
.then((uninstalledExt) => {
|
||||||
@ -84,7 +118,9 @@
|
|||||||
error(`Fail to uninstall store extension (${ext.identifier}): ${err}`)
|
error(`Fail to uninstall store extension (${ext.identifier}): ${err}`)
|
||||||
})
|
})
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
btnLoading = false
|
loading.uninstall = false
|
||||||
|
showBtn.uninstall = false
|
||||||
|
showBtn.install = true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +147,8 @@
|
|||||||
{manifest}
|
{manifest}
|
||||||
installedExt={$installedExt}
|
installedExt={$installedExt}
|
||||||
{demoImages}
|
{demoImages}
|
||||||
bind:btnLoading
|
{showBtn}
|
||||||
|
{loading}
|
||||||
{onInstallSelected}
|
{onInstallSelected}
|
||||||
{onUpgradeSelected}
|
{onUpgradeSelected}
|
||||||
{onUninstallSelected}
|
{onUninstallSelected}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<script lang="ts">
|
<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 { appConfig, winExtMap } from "@/stores"
|
||||||
import { goBackOnEscape } from "@/utils/key"
|
import { goBackOnEscape } from "@/utils/key"
|
||||||
import { goHome } from "@/utils/route"
|
import { goHome } from "@/utils/route"
|
||||||
@ -205,16 +206,12 @@
|
|||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<main class="h-screen">
|
<main class="h-screen">
|
||||||
{#if !uiControl.iframeLoaded}
|
<DanceTransition delay={300} autoHide={false} show={!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}
|
|
||||||
<iframe
|
<iframe
|
||||||
bind:this={iframeRef}
|
bind:this={iframeRef}
|
||||||
class="h-full"
|
class={cn("h-full", {
|
||||||
|
hidden: !uiControl.iframeLoaded
|
||||||
|
})}
|
||||||
onload={onIframeLoaded}
|
onload={onIframeLoaded}
|
||||||
width="100%"
|
width="100%"
|
||||||
height="100%"
|
height="100%"
|
||||||
|
@ -34,8 +34,8 @@
|
|||||||
"lint": "eslint ."
|
"lint": "eslint ."
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest",
|
|
||||||
"@kksh/api": "workspace:*",
|
"@kksh/api": "workspace:*",
|
||||||
|
"@types/bun": "latest",
|
||||||
"bits-ui": "1.0.0-next.36",
|
"bits-ui": "1.0.0-next.36",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"lucide-svelte": "^0.454.0",
|
"lucide-svelte": "^0.454.0",
|
||||||
@ -50,6 +50,8 @@
|
|||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,21 +4,25 @@
|
|||||||
import { Button } from "@kksh/svelte5"
|
import { Button } from "@kksh/svelte5"
|
||||||
import { cn } from "@kksh/ui/utils"
|
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>
|
</script>
|
||||||
|
|
||||||
{#if icon.type === IconEnum.RemoteUrl}
|
{#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}
|
{: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}
|
{: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}
|
{: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}
|
{icon.value}
|
||||||
</Button>
|
</Button>
|
||||||
{:else if icon.type === IconEnum.Svg}
|
{:else if icon.type === IconEnum.Svg}
|
||||||
<span>{@html icon.value}</span>
|
<span {...restProps}>{@html icon.value}</span>
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="mingcute:appstore-fill" class={cn("", className)} />
|
<Icon icon="mingcute:appstore-fill" class={cn("", className)} {...restProps} />
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<CommandPrimitive.Group
|
<CommandPrimitive.Group
|
||||||
class={cn("text-foreground overflow-hidden p-1", className)}
|
class={cn("text-foreground overflow-hidden p-1 select-none", className)}
|
||||||
bind:ref
|
bind:ref
|
||||||
{...restProps}
|
{...restProps}
|
||||||
>
|
>
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
import { Icon as TIcon } from "@kksh/api/models"
|
import { Icon as TIcon } from "@kksh/api/models"
|
||||||
import { SBExt } from "@kksh/api/supabase"
|
import { SBExt } from "@kksh/api/supabase"
|
||||||
import { Button, Command } from "@kksh/svelte5"
|
import { Button, Command } from "@kksh/svelte5"
|
||||||
import { IconMultiplexer } from "@kksh/ui"
|
import { Constants, IconMultiplexer } from "@kksh/ui"
|
||||||
import { humanReadableNumber } from "@kksh/ui/utils"
|
import { cn, humanReadableNumber } from "@kksh/ui/utils"
|
||||||
import { greaterThan, parse as parseSemver } from "@std/semver"
|
import { greaterThan, parse as parseSemver } from "@std/semver"
|
||||||
import { CircleCheckBigIcon, MoveRightIcon } from "lucide-svelte"
|
import { CircleCheckBigIcon, MoveRightIcon } from "lucide-svelte"
|
||||||
import { parse } from "valibot"
|
import { parse } from "valibot"
|
||||||
@ -15,8 +15,10 @@
|
|||||||
onSelect,
|
onSelect,
|
||||||
onUpgrade,
|
onUpgrade,
|
||||||
onInstall,
|
onInstall,
|
||||||
isUpgradable
|
isUpgradable,
|
||||||
|
class: className
|
||||||
}: {
|
}: {
|
||||||
|
class?: string
|
||||||
ext: SBExt
|
ext: SBExt
|
||||||
installedVersion?: string
|
installedVersion?: string
|
||||||
onSelect: () => void
|
onSelect: () => void
|
||||||
@ -26,11 +28,17 @@
|
|||||||
} = $props()
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Command.Item class="flex items-center justify-between" {onSelect}>
|
<Command.Item class={cn("flex items-center justify-between", className)} {onSelect}>
|
||||||
<span class="flex items-center space-x-2">
|
<span class="flex items-center space-x-2">
|
||||||
<IconMultiplexer icon={parse(TIcon, ext.icon)} class="!h-6 !w-6 shrink-0" />
|
<span class="!h-6 !w-6">
|
||||||
|
<IconMultiplexer
|
||||||
|
icon={parse(TIcon, ext.icon)}
|
||||||
|
class={cn(Constants.CLASSNAMES.EXT_LOGO, "!h-6 !w-6 shrink-0")}
|
||||||
|
data-flip-id={`${Constants.CLASSNAMES.EXT_LOGO}-${ext.identifier}`}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
<span class="flex flex-col gap-0">
|
<span class="flex flex-col gap-0">
|
||||||
<div class="font-semibold">{ext.name}</div>
|
<div class="ext-name font-semibold">{ext.name}</div>
|
||||||
<small class="text-muted-foreground font-mono">{ext.short_description}</small>
|
<small class="text-muted-foreground font-mono">{ext.short_description}</small>
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@ -74,3 +82,9 @@
|
|||||||
<span class="w-4 text-center font-mono">{humanReadableNumber(ext.downloads)}</span>
|
<span class="w-4 text-center font-mono">{humanReadableNumber(ext.downloads)}</span>
|
||||||
</span>
|
</span>
|
||||||
</Command.Item>
|
</Command.Item>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* .ext-logo-image {
|
||||||
|
view-transition-name: var(--ext-logo-img);
|
||||||
|
} */
|
||||||
|
</style>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import autoAnimate from "@formkit/auto-animate"
|
||||||
import Icon from "@iconify/svelte"
|
import Icon from "@iconify/svelte"
|
||||||
import { ExtPackageJsonExtra, IconEnum, KunkunExtManifest } from "@kksh/api/models"
|
import { ExtPackageJsonExtra, IconEnum, KunkunExtManifest } from "@kksh/api/models"
|
||||||
import { type Tables } from "@kksh/api/supabase/types"
|
import { type Tables } from "@kksh/api/supabase/types"
|
||||||
import { Button, ScrollArea, Separator } from "@kksh/svelte5"
|
import { Button, ScrollArea, Separator } from "@kksh/svelte5"
|
||||||
import { IconMultiplexer } from "@kksh/ui"
|
import { Constants, IconMultiplexer } from "@kksh/ui"
|
||||||
import { greaterThan, parse as parseSemver } from "@std/semver"
|
import { cn } from "@kksh/ui/utils"
|
||||||
import { CircleCheckBigIcon, MoveRightIcon, Trash2Icon } from "lucide-svelte"
|
import { CircleCheckBigIcon, MoveRightIcon, Trash2Icon } from "lucide-svelte"
|
||||||
import DialogImageCarousel from "../common/DialogImageCarousel.svelte"
|
import DialogImageCarousel from "../common/DialogImageCarousel.svelte"
|
||||||
import PlatformsIcons from "../common/PlatformsIcons.svelte"
|
import PlatformsIcons from "../common/PlatformsIcons.svelte"
|
||||||
@ -20,7 +21,8 @@
|
|||||||
onInstallSelected,
|
onInstallSelected,
|
||||||
onUpgradeSelected,
|
onUpgradeSelected,
|
||||||
onUninstallSelected,
|
onUninstallSelected,
|
||||||
btnLoading = $bindable(false),
|
showBtn,
|
||||||
|
loading,
|
||||||
imageDialogOpen = $bindable(false)
|
imageDialogOpen = $bindable(false)
|
||||||
}: {
|
}: {
|
||||||
ext: Tables<"ext_publish">
|
ext: Tables<"ext_publish">
|
||||||
@ -32,7 +34,16 @@
|
|||||||
onUpgradeSelected?: () => void
|
onUpgradeSelected?: () => void
|
||||||
onUninstallSelected?: () => void
|
onUninstallSelected?: () => void
|
||||||
onEnterPressed?: () => void
|
onEnterPressed?: () => void
|
||||||
btnLoading: boolean
|
showBtn: {
|
||||||
|
upgrade: boolean
|
||||||
|
install: boolean
|
||||||
|
uninstall: boolean
|
||||||
|
}
|
||||||
|
loading: {
|
||||||
|
install: boolean
|
||||||
|
uninstall: boolean
|
||||||
|
upgrade: boolean
|
||||||
|
}
|
||||||
imageDialogOpen: boolean
|
imageDialogOpen: boolean
|
||||||
} = $props()
|
} = $props()
|
||||||
|
|
||||||
@ -57,12 +68,12 @@
|
|||||||
{#snippet upgradeBtn()}
|
{#snippet upgradeBtn()}
|
||||||
<Button
|
<Button
|
||||||
class="w-full bg-yellow-600 hover:bg-yellow-500"
|
class="w-full bg-yellow-600 hover:bg-yellow-500"
|
||||||
disabled={btnLoading}
|
disabled={loading.upgrade}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onclick={onUpgradeSelected}
|
onclick={onUpgradeSelected}
|
||||||
>
|
>
|
||||||
<span>Upgrade</span>
|
<span>Upgrade</span>
|
||||||
{#if btnLoading}
|
{#if loading.upgrade}
|
||||||
{@render spinLoader()}
|
{@render spinLoader()}
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="carbon:upgrade" class="inline h-5 w-5" />
|
<Icon icon="carbon:upgrade" class="inline h-5 w-5" />
|
||||||
@ -76,12 +87,12 @@
|
|||||||
{#snippet uninstallBtn()}
|
{#snippet uninstallBtn()}
|
||||||
<Button
|
<Button
|
||||||
class="w-full bg-red-600 hover:bg-red-500"
|
class="w-full bg-red-600 hover:bg-red-500"
|
||||||
disabled={btnLoading}
|
disabled={loading.uninstall}
|
||||||
variant="destructive"
|
variant="destructive"
|
||||||
onclick={onUninstallSelected}
|
onclick={onUninstallSelected}
|
||||||
>
|
>
|
||||||
<span>Uninstall</span>
|
<span>Uninstall</span>
|
||||||
{#if btnLoading}
|
{#if loading.uninstall}
|
||||||
{@render spinLoader()}
|
{@render spinLoader()}
|
||||||
{:else}
|
{:else}
|
||||||
<Trash2Icon class="h-5 w-5" />
|
<Trash2Icon class="h-5 w-5" />
|
||||||
@ -92,11 +103,11 @@
|
|||||||
{#snippet installBtn()}
|
{#snippet installBtn()}
|
||||||
<Button
|
<Button
|
||||||
class="w-full bg-green-700 text-white hover:bg-green-600"
|
class="w-full bg-green-700 text-white hover:bg-green-600"
|
||||||
disabled={btnLoading}
|
disabled={loading.install}
|
||||||
onclick={onInstallSelected}
|
onclick={onInstallSelected}
|
||||||
>
|
>
|
||||||
<span>Install</span>
|
<span>Install</span>
|
||||||
{#if btnLoading}
|
{#if loading.install}
|
||||||
{@render spinLoader()}
|
{@render spinLoader()}
|
||||||
{:else}
|
{:else}
|
||||||
<Icon icon="mi:enter" class="h-5 w-5" />
|
<Icon icon="mi:enter" class="h-5 w-5" />
|
||||||
@ -107,10 +118,16 @@
|
|||||||
<div data-tauri-drag-region class="h-14"></div>
|
<div data-tauri-drag-region class="h-14"></div>
|
||||||
<ScrollArea class="container pb-12">
|
<ScrollArea class="container pb-12">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<IconMultiplexer icon={manifest.icon} class="h-12 w-12" />
|
<span class="h-12 w-12">
|
||||||
|
<IconMultiplexer
|
||||||
|
icon={manifest.icon}
|
||||||
|
class={cn(Constants.CLASSNAMES.EXT_LOGO, "h-full w-full")}
|
||||||
|
data-flip-id={`${Constants.CLASSNAMES.EXT_LOGO}-${ext.identifier}`}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
<div>
|
<div>
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
<strong class="text-xl">{manifest?.name}</strong>
|
<strong class="ext-name text-xl">{manifest?.name}</strong>
|
||||||
{#if isInstalled}
|
{#if isInstalled}
|
||||||
<CircleCheckBigIcon class="ml-2 inline text-green-400" />
|
<CircleCheckBigIcon class="ml-2 inline text-green-400" />
|
||||||
{/if}
|
{/if}
|
||||||
@ -178,18 +195,14 @@
|
|||||||
</ul>
|
</ul>
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
|
|
||||||
<footer class="fixed bottom-0 mb-1 h-10 w-full px-2">
|
<footer class="fixed bottom-0 mb-1 flex h-10 w-full space-x-2 px-2" use:autoAnimate>
|
||||||
{#if isInstalled}
|
{#if showBtn.upgrade}
|
||||||
{@const isUpgradable = installedExt
|
|
||||||
? greaterThan(parseSemver(ext.version), parseSemver(installedExt.version))
|
|
||||||
: false}
|
|
||||||
{#if isUpgradable}
|
|
||||||
<div class="flex gap-2">
|
|
||||||
{@render upgradeBtn()}
|
{@render upgradeBtn()}
|
||||||
|
{/if}
|
||||||
|
{#if showBtn.uninstall}
|
||||||
{@render uninstallBtn()}
|
{@render uninstallBtn()}
|
||||||
</div>
|
{/if}
|
||||||
{:else}{@render uninstallBtn()}{/if}
|
{#if showBtn.install}
|
||||||
{:else}
|
|
||||||
{@render installBtn()}
|
{@render installBtn()}
|
||||||
{/if}
|
{/if}
|
||||||
</footer>
|
</footer>
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
import { SBExt } from "@kksh/api/supabase"
|
import { SBExt } from "@kksh/api/supabase"
|
||||||
import { Button, Command } from "@kksh/svelte5"
|
import { Button, Command } from "@kksh/svelte5"
|
||||||
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
import { CustomCommandInput, GlobalCommandPaletteFooter } from "@kksh/ui/main"
|
||||||
|
import { cn } from "@kksh/ui/utils"
|
||||||
|
import { afterNavigate, beforeNavigate } from "$app/navigation"
|
||||||
import { type Snippet } from "svelte"
|
import { type Snippet } from "svelte"
|
||||||
import ArrowLeft from "svelte-radix/ArrowLeft.svelte"
|
import ArrowLeft from "svelte-radix/ArrowLeft.svelte"
|
||||||
import type { Writable } from "svelte/store"
|
import type { Writable } from "svelte/store"
|
||||||
@ -16,7 +18,8 @@
|
|||||||
upgradableExpsMap,
|
upgradableExpsMap,
|
||||||
isUpgradable,
|
isUpgradable,
|
||||||
appState,
|
appState,
|
||||||
onGoBack
|
onGoBack,
|
||||||
|
searchTerm = $bindable("")
|
||||||
}: {
|
}: {
|
||||||
storeExtList: SBExt[]
|
storeExtList: SBExt[]
|
||||||
installedExtsMap: Record<string, string>
|
installedExtsMap: Record<string, string>
|
||||||
@ -27,6 +30,7 @@
|
|||||||
isUpgradable: (dbExt: SBExt, installedExtVersion: string) => boolean
|
isUpgradable: (dbExt: SBExt, installedExtVersion: string) => boolean
|
||||||
onGoBack?: () => void
|
onGoBack?: () => void
|
||||||
appState: Writable<{ searchTerm: string }>
|
appState: Writable<{ searchTerm: string }>
|
||||||
|
searchTerm: string
|
||||||
} = $props()
|
} = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -40,7 +44,7 @@
|
|||||||
autofocus
|
autofocus
|
||||||
placeholder="Type a command or search..."
|
placeholder="Type a command or search..."
|
||||||
leftSlot={leftSlot as Snippet}
|
leftSlot={leftSlot as Snippet}
|
||||||
bind:value={$appState.searchTerm}
|
bind:value={searchTerm}
|
||||||
/>
|
/>
|
||||||
<Command.List class="max-h-screen grow">
|
<Command.List class="max-h-screen grow">
|
||||||
<Command.Empty>No results found.</Command.Empty>
|
<Command.Empty>No results found.</Command.Empty>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
const { class: className }: { class?: string } = $props()
|
const { class: className }: { class?: string } = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<footer data-tauri-drag-region class={cn("h-12 border-t", className)}>
|
<footer data-tauri-drag-region class={cn("h-12 select-none border-t", className)}>
|
||||||
<Avatar.Root class="p-2">
|
<Avatar.Root class="p-2">
|
||||||
<Avatar.Image src="/favicon.png" alt="Kunkun Logo" class="select-none invert dark:invert-0" />
|
<Avatar.Image src="/favicon.png" alt="Kunkun Logo" class="select-none invert dark:invert-0" />
|
||||||
</Avatar.Root>
|
</Avatar.Root>
|
||||||
|
16
packages/ui/src/components/transition/view-transition.svelte
Normal file
16
packages/ui/src/components/transition/view-transition.svelte
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { onNavigate } from "$app/navigation"
|
||||||
|
|
||||||
|
onNavigate((navigation) => {
|
||||||
|
if (!document.startViewTransition) {
|
||||||
|
console.warn("View transition not supported")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
document.startViewTransition(async () => {
|
||||||
|
resolve()
|
||||||
|
await navigation.complete
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
3
packages/ui/src/constants.ts
Normal file
3
packages/ui/src/constants.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const CLASSNAMES = {
|
||||||
|
EXT_LOGO: "kk-ext-logo"
|
||||||
|
}
|
@ -7,3 +7,5 @@ export * as Custom from "./components/custom"
|
|||||||
export * as Main from "./components/main/index"
|
export * as Main from "./components/main/index"
|
||||||
export * as Extension from "./components/extension/index"
|
export * as Extension from "./components/extension/index"
|
||||||
export { default as GridAnimation } from "./components/animation/grid-animation.svelte"
|
export { default as GridAnimation } from "./components/animation/grid-animation.svelte"
|
||||||
|
export { default as ViewTransition } from "./components/transition/view-transition.svelte"
|
||||||
|
export * as Constants from "./constants"
|
||||||
|
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@ -114,6 +114,9 @@ importers:
|
|||||||
|
|
||||||
apps/desktop:
|
apps/desktop:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@formkit/auto-animate':
|
||||||
|
specifier: ^0.8.2
|
||||||
|
version: 0.8.2
|
||||||
'@kksh/extension':
|
'@kksh/extension':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/extension
|
version: link:../../packages/extension
|
||||||
@ -138,6 +141,9 @@ importers:
|
|||||||
bits-ui:
|
bits-ui:
|
||||||
specifier: 1.0.0-next.36
|
specifier: 1.0.0-next.36
|
||||||
version: 1.0.0-next.36(svelte@5.1.9)
|
version: 1.0.0-next.36(svelte@5.1.9)
|
||||||
|
gsap:
|
||||||
|
specifier: ^3.12.5
|
||||||
|
version: 3.12.5
|
||||||
lucide-svelte:
|
lucide-svelte:
|
||||||
specifier: ^0.454.0
|
specifier: ^0.454.0
|
||||||
version: 0.454.0(svelte@5.1.9)
|
version: 0.454.0(svelte@5.1.9)
|
||||||
@ -447,9 +453,15 @@ importers:
|
|||||||
|
|
||||||
packages/ui:
|
packages/ui:
|
||||||
dependencies:
|
dependencies:
|
||||||
|
'@formkit/auto-animate':
|
||||||
|
specifier: ^0.8.2
|
||||||
|
version: 0.8.2
|
||||||
'@std/semver':
|
'@std/semver':
|
||||||
specifier: npm:@jsr/std__semver@^1.0.3
|
specifier: npm:@jsr/std__semver@^1.0.3
|
||||||
version: '@jsr/std__semver@1.0.3'
|
version: '@jsr/std__semver@1.0.3'
|
||||||
|
gsap:
|
||||||
|
specifier: ^3.12.5
|
||||||
|
version: 3.12.5
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@kksh/api':
|
'@kksh/api':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
@ -1179,6 +1191,9 @@ packages:
|
|||||||
'@floating-ui/utils@0.2.8':
|
'@floating-ui/utils@0.2.8':
|
||||||
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
|
resolution: {integrity: sha512-kym7SodPp8/wloecOpcmSnWJsK7M0E5Wg8UcFA+uO4B9s5d0ywXOEro/8HM9x0rW+TljRzul/14UYz3TleT3ig==}
|
||||||
|
|
||||||
|
'@formkit/auto-animate@0.8.2':
|
||||||
|
resolution: {integrity: sha512-SwPWfeRa5veb1hOIBMdzI+73te5puUBHmqqaF1Bu7FjvxlYSz/kJcZKSa9Cg60zL0uRNeJL2SbRxV6Jp6Q1nFQ==}
|
||||||
|
|
||||||
'@gcornut/valibot-json-schema@0.31.0':
|
'@gcornut/valibot-json-schema@0.31.0':
|
||||||
resolution: {integrity: sha512-3xGptCurm23e7nuPQkdrE5rEs1FeTPHhAUsBuwwqG4/YeZLwJOoYZv+fmsppUEfo5y9lzUwNQrNqLS/q7HMc7g==}
|
resolution: {integrity: sha512-3xGptCurm23e7nuPQkdrE5rEs1FeTPHhAUsBuwwqG4/YeZLwJOoYZv+fmsppUEfo5y9lzUwNQrNqLS/q7HMc7g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@ -2711,6 +2726,9 @@ packages:
|
|||||||
graphemer@1.4.0:
|
graphemer@1.4.0:
|
||||||
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
|
||||||
|
|
||||||
|
gsap@3.12.5:
|
||||||
|
resolution: {integrity: sha512-srBfnk4n+Oe/ZnMIOXt3gT605BX9x5+rh/prT2F1SsNJsU1XuMiP0E2aptW481OnonOGACZWBqseH5Z7csHxhQ==}
|
||||||
|
|
||||||
has-flag@4.0.0:
|
has-flag@4.0.0:
|
||||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@ -5147,6 +5165,8 @@ snapshots:
|
|||||||
|
|
||||||
'@floating-ui/utils@0.2.8': {}
|
'@floating-ui/utils@0.2.8': {}
|
||||||
|
|
||||||
|
'@formkit/auto-animate@0.8.2': {}
|
||||||
|
|
||||||
'@gcornut/valibot-json-schema@0.31.0':
|
'@gcornut/valibot-json-schema@0.31.0':
|
||||||
dependencies:
|
dependencies:
|
||||||
valibot: 0.31.1
|
valibot: 0.31.1
|
||||||
@ -6963,6 +6983,8 @@ snapshots:
|
|||||||
|
|
||||||
graphemer@1.4.0: {}
|
graphemer@1.4.0: {}
|
||||||
|
|
||||||
|
gsap@3.12.5: {}
|
||||||
|
|
||||||
has-flag@4.0.0: {}
|
has-flag@4.0.0: {}
|
||||||
|
|
||||||
hasown@2.0.2:
|
hasown@2.0.2:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user