Feat: move gallery code to gallery domain (#69)
This commit is contained in:
parent
9bf8f8123f
commit
44fa0fd136
10
README.md
10
README.md
|
|
@ -98,11 +98,7 @@ The app template is designed to be easy for you to _make it your own._ Here's ho
|
|||
|
||||
If you're not building an image gallery, you don't need the gallery demo code, except perhaps to learn from. To get rid of it, delete:
|
||||
|
||||
- `/src/lib/gallery.svelte`
|
||||
- `/src/routes/gallery.svelte`
|
||||
- `/src/components/gallery`
|
||||
- the `galleryStore` in `/src/stores.ts`
|
||||
- the `initializeFilesystem` function in `/src/lib/auth/account.ts` creates directories used by WNFS. Change those to what you need for your app or delete them if you're not using WNFS.
|
||||
- `/src/routes/gallery`
|
||||
|
||||
👏 You're ready to start adding custom functionality! 🚀
|
||||
|
||||
|
|
@ -110,7 +106,6 @@ Check out the [Webnative Guide](https://guide.fission.codes/developers/webnative
|
|||
|
||||
## 🧨 Deploy
|
||||
|
||||
|
||||
The Webnative App Template is currently deployed as a [Netlify app](https://webnative.netlify.app) and a [Fission app](https://webnative-template.fission.app), but it should be supported on any static hosting platform (Vercel, Cloudflare Pages, etc).
|
||||
|
||||
### Netlify
|
||||
|
|
@ -120,9 +115,10 @@ In order to deploy your Webnative application on Netlify:
|
|||
1. Create a new Netlify site and connect your app's git repository. (If you don't have your application stored in a git repository, you can upload the output of a [static build](#static-build).)
|
||||
2. Netlify takes care of the rest. No Netlify-specific configuration is needed.
|
||||
3. There is no step 3.
|
||||
|
||||
### Fission App Hosting
|
||||
|
||||
A Webnative application can be published to IPFS with the [Fission CLI](https://guide.fission.codes/developers/cli) or the [Fission GitHub publish action](https://github.com/fission-suite/publish-action).
|
||||
A Webnative application can be published to IPFS with the [Fission CLI](https://guide.fission.codes/developers/cli) or the [Fission GitHub publish action](https://github.com/fission-suite/publish-action).
|
||||
|
||||
To publish with the Fission CLI:
|
||||
|
||||
|
|
|
|||
|
|
@ -2,4 +2,4 @@ export const appName = 'Awesome Webnative App'
|
|||
export const appDescription = 'This is another awesome Webnative app.'
|
||||
export const appURL = 'https://webnative.netlify.app'
|
||||
export const appImageURL = `${appURL}/preview.png`
|
||||
export const fissionServerUrl = 'runfission.com'
|
||||
export const ipfsGatewayUrl = 'runfission.com'
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
import * as webnative from 'webnative'
|
||||
import type FileSystem from 'webnative/fs/index'
|
||||
|
||||
import { asyncDebounce } from '$lib/utils'
|
||||
import { filesystemStore, sessionStore } from '../../stores'
|
||||
import { getBackupStatus } from '$lib/auth/backup'
|
||||
import { AREAS, GALLERY_DIRS } from '$lib/gallery'
|
||||
|
||||
export const isUsernameValid = async (username: string): Promise<boolean> => {
|
||||
return webnative.account.isUsernameValid(username)
|
||||
|
|
@ -29,9 +27,6 @@ export const register = async (username: string): Promise<boolean> => {
|
|||
const fs = await webnative.bootstrapRootFileSystem()
|
||||
filesystemStore.set(fs)
|
||||
|
||||
// TODO Remove if only public and private directories are needed
|
||||
await initializeFilesystem(fs)
|
||||
|
||||
sessionStore.update(session => ({
|
||||
...session,
|
||||
username,
|
||||
|
|
@ -41,16 +36,6 @@ export const register = async (username: string): Promise<boolean> => {
|
|||
return success
|
||||
}
|
||||
|
||||
/**
|
||||
* Create additional directories and files needed by the app
|
||||
*
|
||||
* @param fs FileSystem
|
||||
*/
|
||||
const initializeFilesystem = async (fs: FileSystem): Promise<void> => {
|
||||
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PUBLIC]))
|
||||
await fs.mkdir(webnative.path.directory(...GALLERY_DIRS[AREAS.PRIVATE]))
|
||||
}
|
||||
|
||||
export const loadAccount = async (username: string): Promise<void> => {
|
||||
await checkDataRoot(username)
|
||||
|
||||
|
|
@ -90,4 +75,4 @@ const checkDataRoot = async (username: string): Promise<void> => {
|
|||
resolve()
|
||||
}, 500)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
import * as wn from 'webnative'
|
||||
import type FileSystem from 'webnative/fs/index'
|
||||
|
||||
import { filesystemStore } from '$src/stores'
|
||||
import { AREAS } from '$routes/gallery/stores'
|
||||
import { GALLERY_DIRS } from '$routes/gallery/lib/gallery'
|
||||
|
||||
let fsCheckCompleted = false
|
||||
|
||||
/**
|
||||
* Create additional directories and files needed by the gallery if they don't exist
|
||||
*
|
||||
* @param fs FileSystem
|
||||
*/
|
||||
const initializeFilesystem = async (fs: FileSystem): Promise<void> => {
|
||||
const publicPathExists = await fs.exists(
|
||||
wn.path.file(...GALLERY_DIRS[AREAS.PUBLIC])
|
||||
)
|
||||
const privatePathExists = await fs.exists(
|
||||
wn.path.file(...GALLERY_DIRS[AREAS.PRIVATE])
|
||||
)
|
||||
|
||||
if (!publicPathExists) {
|
||||
await fs.mkdir(wn.path.directory(...GALLERY_DIRS[AREAS.PUBLIC]))
|
||||
}
|
||||
|
||||
if (!privatePathExists) {
|
||||
await fs.mkdir(wn.path.directory(...GALLERY_DIRS[AREAS.PRIVATE]))
|
||||
}
|
||||
|
||||
fsCheckCompleted = true
|
||||
}
|
||||
|
||||
const unsubscribe = filesystemStore.subscribe(async fs => {
|
||||
if (!fsCheckCompleted && !!fs) {
|
||||
await initializeFilesystem(fs)
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(unsubscribe)
|
||||
</script>
|
||||
|
||||
{#if fsCheckCompleted}
|
||||
<slot />
|
||||
{/if}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
<script lang="ts">
|
||||
import { goto } from '$app/navigation'
|
||||
import { onDestroy } from 'svelte'
|
||||
import { goto } from '$app/navigation'
|
||||
|
||||
import { galleryStore, sessionStore, themeStore } from '../../stores'
|
||||
import { AREAS } from '$lib/gallery'
|
||||
import Dropzone from '$components/gallery/upload/Dropzone.svelte'
|
||||
import ImageGallery from '$components/gallery/imageGallery/ImageGallery.svelte'
|
||||
import { sessionStore, themeStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import Dropzone from '$routes/gallery/components/upload/Dropzone.svelte'
|
||||
import ImageGallery from '$routes/gallery/components/imageGallery/ImageGallery.svelte'
|
||||
|
||||
/**
|
||||
* Tab between the public/private areas and load associated images
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
<script lang="ts">
|
||||
export let classes: string = 'mb-3 w-10 h-10'
|
||||
</script>
|
||||
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class={classes}
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M7 16a4 4 0 0 1-.88-7.903A5 5 0 1 1 15.9 6h.1a5 5 0 0 1 1 9.9M15 13l-3-3m0 0-3 3m3-3v12"
|
||||
/>
|
||||
</svg>
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
<script lang="ts">
|
||||
import type { Image } from '$lib/gallery'
|
||||
import type { Image } from '$routes/gallery/lib/gallery'
|
||||
|
||||
export let image: Image
|
||||
export let openModal
|
||||
|
|
@ -1,15 +1,12 @@
|
|||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte'
|
||||
|
||||
import { galleryStore } from '../../../stores'
|
||||
import { AREAS, getImagesFromWNFS } from '$lib/gallery'
|
||||
import type { Image } from '$lib/gallery'
|
||||
import FileUploadCard from '$components/gallery/upload/FileUploadCard.svelte'
|
||||
import ImageCard from '$components/gallery/imageGallery/ImageCard.svelte'
|
||||
import ImageModal from '$components/gallery/imageGallery/ImageModal.svelte'
|
||||
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
import { filesystemStore, sessionStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
|
||||
import FileUploadCard from '$routes/gallery/components/upload/FileUploadCard.svelte'
|
||||
import ImageCard from '$routes/gallery/components/imageGallery/ImageCard.svelte'
|
||||
import ImageModal from '$routes/gallery/components/imageGallery/ImageModal.svelte'
|
||||
|
||||
/**
|
||||
* Open the ImageModal and pass it the selected `image` from the gallery
|
||||
|
|
@ -23,7 +20,7 @@
|
|||
|
||||
// If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
|
||||
let selectedArea = null
|
||||
const unsubscribe = galleryStore.subscribe(async updatedStore => {
|
||||
const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
|
||||
// Get initial selectedArea
|
||||
if (!selectedArea) {
|
||||
selectedArea = updatedStore.selectedArea
|
||||
|
|
@ -35,7 +32,20 @@
|
|||
}
|
||||
})
|
||||
|
||||
onDestroy(unsubscribe)
|
||||
// Once the user has been authed, fetch the images from their file system
|
||||
let imagesFetched = false
|
||||
const unsubscribeSessionStore = sessionStore.subscribe((newState) => {
|
||||
if (newState.authed && $filesystemStore && !imagesFetched) {
|
||||
imagesFetched = true
|
||||
// Get images from the user's public WNFS
|
||||
getImagesFromWNFS()
|
||||
}
|
||||
})
|
||||
|
||||
onDestroy(() => {
|
||||
unsubscribeGalleryStore()
|
||||
unsubscribeSessionStore()
|
||||
})
|
||||
</script>
|
||||
|
||||
<section class="overflow-hidden text-gray-700">
|
||||
|
|
@ -1,10 +1,9 @@
|
|||
<script lang="ts">
|
||||
import { createEventDispatcher, onDestroy, onMount } from 'svelte'
|
||||
|
||||
import { deleteImageFromWNFS } from '$lib/gallery'
|
||||
import { galleryStore } from '../../../stores'
|
||||
import type { Gallery, Image } from '$lib/gallery'
|
||||
import { fissionServerUrl } from '$lib/app-info'
|
||||
import { ipfsGatewayUrl } from '$lib/app-info';
|
||||
import { galleryStore } from '$routes/gallery/stores'
|
||||
import { deleteImageFromWNFS, type Gallery, type Image } from '$routes/gallery/lib/gallery'
|
||||
|
||||
export let image: Image
|
||||
export let isModalOpen: boolean = false
|
||||
|
|
@ -141,7 +140,7 @@
|
|||
</div>
|
||||
<div class="flex flex-col items-center justify-center">
|
||||
<a
|
||||
href={`https://ipfs.${fissionServerUrl}/ipfs/${image.cid}/userland`}
|
||||
href={`https://ipfs.${ipfsGatewayUrl}/ipfs/${image.cid}/userland`}
|
||||
target="_blank"
|
||||
class="underline mb-4 hover:text-slate-500"
|
||||
>
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
<script lang="ts">
|
||||
import {
|
||||
getImagesFromWNFS,
|
||||
uploadImageToWNFS
|
||||
} from '$routes/gallery/lib/gallery'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
import { getImagesFromWNFS, uploadImageToWNFS } from '$lib/gallery'
|
||||
|
||||
/**
|
||||
* Detect when a user drags a file in or out of the dropzone to change the styles
|
||||
|
|
@ -28,6 +31,7 @@
|
|||
// If the dropped files aren't images, we don't want them!
|
||||
if (!file.type.match('image/*')) {
|
||||
addNotification('Please upload images only', 'error')
|
||||
console.error('Please upload images only')
|
||||
} else {
|
||||
await uploadImageToWNFS(file)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
<script lang="ts">
|
||||
import { galleryStore } from '../../../stores'
|
||||
import { handleFileInput } from '$lib/gallery'
|
||||
import FileUploadIcon from '$components/icons/FileUploadIcon.svelte'
|
||||
import { galleryStore } from '$routes/gallery/stores'
|
||||
import { handleFileInput } from '$routes/gallery/lib/gallery'
|
||||
import FileUploadIcon from '$routes/gallery/components/icons/FileUploadIcon.svelte'
|
||||
import LoadingSpinner from '$components/common/LoadingSpinner.svelte'
|
||||
|
||||
// Handle files uploaded directly through the file input
|
||||
|
|
@ -1,14 +1,13 @@
|
|||
import * as uint8arrays from 'uint8arrays'
|
||||
import { get as getStore } from 'svelte/store'
|
||||
|
||||
import * as wn from 'webnative'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
import { filesystemStore, galleryStore } from '../stores'
|
||||
import * as uint8arrays from 'uint8arrays'
|
||||
import type { CID } from 'multiformats/cid'
|
||||
import type { PuttableUnixTree, File as WNFile } from 'webnative/fs/types'
|
||||
import type { Metadata } from 'webnative/fs/metadata'
|
||||
|
||||
export enum AREAS {
|
||||
PUBLIC = 'Public',
|
||||
PRIVATE = 'Private',
|
||||
}
|
||||
import { filesystemStore } from '$src/stores'
|
||||
import { AREAS, galleryStore } from '$routes/gallery/stores'
|
||||
import { addNotification } from '$lib/notifications'
|
||||
|
||||
export type Image = {
|
||||
cid: string
|
||||
|
|
@ -26,9 +25,22 @@ export type Gallery = {
|
|||
loading: boolean
|
||||
}
|
||||
|
||||
interface GalleryFile extends PuttableUnixTree, WNFile {
|
||||
cid: CID
|
||||
content: Uint8Array
|
||||
header: {
|
||||
content: Uint8Array
|
||||
metadata: Metadata
|
||||
}
|
||||
}
|
||||
|
||||
type Link = {
|
||||
size: number
|
||||
}
|
||||
|
||||
export const GALLERY_DIRS = {
|
||||
[AREAS.PUBLIC]: ['public', 'gallery'],
|
||||
[AREAS.PRIVATE]: ['private', 'gallery'],
|
||||
[AREAS.PRIVATE]: ['private', 'gallery']
|
||||
}
|
||||
const FILE_SIZE_LIMIT = 5
|
||||
|
||||
|
|
@ -38,7 +50,7 @@ const FILE_SIZE_LIMIT = 5
|
|||
export const getImagesFromWNFS: () => Promise<void> = async () => {
|
||||
try {
|
||||
// Set loading: true on the galleryStore
|
||||
galleryStore.update((store) => ({ ...store, loading: true }))
|
||||
galleryStore.update(store => ({ ...store, loading: true }))
|
||||
|
||||
const { selectedArea } = getStore(galleryStore)
|
||||
const isPrivate = selectedArea === AREAS.PRIVATE
|
||||
|
|
@ -58,18 +70,23 @@ export const getImagesFromWNFS: () => Promise<void> = async () => {
|
|||
|
||||
// The CID for private files is currently located in `file.header.content`,
|
||||
// whereas the CID for public files is located in `file.cid`
|
||||
const cid = isPrivate ? file.header.content.toString() : file.cid.toString()
|
||||
const cid = isPrivate
|
||||
? (file as GalleryFile).header.content.toString()
|
||||
: (file as GalleryFile).cid.toString()
|
||||
|
||||
// Create a base64 string to use as the image `src`
|
||||
const src = `data:image/jpeg;base64, ${uint8arrays.toString(file.content, 'base64')}`
|
||||
const src = `data:image/jpeg;base64, ${uint8arrays.toString(
|
||||
(file as GalleryFile).content,
|
||||
'base64'
|
||||
)}`
|
||||
|
||||
return {
|
||||
cid,
|
||||
ctime: file.header.metadata.unixMeta.ctime,
|
||||
ctime: (file as GalleryFile).header.metadata.unixMeta.ctime,
|
||||
name,
|
||||
private: isPrivate,
|
||||
size: links[name].size,
|
||||
src,
|
||||
size: (links[name] as Link).size,
|
||||
src
|
||||
}
|
||||
})
|
||||
)
|
||||
|
|
@ -79,19 +96,22 @@ export const getImagesFromWNFS: () => Promise<void> = async () => {
|
|||
images.sort((a, b) => b.ctime - a.ctime)
|
||||
|
||||
// Push images to the galleryStore
|
||||
galleryStore.update((store) => ({
|
||||
...store,
|
||||
...(isPrivate ? {
|
||||
privateImages: images,
|
||||
} : {
|
||||
publicImages: images,
|
||||
}),
|
||||
loading: false,
|
||||
}))
|
||||
} catch (error) {
|
||||
galleryStore.update(store => ({
|
||||
...store,
|
||||
loading: false,
|
||||
...(isPrivate
|
||||
? {
|
||||
privateImages: images
|
||||
}
|
||||
: {
|
||||
publicImages: images
|
||||
}),
|
||||
loading: false
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
galleryStore.update(store => ({
|
||||
...store,
|
||||
loading: false
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
|
@ -131,9 +151,9 @@ export const uploadImageToWNFS: (
|
|||
await fs.publish()
|
||||
|
||||
addNotification(`${image.name} image has been published`, 'success')
|
||||
|
||||
} catch (error) {
|
||||
addNotification(error.message, 'error')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,7 +161,9 @@ export const uploadImageToWNFS: (
|
|||
* Delete an image from the user's private or public WNFS
|
||||
* @param name
|
||||
*/
|
||||
export const deleteImageFromWNFS: (name: string) => Promise<void> = async (name) => {
|
||||
export const deleteImageFromWNFS: (
|
||||
name: string
|
||||
) => Promise<void> = async name => {
|
||||
try {
|
||||
const { selectedArea } = getStore(galleryStore)
|
||||
const fs = getStore(filesystemStore)
|
||||
|
|
@ -166,6 +188,7 @@ export const deleteImageFromWNFS: (name: string) => Promise<void> = async (name)
|
|||
}
|
||||
} catch (error) {
|
||||
addNotification(error.message, 'error')
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import { writable } from 'svelte/store'
|
||||
import type { Writable } from 'svelte/store'
|
||||
|
||||
import type { Gallery } from '$routes/gallery/lib/gallery'
|
||||
|
||||
export enum AREAS {
|
||||
PUBLIC = 'Public',
|
||||
PRIVATE = 'Private'
|
||||
}
|
||||
|
||||
export const galleryStore: Writable<Gallery> = writable({
|
||||
loading: true,
|
||||
publicImages: [],
|
||||
privateImages: [],
|
||||
selectedArea: AREAS.PUBLIC,
|
||||
})
|
||||
|
|
@ -3,8 +3,6 @@ import type { Writable } from 'svelte/store'
|
|||
import type FileSystem from 'webnative/fs/index'
|
||||
|
||||
import { loadTheme } from '$lib/theme'
|
||||
import { AREAS } from '$lib/gallery'
|
||||
import type { Gallery } from '$lib/gallery'
|
||||
import type { Notification } from '$lib/notifications'
|
||||
import type { Session } from '$lib/session'
|
||||
import type { Theme } from '$lib/theme'
|
||||
|
|
@ -20,11 +18,4 @@ export const sessionStore: Writable<Session> = writable({
|
|||
|
||||
export const filesystemStore: Writable<FileSystem | null> = writable(null)
|
||||
|
||||
export const galleryStore: Writable<Gallery> = writable({
|
||||
loading: true,
|
||||
publicImages: [],
|
||||
privateImages: [],
|
||||
selectedArea: AREAS.PUBLIC,
|
||||
})
|
||||
|
||||
export const notificationStore: Writable<Notification[]> = writable([])
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
"paths": {
|
||||
"$components/*": ["src/components/*"],
|
||||
"$lib/*": ["src/lib/*"],
|
||||
"$routes/*": ["src/routes/*"],
|
||||
"$src/*": ["src/*"],
|
||||
"$static/*": ["static/*"]
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ const config = {
|
|||
resolve: {
|
||||
alias: {
|
||||
$components: resolve('./src/components'),
|
||||
$routes: resolve('./src/routes'),
|
||||
$src: resolve('./src'),
|
||||
$static: resolve('./static')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue