@netlify/spark-ui 1.26.0-alpha.5 → 1.26.0-alpha.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/assets/DropZone.css +1 -1
- package/dist/assets/SearchLink.css +1 -1
- package/dist/assets/SearchModal.css +1 -1
- package/dist/components/preact/DropZone/DropZone.d.ts +29 -7
- package/dist/components/preact/DropZone/DropZone.js +101 -128
- package/dist/components/preact/DropZone/useDropDeploy.d.ts +50 -0
- package/dist/components/preact/DropZone/useDropDeploy.js +64 -0
- package/dist/components/preact/SearchModal/components/SearchLink/SearchLink.js +6 -6
- package/dist/components/preact/SearchModal/types/search.types.d.ts +1 -1
- package/package.json +1 -1
- package/packages/components/preact/DropZone/DropZone.css +52 -91
- package/packages/components/preact/DropZone/DropZone.tsx +153 -159
- package/packages/components/preact/DropZone/useDropDeploy.ts +149 -0
- package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.css +5 -0
- package/packages/components/preact/SearchModal/components/SearchLink/SearchLink.tsx +1 -1
- package/packages/components/preact/SearchModal/types/search.types.tsx +1 -1
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { useRef, useState } from 'preact/hooks';
|
|
2
|
+
import {
|
|
3
|
+
AnonymousDropClient,
|
|
4
|
+
deployFiles,
|
|
5
|
+
getSingleNonIndexHtmlFileName,
|
|
6
|
+
hasMultipleHtmlFilesWithoutIndex,
|
|
7
|
+
type DeployProgress,
|
|
8
|
+
type DropClient,
|
|
9
|
+
type ProcessedFile,
|
|
10
|
+
} from './drop-core';
|
|
11
|
+
|
|
12
|
+
/** How the files reached the zone — carried through to analytics. */
|
|
13
|
+
export type DeploySource = 'drag' | 'browse';
|
|
14
|
+
|
|
15
|
+
/** The live site handed back once a deploy succeeds. */
|
|
16
|
+
export interface DeployedSite {
|
|
17
|
+
url: string;
|
|
18
|
+
token: string;
|
|
19
|
+
siteName: string;
|
|
20
|
+
siteId: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Finite states of a single deploy attempt. */
|
|
24
|
+
export type DeployStatus =
|
|
25
|
+
| { kind: 'idle' }
|
|
26
|
+
| { kind: 'reading' }
|
|
27
|
+
| { kind: 'creating' }
|
|
28
|
+
| { kind: 'uploading'; uploaded: number; total: number }
|
|
29
|
+
| { kind: 'processing' }
|
|
30
|
+
| { kind: 'redirecting' }
|
|
31
|
+
| { kind: 'error'; message: string };
|
|
32
|
+
|
|
33
|
+
/** Provider-agnostic analytics sink. */
|
|
34
|
+
export type TrackFn = (event: string, properties?: Record<string, unknown>) => void;
|
|
35
|
+
|
|
36
|
+
interface UseDropDeployOptions {
|
|
37
|
+
apiBase?: string;
|
|
38
|
+
client?: DropClient;
|
|
39
|
+
claimUrl: (siteName: string) => string;
|
|
40
|
+
onDeploy?: (site: DeployedSite) => void;
|
|
41
|
+
track: TrackFn;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Map a low-level deploy progress event onto our UI status. */
|
|
45
|
+
function progressToStatus(e: DeployProgress): DeployStatus {
|
|
46
|
+
switch (e.phase) {
|
|
47
|
+
case 'creating':
|
|
48
|
+
return { kind: 'creating' };
|
|
49
|
+
case 'uploading':
|
|
50
|
+
return { kind: 'uploading', uploaded: e.uploaded ?? 0, total: e.total ?? 0 };
|
|
51
|
+
case 'processing':
|
|
52
|
+
case 'ready':
|
|
53
|
+
return { kind: 'processing' };
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** Inspect the file set and return a non-blocking index.html warning, if any. */
|
|
58
|
+
function indexHtmlWarning(files: ProcessedFile[]): string | null {
|
|
59
|
+
const singleNonIndex = getSingleNonIndexHtmlFileName(files);
|
|
60
|
+
if (singleNonIndex) {
|
|
61
|
+
return `No index.html found — "${singleNonIndex}" won’t be served at the site root.`;
|
|
62
|
+
}
|
|
63
|
+
if (hasMultipleHtmlFilesWithoutIndex(files)) {
|
|
64
|
+
return 'No index.html found — your site may not have a landing page.';
|
|
65
|
+
}
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Owns the deploy state machine and its side effects (upload, local persistence,
|
|
71
|
+
* analytics, redirect), keeping DropZone a thin view over the returned `status`.
|
|
72
|
+
*/
|
|
73
|
+
export function useDropDeploy({
|
|
74
|
+
apiBase,
|
|
75
|
+
client,
|
|
76
|
+
claimUrl,
|
|
77
|
+
onDeploy,
|
|
78
|
+
track,
|
|
79
|
+
}: UseDropDeployOptions) {
|
|
80
|
+
const [status, setStatus] = useState<DeployStatus>({ kind: 'idle' });
|
|
81
|
+
const [warning, setWarning] = useState<string | null>(null);
|
|
82
|
+
const dropClient = useRef(client ?? new AnonymousDropClient(apiBase)).current;
|
|
83
|
+
|
|
84
|
+
const busy = status.kind !== 'idle' && status.kind !== 'error';
|
|
85
|
+
|
|
86
|
+
async function deploy(read: () => Promise<ProcessedFile[]>, source: DeploySource) {
|
|
87
|
+
setWarning(null);
|
|
88
|
+
setStatus({ kind: 'reading' });
|
|
89
|
+
try {
|
|
90
|
+
const files = await read();
|
|
91
|
+
if (!files.length) {
|
|
92
|
+
setStatus({ kind: 'error', message: 'No files found to deploy.' });
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A drop is the "user provided files" signal; the browse path is already
|
|
97
|
+
// tracked when the picker opens.
|
|
98
|
+
if (source === 'drag') {
|
|
99
|
+
track('dropzone_files_dropped', { method: source, file_count: files.length });
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
setWarning(indexHtmlWarning(files));
|
|
103
|
+
|
|
104
|
+
const {
|
|
105
|
+
url,
|
|
106
|
+
token,
|
|
107
|
+
deploy: result,
|
|
108
|
+
} = await deployFiles(dropClient, files, {
|
|
109
|
+
onProgress: e => setStatus(progressToStatus(e)),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Best-effort local persistence. Note this does NOT reach the dashboard:
|
|
113
|
+
// localStorage is per-origin, so a token written here (e.g. www.netlify.com)
|
|
114
|
+
// is invisible to app.netlify.com. The cross-origin handoff is the URL
|
|
115
|
+
// fragment below; this only helps if the drop and claim share an origin.
|
|
116
|
+
try {
|
|
117
|
+
localStorage.setItem('drop-token', token);
|
|
118
|
+
localStorage.setItem('dropSiteName', result.subdomain);
|
|
119
|
+
} catch {
|
|
120
|
+
/* private mode / storage disabled — non-fatal */
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
setStatus({ kind: 'redirecting' });
|
|
124
|
+
// Fired before the redirect below. Note: the navigation can cut a
|
|
125
|
+
// fire-and-forget analytics request short — see the docs for delivery
|
|
126
|
+
// hardening (sendBeacon / deferring the redirect).
|
|
127
|
+
track('dropzone_deploy_succeeded', {
|
|
128
|
+
method: source,
|
|
129
|
+
site_name: result.subdomain,
|
|
130
|
+
file_count: files.length,
|
|
131
|
+
});
|
|
132
|
+
onDeploy?.({ url, token, siteName: result.subdomain, siteId: result.id });
|
|
133
|
+
|
|
134
|
+
// Hand the drop token to the dashboard via the URL fragment — localStorage
|
|
135
|
+
// does not cross origins. The dashboard's /drop/:name page reads
|
|
136
|
+
// `#drop_token=` and persists it for the claim + post-signup steps.
|
|
137
|
+
window.location.assign(`${claimUrl(result.subdomain)}#drop_token=${token}`);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
setStatus({ kind: 'error', message: err instanceof Error ? err.message : String(err) });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function reset() {
|
|
144
|
+
setWarning(null);
|
|
145
|
+
setStatus({ kind: 'idle' });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return { status, warning, busy, deploy, reset };
|
|
149
|
+
}
|
|
@@ -109,3 +109,8 @@
|
|
|
109
109
|
--n-search-link-badge-text-color: var(--color-highlight-text);
|
|
110
110
|
--n-search-link-badge-border: var(--color-highlight-border);
|
|
111
111
|
}
|
|
112
|
+
.n-search-link-badge[data-resource='knowledge-base'] {
|
|
113
|
+
--n-search-link-badge-color: var(--color-success-bg);
|
|
114
|
+
--n-search-link-badge-text-color: var(--color-success-text);
|
|
115
|
+
--n-search-link-badge-border: var(--color-success-border);
|
|
116
|
+
}
|
|
@@ -5,7 +5,7 @@ export interface SearchLinkProps {
|
|
|
5
5
|
as?: any;
|
|
6
6
|
icon?: IconName;
|
|
7
7
|
label?: string;
|
|
8
|
-
resource?: 'blog' | 'changelog' | 'docs' | 'guide';
|
|
8
|
+
resource?: 'blog' | 'changelog' | 'docs' | 'guide' | 'knowledge-base';
|
|
9
9
|
url?: string;
|
|
10
10
|
excerpt?: string;
|
|
11
11
|
children?: ComponentChildren;
|