Skip to content

Commit 648c0f9

Browse files
authored
Merge pull request #112 from JeremyVoisin/feature/storage-list-and-download
Adding DownloadLink and StorageList components
2 parents c326e17 + ae98719 commit 648c0f9

File tree

18 files changed

+291
-10
lines changed

18 files changed

+291
-10
lines changed

README.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,47 @@ Collections can also take a Firestore Query instead of a path:
279279
</Collection>
280280
```
281281

282+
### DownloadLink
283+
284+
DownloadLink provides a `link` to download a file from Firebase Storage and its `reference`.
285+
286+
```svelte
287+
<DownloadLink ref={item} let:link let:ref>
288+
<a href="{link}" download>Download {ref?.name}</a>
289+
</DownloadLink>
290+
```
291+
292+
### StorageList
293+
294+
StorageList provides a list of `items` and `prefixes` corresponding to the list of objects and sub-folders at a given Firebase Storage path.
295+
296+
```svelte
297+
<StorageList ref="/" let:list>
298+
<ul>
299+
{#if list === null}
300+
<li>Loading...</li>
301+
{:else if list.prefixes.length === 0 && list.items.length === 0}
302+
<li>Empty</li>
303+
{:else}
304+
<!-- Listing the prefixes -->
305+
{#each list.prefixes as prefix}
306+
<li>
307+
{prefix.name}
308+
</li>
309+
{/each}
310+
<!-- Listing the objects in the given folder -->
311+
{#each list.items as item}
312+
<li>
313+
{item.name}
314+
</li>
315+
{/each}
316+
{/if}
317+
</ul>
318+
</StorageList>
319+
```
320+
321+
You can combine
322+
282323
### Using Components Together
283324

284325
These components can be combined to build complex realtime apps. It's especially powerful when fetching data that requires the current user's UID or a related document's path.

dist/components/FirebaseApp.svelte

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<script>import { setFirebaseContext } from "../stores/sdk.js";
22
export let firestore;
33
export let auth;
4-
setFirebaseContext({ firestore, auth });
4+
export let storage;
5+
setFirebaseContext({ firestore, auth, storage });
56
</script>
67

78
<slot />

dist/components/FirebaseApp.svelte.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { SvelteComponent } from "svelte";
22
import type { Auth } from "firebase/auth";
33
import type { Firestore } from "firebase/firestore";
4+
import type { FirebaseStorage } from "firebase/storage";
45
declare const __propDef: {
56
props: {
67
firestore: Firestore;
78
auth: Auth;
9+
storage: FirebaseStorage;
810
};
911
events: {
1012
[evt: string]: CustomEvent<any>;

dist/stores/sdk.d.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { Firestore } from "firebase/firestore";
22
import type { Auth } from "firebase/auth";
3+
import type { FirebaseStorage } from "firebase/storage";
34
export interface FirebaseSDKContext {
45
auth?: Auth;
56
firestore?: Firestore;
7+
storage?: FirebaseStorage;
68
}
79
export declare const contextKey = "firebase";
810
export declare function setFirebaseContext(sdks: FirebaseSDKContext): void;

firebase.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"firestore": {
77
"port": 8080
88
},
9+
"storage": {
10+
"port": 9199
11+
},
912
"hosting": {
1013
"port": 5000
1114
},
@@ -14,6 +17,9 @@
1417
},
1518
"singleProjectMode": true
1619
},
20+
"storage": {
21+
"rules": "storage.rules"
22+
},
1723
"hosting": {
1824
"public": "docs/dist",
1925
"ignore": [
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import { downloadUrlStore } from '$lib/stores/storage.js';
3+
import { getFirebaseContext } from '$lib/stores/sdk.js';
4+
import type { FirebaseStorage, StorageReference } from 'firebase/storage';
5+
6+
export let ref: string | StorageReference;
7+
8+
const { storage } = getFirebaseContext();
9+
const store = downloadUrlStore(storage!, ref);
10+
11+
interface $$Slots {
12+
default: { link: string | null; ref: StorageReference | null; storage?: FirebaseStorage },
13+
loading: {},
14+
}
15+
</script>
16+
17+
{#if $store !== undefined}
18+
<slot link={$store} ref={store.reference} {storage}/>
19+
{:else}
20+
<slot name="loading" />
21+
{/if}
22+

src/lib/components/FirebaseApp.svelte

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import { setFirebaseContext } from "$lib/stores/sdk.js";
33
import type { Auth } from "firebase/auth";
44
import type { Firestore } from "firebase/firestore";
5+
import type { FirebaseStorage } from "firebase/storage";
56
67
export let firestore: Firestore;
78
export let auth: Auth;
9+
export let storage: FirebaseStorage;
810
9-
setFirebaseContext({ firestore, auth });
11+
setFirebaseContext({ firestore, auth, storage });
1012
</script>
1113

1214
<slot />

src/lib/components/StorageList.svelte

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<script lang="ts">
2+
import { storageListStore } from '$lib/stores/storage.js';
3+
import { getFirebaseContext } from '$lib/stores/sdk.js';
4+
import type { FirebaseStorage, ListResult, StorageReference } from 'firebase/storage';
5+
6+
export let ref: string | StorageReference;
7+
8+
const { storage } = getFirebaseContext();
9+
const listStore = storageListStore(storage!, ref);
10+
11+
interface $$Slots {
12+
default: { list: ListResult | null; ref: StorageReference | null; storage?: FirebaseStorage },
13+
loading: {},
14+
}
15+
</script>
16+
17+
{#if $listStore !== undefined}
18+
<slot list={$listStore} ref={listStore.reference} {storage} />
19+
{:else}
20+
<slot name="loading" />
21+
{/if}
22+

src/lib/stores/sdk.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ import { writable } from "svelte/store";
22
import type { Firestore } from "firebase/firestore";
33
import type { Auth } from "firebase/auth";
44
import { getContext, setContext } from "svelte";
5+
import type { FirebaseStorage } from "firebase/storage";
56

67

78
export interface FirebaseSDKContext {
89
auth?: Auth;
910
firestore?: Firestore;
11+
storage?: FirebaseStorage;
1012
}
1113

1214
export const contextKey = "firebase";

src/lib/stores/storage.ts

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { readable } from "svelte/store";
2+
import { getDownloadURL, list, ref } from "firebase/storage";
3+
4+
import type {
5+
StorageReference,
6+
FirebaseStorage,
7+
ListResult,
8+
} from "firebase/storage";
9+
10+
const defaultListResult: ListResult = {
11+
prefixes: [],
12+
items: [],
13+
};
14+
15+
interface StorageListStore {
16+
subscribe: (cb: (value: ListResult) => void) => void | (() => void);
17+
reference: StorageReference | null;
18+
}
19+
20+
/**
21+
* @param {FirebaseStorage} storage firebase storage instance
22+
* @param {string|StorageReference} reference file or storage item path or reference
23+
* @param {{prefixes:[], items:[]}} startWith optional default data
24+
* @returns a store with the list result
25+
*/
26+
export function storageListStore(
27+
storage: FirebaseStorage,
28+
reference: string | StorageReference,
29+
startWith: ListResult = defaultListResult
30+
): StorageListStore {
31+
32+
// Fallback for SSR
33+
if (!globalThis.window) {
34+
const { subscribe } = readable(startWith);
35+
return {
36+
subscribe,
37+
reference: null,
38+
};
39+
}
40+
41+
// Fallback for missing SDK
42+
if (!storage) {
43+
console.warn(
44+
"Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?"
45+
);
46+
const { subscribe } = readable(defaultListResult);
47+
return {
48+
subscribe,
49+
reference: null,
50+
};
51+
}
52+
53+
const storageRef = typeof reference === "string" ? ref(storage, reference) : reference;
54+
55+
const { subscribe } = readable(startWith, (set) => {
56+
list(storageRef).then((snapshot) => {
57+
set(snapshot);
58+
});
59+
});
60+
61+
return {
62+
subscribe,
63+
reference: storageRef,
64+
};
65+
}
66+
67+
interface DownloadUrlStore {
68+
subscribe: (cb: (value: string | null) => void) => void | (() => void);
69+
reference: StorageReference | null;
70+
}
71+
72+
/**
73+
* @param {FirebaseStorage} storage firebase storage instance
74+
* @param {string|StorageReference} reference file or storage item path or reference
75+
* @param {null} startWith optional default data
76+
* @returns a store with the list result
77+
*/
78+
export function downloadUrlStore(
79+
storage: FirebaseStorage,
80+
reference: string | StorageReference,
81+
startWith: string | null = null
82+
): DownloadUrlStore {
83+
84+
// Fallback for SSR
85+
if (!globalThis.window) {
86+
const { subscribe } = readable(startWith);
87+
return {
88+
subscribe,
89+
reference: null,
90+
};
91+
}
92+
93+
// Fallback for missing SDK
94+
if (!storage) {
95+
console.warn(
96+
"Cloud Storage is not initialized. Are you missing FirebaseApp as a parent component?"
97+
);
98+
const { subscribe } = readable(null);
99+
return {
100+
subscribe,
101+
reference: null,
102+
};
103+
}
104+
105+
const storageRef = typeof reference === "string" ? ref(storage, reference) : reference;
106+
107+
const { subscribe } = readable(startWith, (set) => {
108+
getDownloadURL(storageRef).then((snapshot) => {
109+
set(snapshot);
110+
});
111+
});
112+
113+
return {
114+
subscribe,
115+
reference: storageRef,
116+
};
117+
}
118+

0 commit comments

Comments
 (0)