tangled
alpha
login
or
join now
vielle.dev
/
pdsls
forked from
pds.ls/pdsls
0
fork
atom
atproto explorer
0
fork
atom
overview
issues
pulls
pipelines
add navbar copy menu
handle.invalid
6 months ago
0c96896a
4cf4e93a
+86
-71
2 changed files
expand all
collapse all
unified
split
src
components
navbar.tsx
layout.tsx
+67
-44
src/components/navbar.tsx
···
1
1
import { A, Params, useLocation } from "@solidjs/router";
2
2
import Tooltip from "./tooltip";
3
3
-
import { createEffect, createSignal, Show } from "solid-js";
3
3
+
import { createEffect, createSignal, onMount, Show } from "solid-js";
4
4
import { didDocCache, labelerCache, validateHandle } from "../utils/api";
5
5
import { Did, Handle } from "@atcute/lexicons";
6
6
import { addToClipboard } from "../utils/copy";
···
32
32
const [validHandle, setValidHandle] = createSignal<boolean | undefined>(undefined);
33
33
const [fullCid, setFullCid] = createSignal(false);
34
34
const [showHandle, setShowHandle] = createSignal(localStorage.showHandle === "true");
35
35
+
const [showCopyMenu, setShowCopyMenu] = createSignal(false);
36
36
+
const [copyMenu, setCopyMenu] = createSignal<HTMLDivElement>();
37
37
+
const [menuButton, setMenuButton] = createSignal<HTMLButtonElement>();
35
38
36
39
createEffect(() => {
37
40
if (cid() !== undefined) setFullCid(false);
···
51
54
}
52
55
});
53
56
57
57
+
onMount(() =>
58
58
+
window.addEventListener("click", (ev) => {
59
59
+
if (!menuButton()?.contains(ev.target as Node) && !copyMenu()?.contains(ev.target as Node))
60
60
+
setShowCopyMenu(false);
61
61
+
}),
62
62
+
);
63
63
+
64
64
+
const CopyButton = (props: { copyContent: string; label: string }) => {
65
65
+
return (
66
66
+
<button
67
67
+
onClick={() => {
68
68
+
addToClipboard(props.copyContent);
69
69
+
setShowCopyMenu(false);
70
70
+
}}
71
71
+
class="flex rounded-lg p-1 whitespace-nowrap hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
72
72
+
>
73
73
+
{props.label}
74
74
+
</button>
75
75
+
);
76
76
+
};
77
77
+
54
78
return (
55
79
<nav class="mt-4 flex w-[22rem] flex-col text-sm wrap-anywhere sm:w-[24rem]">
56
80
<div class="relative flex items-center justify-between gap-1">
57
81
<div class="flex min-h-[1.25rem] basis-full items-center gap-2">
58
82
<Tooltip text="PDS">
59
59
-
<button
60
60
-
class="iconify lucide--hard-drive shrink-0 text-lg"
61
61
-
onclick={() => addToClipboard(pds()!)}
62
62
-
></button>
83
83
+
<span class="iconify lucide--hard-drive shrink-0 text-lg"></span>
63
84
</Tooltip>
64
85
<Show when={pds()}>
65
86
<Show when={props.params.repo}>
···
76
97
</Show>
77
98
</Show>
78
99
</div>
79
79
-
<Tooltip
80
80
-
text={`Copy ${
81
81
-
props.params.collection ? "AT URI"
82
82
-
: props.params.repo ? "DID"
83
83
-
: "PDS"
84
84
-
}`}
85
85
-
>
100
100
+
<div class="relative">
86
101
<button
87
87
-
class="iconify lucide--copy shrink-0 text-lg"
88
88
-
onclick={() =>
89
89
-
addToClipboard(
90
90
-
props.params.collection ?
91
91
-
`at://${props.params.repo}/${props.params.collection}${props.params.rkey ? `/${props.params.rkey}` : ""}`
92
92
-
: props.params.repo ? props.params.repo
93
93
-
: pds()!,
94
94
-
)
95
95
-
}
96
96
-
></button>
97
97
-
</Tooltip>
102
102
+
class="flex items-center rounded p-0.5 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
103
103
+
ref={setMenuButton}
104
104
+
onClick={() => setShowCopyMenu(!showCopyMenu())}
105
105
+
>
106
106
+
<span class="iconify lucide--copy text-base"></span>
107
107
+
</button>
108
108
+
<Show when={showCopyMenu()}>
109
109
+
<div
110
110
+
ref={setCopyMenu}
111
111
+
class="dark:bg-dark-300 absolute top-6 right-0 z-20 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-2 text-xs shadow-md dark:border-neutral-700"
112
112
+
>
113
113
+
<Show when={pds()}>
114
114
+
<CopyButton copyContent={pds()!} label="Copy PDS" />
115
115
+
</Show>
116
116
+
<Show when={props.params.repo}>
117
117
+
<CopyButton copyContent={props.params.repo} label="Copy DID" />
118
118
+
<CopyButton
119
119
+
copyContent={`at://${props.params.repo}${props.params.collection ? `/${props.params.collection}` : ""}${props.params.rkey ? `/${props.params.rkey}` : ""}`}
120
120
+
label="Copy AT URI"
121
121
+
/>
122
122
+
</Show>
123
123
+
<Show when={props.params.rkey && cid()}>
124
124
+
<CopyButton copyContent={cid()!} label="Copy CID" />
125
125
+
</Show>
126
126
+
</div>
127
127
+
</Show>
128
128
+
</div>
98
129
</div>
99
130
<div class="flex flex-col flex-wrap">
100
131
<Show when={props.params.repo}>
101
132
<div class="relative mt-1 flex items-center justify-between gap-1">
102
133
<div class="flex basis-full items-center gap-2">
103
134
<Tooltip text="Repository">
104
104
-
<button
105
105
-
class="iconify lucide--book-user text-lg"
106
106
-
onclick={() => addToClipboard(props.params.repo)}
107
107
-
></button>
135
135
+
<span class="iconify lucide--book-user text-lg"></span>
108
136
</Tooltip>
109
137
<div class="flex w-full gap-1">
110
138
{props.params.collection || location.pathname.includes("/labels") ?
···
139
167
</div>
140
168
<Tooltip text={showHandle() ? "Show DID" : "Show handle"}>
141
169
<button
170
170
+
class="flex items-center rounded p-0.5 hover:bg-neutral-200 active:bg-neutral-200 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
142
171
onclick={() => {
143
172
localStorage.showHandle = !showHandle();
144
173
setShowHandle(!showHandle());
145
174
}}
146
146
-
class={
147
147
-
`iconify shrink-0 text-lg transition-transform duration-400 ${showHandle() ? "rotate-y-180" : ""} ` +
148
148
-
(swapIcons[props.params.repo] ?? "lucide--arrow-left-right")
149
149
-
}
150
150
-
></button>
175
175
+
>
176
176
+
<span
177
177
+
class={
178
178
+
`iconify shrink-0 text-base transition-transform duration-400 ${showHandle() ? "rotate-y-180" : ""} ` +
179
179
+
(swapIcons[props.params.repo] ?? "lucide--arrow-left-right")
180
180
+
}
181
181
+
></span>
182
182
+
</button>
151
183
</Tooltip>
152
184
</div>
153
185
</Show>
···
166
198
<Show when={props.params.collection}>
167
199
<div class="mt-1 flex items-center gap-2">
168
200
<Tooltip text="Collection">
169
169
-
<button
170
170
-
onclick={() => addToClipboard(props.params.collection)}
171
171
-
class="iconify lucide--folder-open text-lg"
172
172
-
></button>
201
201
+
<span class="iconify lucide--folder-open text-lg"></span>
173
202
</Tooltip>
174
203
<Show when={props.params.rkey}>
175
204
<A
···
188
217
<Show when={props.params.rkey}>
189
218
<div class="mt-1 flex items-center gap-2">
190
219
<Tooltip text="Record">
191
191
-
<button
192
192
-
onclick={() => addToClipboard(props.params.rkey)}
193
193
-
class="iconify lucide--file-json text-lg"
194
194
-
></button>
220
220
+
<span class="iconify lucide--file-json text-lg"></span>
195
221
</Tooltip>
196
222
<div class="flex gap-1">
197
223
<span>{props.params.rkey}</span>
···
228
254
{(cid) => (
229
255
<div class="mt-1 flex gap-2">
230
256
<Tooltip text="CID">
231
231
-
<button
232
232
-
onclick={() => addToClipboard(cid())}
233
233
-
class="iconify lucide--box text-lg"
234
234
-
></button>
257
257
+
<span class="iconify lucide--box text-lg"></span>
235
258
</Tooltip>
236
259
<button
237
260
dir="rtl"
+19
-27
src/layout.tsx
···
38
38
}
39
39
});
40
40
41
41
-
const clickEvent = (event: MouseEvent) => {
42
42
-
if (!menuButton()?.contains(event.target as Node) && !menu()?.contains(event.target as Node))
43
43
-
setShowMenu(false);
44
44
-
};
45
45
-
46
41
onMount(() => {
47
42
window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", themeEvent);
48
48
-
window.addEventListener("click", clickEvent);
43
43
+
window.addEventListener("click", (ev) => {
44
44
+
if (!menuButton()?.contains(ev.target as Node) && !menu()?.contains(ev.target as Node))
45
45
+
setShowMenu(false);
46
46
+
});
49
47
});
48
48
+
49
49
+
const NavButton = (props: { href: string; label: string }) => {
50
50
+
return (
51
51
+
<A
52
52
+
href={props.href}
53
53
+
onClick={() => setShowMenu(false)}
54
54
+
class="rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
55
55
+
>
56
56
+
{props.label}
57
57
+
</A>
58
58
+
);
59
59
+
};
50
60
51
61
return (
52
62
<div id="main" class="m-4 flex flex-col items-center text-neutral-900 dark:text-neutral-200">
···
82
92
ref={setMenu}
83
93
class="dark:bg-dark-300 absolute top-8 right-0 z-20 flex flex-col rounded-lg border-[0.5px] border-neutral-300 bg-neutral-50 p-3 text-sm shadow-md dark:border-neutral-700"
84
94
>
85
85
-
<A
86
86
-
href="/jetstream"
87
87
-
onClick={() => setShowMenu(false)}
88
88
-
class="rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
89
89
-
>
90
90
-
<span>Jetstream</span>
91
91
-
</A>
92
92
-
<A
93
93
-
href="/firehose"
94
94
-
onClick={() => setShowMenu(false)}
95
95
-
class="rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
96
96
-
>
97
97
-
<span>Firehose</span>
98
98
-
</A>
99
99
-
<A
100
100
-
href="/settings"
101
101
-
onClick={() => setShowMenu(false)}
102
102
-
class="rounded-lg p-1 hover:bg-neutral-200/50 active:bg-neutral-200/50 dark:hover:bg-neutral-700 dark:active:bg-neutral-700"
103
103
-
>
104
104
-
<span>Settings</span>
105
105
-
</A>
95
95
+
<NavButton href="/jetstream" label="Jetstream" />
96
96
+
<NavButton href="/firehose" label="Firehose" />
97
97
+
<NavButton href="/settings" label="Settings" />
106
98
<ThemeSelection />
107
99
</div>
108
100
</Show>