|
|
|
@ -4,8 +4,10 @@
|
|
|
|
import * as Dialog from '$lib/components/ui/dialog'
|
|
|
|
import * as Dialog from '$lib/components/ui/dialog'
|
|
|
|
import * as DropdownMenu from '$lib/components/ui/dropdown-menu'
|
|
|
|
import * as DropdownMenu from '$lib/components/ui/dropdown-menu'
|
|
|
|
import * as Tooltip from '$lib/components/ui/tooltip'
|
|
|
|
import * as Tooltip from '$lib/components/ui/tooltip'
|
|
|
|
|
|
|
|
import * as Avatar from '$lib/components/ui/avatar'
|
|
|
|
import { Badge } from '$lib/components/ui/badge'
|
|
|
|
import { Badge } from '$lib/components/ui/badge'
|
|
|
|
import { ScrollArea } from '$lib/components/ui/scroll-area'
|
|
|
|
import { ScrollArea } from '$lib/components/ui/scroll-area'
|
|
|
|
|
|
|
|
import { Separator } from '$lib/components/ui/separator'
|
|
|
|
import type { Shortener, Project, Setting } from '$lib/db/types'
|
|
|
|
import type { Shortener, Project, Setting } from '$lib/db/types'
|
|
|
|
import {
|
|
|
|
import {
|
|
|
|
BarChart,
|
|
|
|
BarChart,
|
|
|
|
@ -35,36 +37,10 @@
|
|
|
|
deleteDialogOpen = true
|
|
|
|
deleteDialogOpen = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const showEditModal = async (e: MouseEvent) => {
|
|
|
|
|
|
|
|
// bail if opening a new tab, or we're on too small a screen
|
|
|
|
|
|
|
|
if (innerWidth < 640) return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// // prevent navigation
|
|
|
|
|
|
|
|
// e.preventDefault()
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
const { href } = e.currentTarget as HTMLAnchorElement
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// run `load` functions (or rather, get the result of the `load` functions
|
|
|
|
|
|
|
|
// that are already running because of `data-sveltekit-preload-data`)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// const href = `/projects/${selected_project.uuid}/links/${shortener.code}/edit`
|
|
|
|
|
|
|
|
const result = await preloadData(href)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (result.type === 'loaded' && result.status === 200) {
|
|
|
|
|
|
|
|
pushState(href, { editProjectLink: result.data })
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
// something bad happened! try navigating
|
|
|
|
|
|
|
|
goto(href)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let editProjectLinkOpen = false
|
|
|
|
let editProjectLinkOpen = false
|
|
|
|
let editData: typeof $page.state.editProjectLink
|
|
|
|
let editData: typeof $page.state.editProjectLink
|
|
|
|
|
|
|
|
|
|
|
|
const showEditModalNew = async (
|
|
|
|
const showEditModal = async (projectUuid: string, code: string) => {
|
|
|
|
projectUuid: string,
|
|
|
|
|
|
|
|
code: string,
|
|
|
|
|
|
|
|
) => {
|
|
|
|
|
|
|
|
const href = `/projects/${projectUuid}/links/${code}/edit`
|
|
|
|
const href = `/projects/${projectUuid}/links/${code}/edit`
|
|
|
|
const result = await preloadData(href)
|
|
|
|
const result = await preloadData(href)
|
|
|
|
|
|
|
|
|
|
|
|
@ -90,42 +66,26 @@
|
|
|
|
goto(href)
|
|
|
|
goto(href)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const hostDomain = 'https://' + shortener.link.split('/')[2]
|
|
|
|
</script>
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<Card.Root class="w-full max-w-[500px]">
|
|
|
|
<Card.Root>
|
|
|
|
<Card.Header>
|
|
|
|
<Card.Header>
|
|
|
|
<Card.Title class="flex items-center justify-between gap-2">
|
|
|
|
<Card.Title class="flex gap-4 justify-between items-center">
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
<Avatar.Root>
|
|
|
|
<a
|
|
|
|
<Avatar.Image
|
|
|
|
href={'https://' + shortener_url + '/' + shortener.code}
|
|
|
|
src={hostDomain + '/favicon.ico'}
|
|
|
|
target="_blank"
|
|
|
|
alt="favicon" />
|
|
|
|
class="hover:underline">
|
|
|
|
<Avatar.Fallback class="bg-opacity-0">
|
|
|
|
{shortener_url + '/' + shortener.code}
|
|
|
|
<img src="/favicon.png" alt="favicon" />
|
|
|
|
</a>
|
|
|
|
</Avatar.Fallback>
|
|
|
|
<ExternalLink size={16} />
|
|
|
|
</Avatar.Root>
|
|
|
|
</div>
|
|
|
|
<div class="flex flex-col flex-grow gap-2 items-start">
|
|
|
|
<div class="flex gap-4">
|
|
|
|
|
|
|
|
<Badge variant="outline" class="flex gap-2">
|
|
|
|
|
|
|
|
{#if shortener.active}
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
class="relative inline-flex h-2 w-2 rounded-full bg-green-400"
|
|
|
|
|
|
|
|
></span>
|
|
|
|
|
|
|
|
Active
|
|
|
|
|
|
|
|
{:else}
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
class="relative inline-flex h-2 w-2 rounded-full bg-gray-600"
|
|
|
|
|
|
|
|
></span>
|
|
|
|
|
|
|
|
Inactive
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Card.Title>
|
|
|
|
|
|
|
|
<Card.Description>
|
|
|
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
|
|
|
<Tooltip.Root>
|
|
|
|
<Tooltip.Root>
|
|
|
|
<Tooltip.Trigger>
|
|
|
|
<Tooltip.Trigger>
|
|
|
|
<div
|
|
|
|
<div
|
|
|
|
class="max-w-[200px] overflow-hidden overflow-ellipsis whitespace-nowrap">
|
|
|
|
class="whitespace-nowrap max-w-[250px] overflow-x-clip overflow-ellipsis">
|
|
|
|
{shortener.link}
|
|
|
|
{shortener.link}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Tooltip.Trigger>
|
|
|
|
</Tooltip.Trigger>
|
|
|
|
@ -133,51 +93,19 @@
|
|
|
|
<p>{shortener.link}</p>
|
|
|
|
<p>{shortener.link}</p>
|
|
|
|
</Tooltip.Content>
|
|
|
|
</Tooltip.Content>
|
|
|
|
</Tooltip.Root>
|
|
|
|
</Tooltip.Root>
|
|
|
|
{#if shortener.ios}
|
|
|
|
|
|
|
|
<Tooltip.Root>
|
|
|
|
<div
|
|
|
|
<Tooltip.Trigger>
|
|
|
|
class="flex gap-2 items-center text-sm text-muted-foreground">
|
|
|
|
<Badge variant="outline" class="flex gap-2">iOS</Badge>
|
|
|
|
|
|
|
|
</Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Tooltip.Content>
|
|
|
|
|
|
|
|
<p>{shortener.ios_link}</p>
|
|
|
|
|
|
|
|
</Tooltip.Content>
|
|
|
|
|
|
|
|
</Tooltip.Root>
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
{#if shortener.android}
|
|
|
|
|
|
|
|
<Tooltip.Root>
|
|
|
|
|
|
|
|
<Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Badge variant="outline" class="flex gap-2"
|
|
|
|
|
|
|
|
>Android</Badge>
|
|
|
|
|
|
|
|
</Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Tooltip.Content>
|
|
|
|
|
|
|
|
<p>{shortener.android_link}</p>
|
|
|
|
|
|
|
|
</Tooltip.Content>
|
|
|
|
|
|
|
|
</Tooltip.Root>
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Card.Description>
|
|
|
|
|
|
|
|
</Card.Header>
|
|
|
|
|
|
|
|
<Card.Content>
|
|
|
|
|
|
|
|
<div class="flex items-center justify-between">
|
|
|
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
href={`/links/${shortener.code}`}
|
|
|
|
|
|
|
|
class="bg-secondary flex h-8 items-center justify-center gap-1 rounded text-sm">
|
|
|
|
|
|
|
|
<BarChart size={20} />
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
{shortener.visitorCount} visits
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<a
|
|
|
|
<a
|
|
|
|
class={cn(
|
|
|
|
href={'https://' + shortener_url + '/' + shortener.code}
|
|
|
|
buttonVariants({ variant: 'default' }),
|
|
|
|
target="_blank"
|
|
|
|
'bg-secondary flex h-8 items-center justify-center gap-1 rounded text-sm',
|
|
|
|
class="hover:underline">
|
|
|
|
)}
|
|
|
|
{shortener_url + '/' + shortener.code}
|
|
|
|
href={`/projects/${selected_project.uuid}/links/${shortener.code}/qr`}
|
|
|
|
|
|
|
|
on:click|preventDefault={showQRModal}>
|
|
|
|
|
|
|
|
<QrCode size={20} />
|
|
|
|
|
|
|
|
</a>
|
|
|
|
</a>
|
|
|
|
|
|
|
|
<ExternalLink size={16} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<DropdownMenu.Root>
|
|
|
|
<DropdownMenu.Root>
|
|
|
|
<DropdownMenu.Trigger>
|
|
|
|
<DropdownMenu.Trigger>
|
|
|
|
<MoreVertical />
|
|
|
|
<MoreVertical />
|
|
|
|
@ -194,23 +122,85 @@
|
|
|
|
<a
|
|
|
|
<a
|
|
|
|
href={`/projects/${selected_project.uuid}/links/${shortener.code}/edit`}
|
|
|
|
href={`/projects/${selected_project.uuid}/links/${shortener.code}/edit`}
|
|
|
|
on:click|preventDefault={() =>
|
|
|
|
on:click|preventDefault={() =>
|
|
|
|
showEditModalNew(
|
|
|
|
showEditModal(
|
|
|
|
selected_project.uuid || '',
|
|
|
|
selected_project.uuid || '',
|
|
|
|
shortener.code,
|
|
|
|
shortener.code,
|
|
|
|
)}>
|
|
|
|
)}>
|
|
|
|
<DropdownMenu.Item class="flex items-center gap-2">
|
|
|
|
<DropdownMenu.Item class="flex gap-2 items-center">
|
|
|
|
<EditIcon size={16} />Edit
|
|
|
|
<EditIcon size={16} />Edit
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
</a>
|
|
|
|
</a>
|
|
|
|
<DropdownMenu.Item
|
|
|
|
<DropdownMenu.Item
|
|
|
|
on:click={() => openDeleteDialog(shortener.code)}
|
|
|
|
on:click={() => openDeleteDialog(shortener.code)}
|
|
|
|
class="text-destructive data-[highlighted]:bg-destructive flex items-center gap-2">
|
|
|
|
class="flex gap-2 items-center text-destructive data-[highlighted]:bg-destructive">
|
|
|
|
<TrashIcon size={16} />
|
|
|
|
<TrashIcon size={16} />
|
|
|
|
Delete
|
|
|
|
Delete
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
</DropdownMenu.Group>
|
|
|
|
</DropdownMenu.Group>
|
|
|
|
</DropdownMenu.Content>
|
|
|
|
</DropdownMenu.Content>
|
|
|
|
</DropdownMenu.Root>
|
|
|
|
</DropdownMenu.Root>
|
|
|
|
|
|
|
|
</Card.Title>
|
|
|
|
|
|
|
|
</Card.Header>
|
|
|
|
|
|
|
|
<Card.Content>
|
|
|
|
|
|
|
|
<div class="flex justify-between items-center">
|
|
|
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
|
|
|
<Button
|
|
|
|
|
|
|
|
href={`/links/${shortener.code}`}
|
|
|
|
|
|
|
|
class="flex gap-1 justify-center items-center h-8 text-sm rounded bg-secondary">
|
|
|
|
|
|
|
|
<BarChart size={20} />
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
|
|
|
{shortener.visitorCount} visits
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
|
|
<a
|
|
|
|
|
|
|
|
class={cn(
|
|
|
|
|
|
|
|
buttonVariants({ variant: 'default' }),
|
|
|
|
|
|
|
|
'flex h-8 items-center justify-center gap-1 rounded bg-secondary text-sm',
|
|
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
href={`/projects/${selected_project.uuid}/links/${shortener.code}/qr`}
|
|
|
|
|
|
|
|
on:click|preventDefault={showQRModal}>
|
|
|
|
|
|
|
|
<QrCode size={20} />
|
|
|
|
|
|
|
|
</a>
|
|
|
|
|
|
|
|
{#if shortener.ios}
|
|
|
|
|
|
|
|
<Tooltip.Root>
|
|
|
|
|
|
|
|
<Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Badge variant="outline" class="flex gap-2">iOS</Badge>
|
|
|
|
|
|
|
|
</Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Tooltip.Content>
|
|
|
|
|
|
|
|
<p>{shortener.ios_link}</p>
|
|
|
|
|
|
|
|
</Tooltip.Content>
|
|
|
|
|
|
|
|
</Tooltip.Root>
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
{#if shortener.android}
|
|
|
|
|
|
|
|
<Tooltip.Root>
|
|
|
|
|
|
|
|
<Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Badge variant="outline" class="flex gap-2"
|
|
|
|
|
|
|
|
>Android</Badge>
|
|
|
|
|
|
|
|
</Tooltip.Trigger>
|
|
|
|
|
|
|
|
<Tooltip.Content>
|
|
|
|
|
|
|
|
<p>{shortener.android_link}</p>
|
|
|
|
|
|
|
|
</Tooltip.Content>
|
|
|
|
|
|
|
|
</Tooltip.Root>
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
<div class="flex gap-4">
|
|
|
|
|
|
|
|
{#if shortener.projectName}
|
|
|
|
|
|
|
|
<Badge variant="secondary">{shortener.projectName}</Badge>
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
<Badge variant="outline" class="flex gap-2">
|
|
|
|
|
|
|
|
{#if shortener.active}
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
class="inline-flex relative w-2 h-2 bg-green-400 rounded-full"
|
|
|
|
|
|
|
|
></span>
|
|
|
|
|
|
|
|
Active
|
|
|
|
|
|
|
|
{:else}
|
|
|
|
|
|
|
|
<span
|
|
|
|
|
|
|
|
class="inline-flex relative w-2 h-2 bg-gray-600 rounded-full"
|
|
|
|
|
|
|
|
></span>
|
|
|
|
|
|
|
|
Inactive
|
|
|
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
</Badge>
|
|
|
|
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Card.Content>
|
|
|
|
</Card.Content>
|
|
|
|
</Card.Root>
|
|
|
|
</Card.Root>
|
|
|
|
|