230 lines
5.2 KiB
Svelte
230 lines
5.2 KiB
Svelte
<script>
|
|
import { Heading, Body, Button, Icon } from "@budibase/bbui"
|
|
import { processStringSync } from "@budibase/string-templates"
|
|
import { auth } from "stores/portal"
|
|
import { goto } from "@roxi/routify"
|
|
import { UserAvatars } from "@budibase/frontend-core"
|
|
import { sdk } from "@budibase/shared-core"
|
|
import AppRowContext from "./AppRowContext.svelte"
|
|
import FavouriteAppButton from "pages/builder/portal/apps/FavouriteAppButton.svelte"
|
|
|
|
export let app
|
|
export let lockedAction
|
|
|
|
let actionsOpen = false
|
|
|
|
$: editing = app.sessions?.length
|
|
$: isBuilder = sdk.users.isBuilder($auth.user, app?.devId)
|
|
$: unclickable = !isBuilder && !app.deployed
|
|
|
|
const handleDefaultClick = () => {
|
|
if (!isBuilder) {
|
|
goToApp()
|
|
} else if (window.innerWidth < 640) {
|
|
goToOverview()
|
|
} else {
|
|
goToBuilder()
|
|
}
|
|
}
|
|
|
|
const goToBuilder = () => {
|
|
$goto(`../../app/${app.devId}`)
|
|
}
|
|
|
|
const goToOverview = () => {
|
|
$goto(`../../app/${app.devId}/settings`)
|
|
}
|
|
|
|
const goToApp = () => {
|
|
if (app.deployed && app.url) {
|
|
window.open(`/app${app.url}`, "_blank")
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
<div
|
|
class="app-row"
|
|
class:unclickable
|
|
class:actionsOpen
|
|
class:favourite={app.favourite}
|
|
on:click={lockedAction || handleDefaultClick}
|
|
>
|
|
<div class="title">
|
|
<div class="app-icon">
|
|
<Icon size="L" name={app.icon?.name || "Apps"} color={app.icon?.color} />
|
|
</div>
|
|
<div class="name">
|
|
<Heading size="S">
|
|
{app.name}
|
|
</Heading>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="updated">
|
|
{#if editing && isBuilder}
|
|
Currently editing
|
|
<UserAvatars users={app.sessions} />
|
|
{:else if app.updatedAt}
|
|
{processStringSync("Updated {{ duration time 'millisecond' }} ago", {
|
|
time: new Date().getTime() - new Date(app.updatedAt).getTime(),
|
|
})}
|
|
{:else}
|
|
Never updated
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="title app-status" class:deployed={app.deployed}>
|
|
<Icon size="L" name={app.deployed ? "GlobeCheck" : "GlobeStrike"} />
|
|
<Body size="S">{app.deployed ? "Published" : "Unpublished"}</Body>
|
|
</div>
|
|
|
|
<div class="actions-wrap">
|
|
<div class="app-row-actions">
|
|
{#if isBuilder}
|
|
<div class="row-action">
|
|
<Button size="S" secondary on:click={lockedAction || goToBuilder}>
|
|
Edit
|
|
</Button>
|
|
</div>
|
|
<div class="row-action">
|
|
<AppRowContext
|
|
{app}
|
|
on:open={() => {
|
|
actionsOpen = true
|
|
}}
|
|
on:close={() => {
|
|
actionsOpen = false
|
|
}}
|
|
/>
|
|
</div>
|
|
{:else}
|
|
<!-- this can happen if an app builder has app user access to an app -->
|
|
<Button size="S" secondary>View</Button>
|
|
{/if}
|
|
</div>
|
|
|
|
<div class="favourite-icon">
|
|
<FavouriteAppButton {app} noWrap />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<style>
|
|
.app-row {
|
|
background: var(--background);
|
|
padding: 24px 32px;
|
|
border-radius: 8px;
|
|
display: grid;
|
|
grid-template-columns: 35% 25% 15% auto;
|
|
align-items: center;
|
|
gap: var(--spacing-m);
|
|
transition: border 130ms ease-out;
|
|
border: 1px solid transparent;
|
|
}
|
|
.app-row:not(.unclickable):hover {
|
|
cursor: pointer;
|
|
border-color: var(--spectrum-global-color-gray-300);
|
|
}
|
|
|
|
.app-row .favourite-icon {
|
|
display: none;
|
|
}
|
|
|
|
.app-row:hover .favourite-icon,
|
|
.app-row.favourite .favourite-icon,
|
|
.app-row.actionsOpen .favourite-icon {
|
|
display: flex;
|
|
}
|
|
|
|
.updated {
|
|
color: var(--spectrum-global-color-gray-700);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.title,
|
|
.name {
|
|
flex: 1 1 auto;
|
|
}
|
|
.name {
|
|
width: 0;
|
|
}
|
|
.title,
|
|
.app-status {
|
|
display: flex;
|
|
flex-direction: row;
|
|
justify-content: flex-start;
|
|
align-items: center;
|
|
gap: 10px;
|
|
}
|
|
|
|
.title :global(.spectrum-Heading),
|
|
.title :global(.spectrum-Icon),
|
|
.title :global(.spectrum-Body) {
|
|
color: var(--spectrum-global-color-gray-900);
|
|
}
|
|
|
|
.app-status:not(.deployed) :global(.spectrum-Icon),
|
|
.app-status:not(.deployed) :global(.spectrum-Body) {
|
|
color: var(--spectrum-global-color-gray-600);
|
|
}
|
|
|
|
.app-row-actions {
|
|
display: none;
|
|
}
|
|
|
|
.app-row:hover .app-row-actions,
|
|
.app-row.actionsOpen .app-row-actions {
|
|
gap: var(--spacing-m);
|
|
flex-direction: row;
|
|
justify-content: flex-end;
|
|
align-items: center;
|
|
display: flex;
|
|
}
|
|
|
|
.actions-wrap {
|
|
gap: var(--spacing-m);
|
|
display: flex;
|
|
justify-content: flex-end;
|
|
min-height: var(--spectrum-alias-item-height-s);
|
|
}
|
|
|
|
.name {
|
|
text-decoration: none;
|
|
overflow: hidden;
|
|
}
|
|
.name :global(.spectrum-Heading) {
|
|
overflow: hidden;
|
|
white-space: nowrap;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
@media (max-width: 1000px) {
|
|
.app-row {
|
|
grid-template-columns: 45% 30% auto;
|
|
}
|
|
.updated {
|
|
display: none;
|
|
}
|
|
}
|
|
@media (max-width: 800px) {
|
|
.app-row {
|
|
grid-template-columns: 1fr auto;
|
|
}
|
|
.app-status {
|
|
display: none;
|
|
}
|
|
}
|
|
@media (max-width: 640px) {
|
|
.app-row {
|
|
padding: 20px;
|
|
}
|
|
.app-row-actions {
|
|
display: none;
|
|
}
|
|
}
|
|
</style>
|