mirror of
https://seed.radicle.dev/rad:z4V1sjrXqjvFdnCUbxPFqd5p4DtH5.git
synced 2026-11-02 02:00:31 +08:00
Add local switch and contextual help to sidebar
This commit is contained in:
+6
-1
@@ -121,6 +121,11 @@ async function checkState() {
|
||||
.runExclusive(async () => {
|
||||
try {
|
||||
const { state: node } = await api.getNode();
|
||||
|
||||
if (httpdState && httpdState.state !== "stopped") {
|
||||
httpdState.node = node;
|
||||
}
|
||||
|
||||
if (httpdState && httpdState.state === "authenticated") {
|
||||
const sess = await api.session.getById(httpdState.session.id);
|
||||
const unixTimeInSeconds = Math.floor(Date.now() / 1000);
|
||||
@@ -130,7 +135,7 @@ async function checkState() {
|
||||
) {
|
||||
update({ state: "running", node });
|
||||
} else {
|
||||
update({ ...httpdState, node });
|
||||
update(httpdState);
|
||||
}
|
||||
} else {
|
||||
update({ state: "running", node });
|
||||
|
||||
@@ -1,16 +1,13 @@
|
||||
<script lang="ts">
|
||||
import type { ProjectBaseUrlActivity } from "./router";
|
||||
|
||||
import { api } from "@app/lib/httpd";
|
||||
import { twemoji } from "@app/lib/utils";
|
||||
import { isLocal, twemoji } from "@app/lib/utils";
|
||||
|
||||
import AppLayout from "@app/App/AppLayout.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import ProjectCard from "@app/components/ProjectCard.svelte";
|
||||
|
||||
export let projects: ProjectBaseUrlActivity[];
|
||||
|
||||
$: localProjects = projects[0]?.baseUrl === api.baseUrl;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -69,7 +66,7 @@
|
||||
|
||||
{#if projects.length > 0}
|
||||
<div class="heading">
|
||||
{#if localProjects}
|
||||
{#if isLocal(projects[0].baseUrl.hostname)}
|
||||
<!-- prettier-ignore -->
|
||||
<span>Explore projects on your <span class="txt-bold">local node</span>.</span>
|
||||
{:else}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import MobileFooter from "@app/App/MobileFooter.svelte";
|
||||
import Sidebar from "./Sidebar.svelte";
|
||||
import Sidebar from "@app/views/projects/Sidebar.svelte";
|
||||
|
||||
export let activeTab: ActiveTab | undefined = undefined;
|
||||
export let baseUrl: BaseUrl;
|
||||
|
||||
@@ -2,11 +2,17 @@
|
||||
import type { ActiveTab } from "./Header.svelte";
|
||||
import type { BaseUrl, Project } from "@httpd-client";
|
||||
|
||||
import { ResponseError } from "@httpd-client/lib/fetcher";
|
||||
|
||||
import { api, httpdStore } from "@app/lib/httpd";
|
||||
import { isLocal } from "@app/lib/utils";
|
||||
import { onMount } from "svelte";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import ContextHelp from "@app/views/projects/Sidebar/ContextHelp.svelte";
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
import Loading from "@app/components/Loading.svelte";
|
||||
import Popover from "@app/components/Popover.svelte";
|
||||
|
||||
import Help from "@app/App/Help.svelte";
|
||||
@@ -27,6 +33,20 @@
|
||||
);
|
||||
}
|
||||
|
||||
// To avoid concurrent request.
|
||||
let queryingLocalProject: boolean = true;
|
||||
let localProject: "notFound" | "found" | undefined = undefined;
|
||||
$: hideContextHelp =
|
||||
isLocal(baseUrl.hostname) && $httpdStore.state === "authenticated";
|
||||
$: loadingContextHelp =
|
||||
$httpdStore.state !== "stopped" && localProject === undefined;
|
||||
|
||||
httpdStore.subscribe(async () => {
|
||||
if ($httpdStore.state !== "stopped" && !queryingLocalProject) {
|
||||
await detectLocalProject();
|
||||
}
|
||||
});
|
||||
|
||||
function loadSidebarState(): boolean {
|
||||
const storedSidebarState = window.localStorage.getItem(SIDEBAR_STATE_KEY);
|
||||
|
||||
@@ -42,8 +62,22 @@
|
||||
storeSidebarState(expanded);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
async function detectLocalProject(): Promise<void> {
|
||||
queryingLocalProject = true;
|
||||
localProject = await api.project
|
||||
.getById(project.id)
|
||||
.then<"found">(() => "found")
|
||||
.catch((error: unknown) =>
|
||||
error instanceof ResponseError && error.status === 404
|
||||
? "notFound"
|
||||
: undefined,
|
||||
);
|
||||
queryingLocalProject = false;
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
expanded = loadSidebarState();
|
||||
await detectLocalProject();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -55,6 +89,9 @@
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.expanded {
|
||||
width: 22.5rem;
|
||||
}
|
||||
.project-navigation {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
@@ -85,9 +122,16 @@
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
}
|
||||
.help-box {
|
||||
padding: 1rem;
|
||||
background-color: var(--color-background-float);
|
||||
border: 1px solid var(--color-border-hint);
|
||||
font-size: var(--font-size-small);
|
||||
border-radius: var(--border-radius-small);
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="sidebar">
|
||||
<div class="sidebar" class:expanded>
|
||||
{#if expanded}
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
<div class="project-navigation">
|
||||
@@ -164,39 +208,62 @@
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-direction:column; gap: 1rem;">
|
||||
{#if !hideContextHelp}
|
||||
{#if loadingContextHelp}
|
||||
<div
|
||||
style="display: flex; justify-content: center; align-items: center; height: 2rem;">
|
||||
<Loading small />
|
||||
</div>
|
||||
{:else}
|
||||
<div class="help-box">
|
||||
<ContextHelp
|
||||
{localProject}
|
||||
{baseUrl}
|
||||
projectId={project.id}
|
||||
hideLocalButton={isLocal(baseUrl.hostname)}
|
||||
disableLocalButton={$httpdStore.state !== "authenticated" ||
|
||||
localProject !== "found"} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="sidebar-footer" style:flex-direction="row">
|
||||
<Button title={"Collapse"} on:click={toggleSidebar} variant="background">
|
||||
<IconSmall name="chevron-left" />
|
||||
</Button>
|
||||
<div style:width="1.5rem" />
|
||||
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<div class="sidebar-footer" style:flex-direction="row">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Settings"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="settings" />
|
||||
Settings
|
||||
title={"Collapse"}
|
||||
on:click={toggleSidebar}
|
||||
variant="background">
|
||||
<IconSmall name="chevron-left" />
|
||||
</Button>
|
||||
<div style:width="1.5rem" />
|
||||
|
||||
<Settings slot="popover" />
|
||||
</Popover>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Help"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="help" />
|
||||
Help
|
||||
</Button>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Settings"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="settings" />
|
||||
Settings
|
||||
</Button>
|
||||
|
||||
<Help slot="popover" />
|
||||
</Popover>
|
||||
<Settings slot="popover" />
|
||||
</Popover>
|
||||
<Popover popoverPositionBottom="2.5rem" popoverPositionLeft="0">
|
||||
<Button
|
||||
variant="background"
|
||||
title="Help"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="help" />
|
||||
Help
|
||||
</Button>
|
||||
|
||||
<Help slot="popover" />
|
||||
</Popover>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<div style="display: flex; flex-direction: column; gap: 1rem;">
|
||||
@@ -287,6 +354,37 @@
|
||||
|
||||
<Help slot="popover" />
|
||||
</Popover>
|
||||
|
||||
{#if !hideContextHelp}
|
||||
{#if loadingContextHelp}
|
||||
<div
|
||||
style="display: flex; justify-content: center; align-items: center; height: 2rem;">
|
||||
<Loading small condensed />
|
||||
</div>
|
||||
{:else}
|
||||
<Popover popoverPositionBottom="0" popoverPositionLeft="3rem">
|
||||
<Button
|
||||
stylePadding="0 0.75rem"
|
||||
variant="background"
|
||||
title="Help"
|
||||
slot="toggle"
|
||||
let:toggle
|
||||
on:click={toggle}>
|
||||
<IconSmall name="globe" />
|
||||
</Button>
|
||||
|
||||
<ContextHelp
|
||||
{localProject}
|
||||
{baseUrl}
|
||||
popover
|
||||
projectId={project.id}
|
||||
hideLocalButton={isLocal(baseUrl.hostname)}
|
||||
disableLocalButton={$httpdStore.state !== "authenticated" ||
|
||||
localProject !== "found"}
|
||||
slot="popover" />
|
||||
</Popover>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import type { BaseUrl } from "@httpd-client";
|
||||
|
||||
import { api, httpdStore } from "@app/lib/httpd";
|
||||
import { isLocal } from "@app/lib/utils";
|
||||
|
||||
import Button from "@app/components/Button.svelte";
|
||||
import ExternalLink from "@app/components/ExternalLink.svelte";
|
||||
import IconSmall from "@app/components/IconSmall.svelte";
|
||||
import Link from "@app/components/Link.svelte";
|
||||
|
||||
export let baseUrl: BaseUrl;
|
||||
export let disableLocalButton: boolean;
|
||||
export let hideLocalButton: boolean;
|
||||
export let localProject: "notFound" | "found" | undefined;
|
||||
export let projectId: string;
|
||||
export let popover: boolean = false;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.help {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.popover {
|
||||
width: 18.5rem;
|
||||
}
|
||||
.title {
|
||||
padding-bottom: 0.75rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="help" class:popover>
|
||||
{#if $httpdStore.state === "stopped"}
|
||||
<div class="title txt-bold">Device not connected</div>
|
||||
<div>Click the Connect button in the top right corner to get started.</div>
|
||||
{:else if localProject === "notFound"}
|
||||
<div class="title txt-bold">Project not available locally</div>
|
||||
<div>
|
||||
This project hasn't been found on your local node. Click the Clone button
|
||||
in the top right corner to get a local copy.
|
||||
</div>
|
||||
{:else if $httpdStore.state === "running" && localProject === "found"}
|
||||
<div class="title txt-bold">Not authenticated</div>
|
||||
<div>To make changes you need to authenticate yourself.</div>
|
||||
<div>
|
||||
Click the Authenticate button in the top right corner to get
|
||||
authenticated.
|
||||
</div>
|
||||
{:else if !isLocal(baseUrl.hostname) && localProject === "found"}
|
||||
<div class="title txt-bold">Read Only</div>
|
||||
<div>This is a read only preview hosted on</div>
|
||||
<ExternalLink href={baseUrl.hostname} />
|
||||
{/if}
|
||||
|
||||
{#if !hideLocalButton}
|
||||
<div style:padding-top="1rem">
|
||||
<Link
|
||||
disabled={disableLocalButton}
|
||||
route={{
|
||||
resource: "project.source",
|
||||
node: api.baseUrl,
|
||||
project: projectId,
|
||||
}}>
|
||||
<Button size="large" styleWidth="100%" disabled={disableLocalButton}>
|
||||
<IconSmall name="device" />Make changes on your local node
|
||||
</Button>
|
||||
</Link>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
Reference in New Issue
Block a user