|
6 | 6 | RELAY_ORIGIN, |
7 | 7 | type TunnelCommandOS |
8 | 8 | } from '$lib/tunnel-command'; |
9 | | - import { buildDefaultExposeName, resolveExposeName } from '$lib/expose-name'; |
| 9 | + import { buildDefaultExposeName } from '$lib/expose-name'; |
| 10 | + import { classifyShareInput, type ShareKind } from '$lib/share-link'; |
10 | 11 |
|
11 | | - const DEFAULT_HOST = '3000'; |
| 12 | + const SHARE_KIND_LABEL: Record<ShareKind, string> = { url: 'URL', file: 'File', port: 'Port' }; |
| 13 | + const SHARE_PLACEHOLDER = 'file:///Users/me/site/index.html or 3000'; |
12 | 14 |
|
13 | | - let target = $state('3000'); |
| 15 | + // Empty by default so the field reads as "paste a link here" instead of |
| 16 | + // pre-committing the user to a local port. |
| 17 | + let target = $state(''); |
14 | 18 | let os: TunnelCommandOS = $state('unix'); |
15 | 19 | let name = $state(''); |
16 | 20 | let nameSeed = $state(''); |
|
20 | 24 | nameSeed = crypto.randomUUID(); |
21 | 25 | }); |
22 | 26 |
|
23 | | - const generatedName = $derived(buildDefaultExposeName(target, nameSeed)); |
| 27 | + const share = $derived(classifyShareInput(target)); |
| 28 | + const generatedName = $derived(buildDefaultExposeName(share.seedTarget, nameSeed)); |
24 | 29 | const effectiveName = $derived(name.trim() || generatedName); |
25 | 30 |
|
26 | | - const installBlock = $derived.by(() => { |
27 | | - const cmd = buildTunnelDisplayCommand({ |
| 31 | + const commandLines = $derived.by(() => |
| 32 | + buildTunnelDisplayCommand({ |
28 | 33 | currentOrigin: RELAY_ORIGIN, |
29 | | - target, |
| 34 | + target: share.target, |
30 | 35 | name: effectiveName, |
31 | 36 | nameSeed, |
32 | 37 | relayUrls: [RELAY_ORIGIN], |
33 | 38 | discovery: true, |
34 | 39 | thumbnailURL: '', |
35 | | - os |
36 | | - }); |
37 | | - const lines = cmd.split('\n'); |
38 | | - // Install is first line(s), expose is the rest |
39 | | - if (os === 'windows') { |
40 | | - // Windows: first two lines are install |
41 | | - return lines.slice(0, 2).join('\n'); |
42 | | - } |
43 | | - return lines[0] ?? ''; |
44 | | - }); |
| 40 | + os, |
| 41 | + shareKind: share.kind, |
| 42 | + servePath: share.path |
| 43 | + }).split('\n') |
| 44 | + ); |
45 | 45 |
|
46 | | - const runBlock = $derived.by(() => { |
47 | | - const cmd = buildTunnelDisplayCommand({ |
48 | | - currentOrigin: RELAY_ORIGIN, |
49 | | - target, |
50 | | - name: effectiveName, |
51 | | - nameSeed, |
52 | | - relayUrls: [RELAY_ORIGIN], |
53 | | - discovery: true, |
54 | | - thumbnailURL: '', |
55 | | - os |
56 | | - }); |
57 | | - const lines = cmd.split('\n'); |
58 | | - if (os === 'windows') { |
59 | | - return lines.slice(2).join('\n'); |
60 | | - } |
61 | | - return lines.slice(1).join('\n'); |
62 | | - }); |
| 46 | + // Install is the first line(s); the expose command is the rest. |
| 47 | + const installBlock = $derived( |
| 48 | + os === 'windows' ? commandLines.slice(0, 2).join('\n') : (commandLines[0] ?? '') |
| 49 | + ); |
| 50 | + const runBlock = $derived( |
| 51 | + os === 'windows' ? commandLines.slice(2).join('\n') : commandLines.slice(1).join('\n') |
| 52 | + ); |
63 | 53 |
|
64 | 54 | const previewURL = $derived( |
65 | | - buildTunnelPreviewURL(RELAY_ORIGIN, effectiveName, target, nameSeed) |
| 55 | + buildTunnelPreviewURL(RELAY_ORIGIN, effectiveName, share.seedTarget, nameSeed) |
66 | 56 | ); |
67 | 57 |
|
68 | 58 | function handleCopy() { |
|
123 | 113 | </div> |
124 | 114 |
|
125 | 115 | <div class="space-y-5"> |
126 | | - <!-- 1. Start your local app --> |
| 116 | + <!-- 1. Paste what you want to share --> |
127 | 117 | <div class="space-y-2"> |
128 | 118 | <div class="space-y-1.5"> |
129 | 119 | <p class="text-[13px] font-semibold tracking-[0.04em] text-slate-100 sm:text-sm"> |
130 | | - 1. Start your local app |
131 | | - <span class="ml-1 normal-case tracking-normal text-slate-400"> |
132 | | - (e.g. |
133 | | - <span class="mx-1 font-mono text-slate-200">localhost:3000</span> |
134 | | - ) |
135 | | - </span> |
| 120 | + 1. Paste what you want to share |
| 121 | + </p> |
| 122 | + <p class="text-[12px] leading-5 text-slate-400"> |
| 123 | + A local port such as |
| 124 | + <span class="mx-1 font-mono text-slate-200">3000</span>, a running URL |
| 125 | + such as |
| 126 | + <span class="mx-1 font-mono text-slate-200">http://localhost:3000</span>, |
| 127 | + or a file path such as |
| 128 | + <span class="mx-1 font-mono text-slate-200" |
| 129 | + >file:///Users/me/site/index.html</span |
| 130 | + > |
| 131 | + — file paths are served as a static site straight from that folder, so |
| 132 | + nothing needs to be running locally. |
136 | 133 | </p> |
137 | 134 | </div> |
138 | 135 | </div> |
|
174 | 171 | </div> |
175 | 172 | </div> |
176 | 173 |
|
177 | | - <!-- Port + Name controls --> |
178 | | - <div class="flex flex-wrap items-center gap-x-4 gap-y-2 sm:flex-nowrap"> |
179 | | - <div class="flex shrink-0 items-center gap-2"> |
| 174 | + <!-- Share + Name controls --> |
| 175 | + <div class="space-y-2"> |
| 176 | + <div class="flex min-w-0 items-center gap-2"> |
180 | 177 | <span |
181 | 178 | class="shrink-0 text-[9px] font-semibold uppercase tracking-[0.16em] text-slate-500" |
182 | 179 | > |
183 | | - Port |
| 180 | + Share |
184 | 181 | </span> |
185 | 182 | <input |
186 | 183 | type="text" |
187 | 184 | bind:value={target} |
188 | | - placeholder={DEFAULT_HOST} |
189 | | - aria-label="Local port or address" |
190 | | - class="h-auto w-[76px] border-0 bg-transparent px-0 py-0 font-mono text-[13px] text-slate-200 shadow-none outline-none placeholder:text-slate-600" |
| 185 | + placeholder={SHARE_PLACEHOLDER} |
| 186 | + aria-label="Link, file path, or local port to share" |
| 187 | + class="h-auto min-w-0 flex-1 border-0 bg-transparent px-0 py-0 font-mono text-[13px] text-slate-200 shadow-none outline-none placeholder:text-slate-600" |
191 | 188 | /> |
| 189 | + {#if target.trim() !== ''} |
| 190 | + <span |
| 191 | + class="shrink-0 rounded px-1.5 py-0.5 text-[9px] font-semibold uppercase tracking-[0.12em] text-slate-400" |
| 192 | + style="background: rgba(255,255,255,0.06);" |
| 193 | + title="Detected share type" |
| 194 | + > |
| 195 | + {SHARE_KIND_LABEL[share.kind]} |
| 196 | + </span> |
| 197 | + {/if} |
192 | 198 | </div> |
193 | | - <div class="ml-auto flex min-w-0 items-center justify-end gap-2 sm:w-88"> |
| 199 | + <div class="flex min-w-0 items-center gap-2"> |
194 | 200 | <span |
195 | 201 | class="shrink-0 text-[9px] font-semibold uppercase tracking-[0.16em] text-slate-500" |
196 | 202 | > |
|
0 commit comments