Feat: move gallery code to gallery domain (#69)

This commit is contained in:
Andrew Vivash 2022-09-27 11:30:41 -07:00 committed by GitHub
parent 9bf8f8123f
commit 44fa0fd136
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 181 additions and 87 deletions

View File

@ -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: 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`
- `/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.
👏 You're ready to start adding custom functionality! 🚀 👏 You're ready to start adding custom functionality! 🚀
@ -110,7 +106,6 @@ Check out the [Webnative Guide](https://guide.fission.codes/developers/webnative
## 🧨 Deploy ## 🧨 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). 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 ### Netlify
@ -120,6 +115,7 @@ 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).) 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. 2. Netlify takes care of the rest. No Netlify-specific configuration is needed.
3. There is no step 3. 3. There is no step 3.
### Fission App Hosting ### 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).

View File

@ -2,4 +2,4 @@ export const appName = 'Awesome Webnative App'
export const appDescription = 'This is another awesome Webnative app.' export const appDescription = 'This is another awesome Webnative app.'
export const appURL = 'https://webnative.netlify.app' export const appURL = 'https://webnative.netlify.app'
export const appImageURL = `${appURL}/preview.png` export const appImageURL = `${appURL}/preview.png`
export const fissionServerUrl = 'runfission.com' export const ipfsGatewayUrl = 'runfission.com'

View File

@ -1,10 +1,8 @@
import * as webnative from 'webnative' import * as webnative from 'webnative'
import type FileSystem from 'webnative/fs/index'
import { asyncDebounce } from '$lib/utils' import { asyncDebounce } from '$lib/utils'
import { filesystemStore, sessionStore } from '../../stores' import { filesystemStore, sessionStore } from '../../stores'
import { getBackupStatus } from '$lib/auth/backup' import { getBackupStatus } from '$lib/auth/backup'
import { AREAS, GALLERY_DIRS } from '$lib/gallery'
export const isUsernameValid = async (username: string): Promise<boolean> => { export const isUsernameValid = async (username: string): Promise<boolean> => {
return webnative.account.isUsernameValid(username) return webnative.account.isUsernameValid(username)
@ -29,9 +27,6 @@ export const register = async (username: string): Promise<boolean> => {
const fs = await webnative.bootstrapRootFileSystem() const fs = await webnative.bootstrapRootFileSystem()
filesystemStore.set(fs) filesystemStore.set(fs)
// TODO Remove if only public and private directories are needed
await initializeFilesystem(fs)
sessionStore.update(session => ({ sessionStore.update(session => ({
...session, ...session,
username, username,
@ -41,16 +36,6 @@ export const register = async (username: string): Promise<boolean> => {
return success 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> => { export const loadAccount = async (username: string): Promise<void> => {
await checkDataRoot(username) await checkDataRoot(username)

View File

@ -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}

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import { goto } from '$app/navigation'
import { galleryStore, sessionStore, themeStore } from '../../stores' import { sessionStore, themeStore } from '$src/stores'
import { AREAS } from '$lib/gallery' import { AREAS, galleryStore } from '$routes/gallery/stores'
import Dropzone from '$components/gallery/upload/Dropzone.svelte' import Dropzone from '$routes/gallery/components/upload/Dropzone.svelte'
import ImageGallery from '$components/gallery/imageGallery/ImageGallery.svelte' import ImageGallery from '$routes/gallery/components/imageGallery/ImageGallery.svelte'
/** /**
* Tab between the public/private areas and load associated images * Tab between the public/private areas and load associated images

View File

@ -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>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import type { Image } from '$lib/gallery' import type { Image } from '$routes/gallery/lib/gallery'
export let image: Image export let image: Image
export let openModal export let openModal

View File

@ -1,15 +1,12 @@
<script lang="ts"> <script lang="ts">
import { onDestroy } from 'svelte' import { onDestroy } from 'svelte'
import { galleryStore } from '../../../stores' import { filesystemStore, sessionStore } from '$src/stores'
import { AREAS, getImagesFromWNFS } from '$lib/gallery' import { AREAS, galleryStore } from '$routes/gallery/stores'
import type { Image } from '$lib/gallery' import { getImagesFromWNFS, type Image } from '$routes/gallery/lib/gallery'
import FileUploadCard from '$components/gallery/upload/FileUploadCard.svelte' import FileUploadCard from '$routes/gallery/components/upload/FileUploadCard.svelte'
import ImageCard from '$components/gallery/imageGallery/ImageCard.svelte' import ImageCard from '$routes/gallery/components/imageGallery/ImageCard.svelte'
import ImageModal from '$components/gallery/imageGallery/ImageModal.svelte' import ImageModal from '$routes/gallery/components/imageGallery/ImageModal.svelte'
// Get images from the user's public WNFS
getImagesFromWNFS()
/** /**
* Open the ImageModal and pass it the selected `image` from the gallery * 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 // If galleryStore.selectedArea changes from private to public, re-run getImagesFromWNFS
let selectedArea = null let selectedArea = null
const unsubscribe = galleryStore.subscribe(async updatedStore => { const unsubscribeGalleryStore = galleryStore.subscribe(async updatedStore => {
// Get initial selectedArea // Get initial selectedArea
if (!selectedArea) { if (!selectedArea) {
selectedArea = updatedStore.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> </script>
<section class="overflow-hidden text-gray-700"> <section class="overflow-hidden text-gray-700">

View File

@ -1,10 +1,9 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher, onDestroy, onMount } from 'svelte' import { createEventDispatcher, onDestroy, onMount } from 'svelte'
import { deleteImageFromWNFS } from '$lib/gallery' import { ipfsGatewayUrl } from '$lib/app-info';
import { galleryStore } from '../../../stores' import { galleryStore } from '$routes/gallery/stores'
import type { Gallery, Image } from '$lib/gallery' import { deleteImageFromWNFS, type Gallery, type Image } from '$routes/gallery/lib/gallery'
import { fissionServerUrl } from '$lib/app-info'
export let image: Image export let image: Image
export let isModalOpen: boolean = false export let isModalOpen: boolean = false
@ -141,7 +140,7 @@
</div> </div>
<div class="flex flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center">
<a <a
href={`https://ipfs.${fissionServerUrl}/ipfs/${image.cid}/userland`} href={`https://ipfs.${ipfsGatewayUrl}/ipfs/${image.cid}/userland`}
target="_blank" target="_blank"
class="underline mb-4 hover:text-slate-500" class="underline mb-4 hover:text-slate-500"
> >

View File

@ -1,6 +1,9 @@
<script lang="ts"> <script lang="ts">
import {
getImagesFromWNFS,
uploadImageToWNFS
} from '$routes/gallery/lib/gallery'
import { addNotification } from '$lib/notifications' 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 * 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 the dropped files aren't images, we don't want them!
if (!file.type.match('image/*')) { if (!file.type.match('image/*')) {
addNotification('Please upload images only', 'error') addNotification('Please upload images only', 'error')
console.error('Please upload images only')
} else { } else {
await uploadImageToWNFS(file) await uploadImageToWNFS(file)
} }

View File

@ -1,7 +1,7 @@
<script lang="ts"> <script lang="ts">
import { galleryStore } from '../../../stores' import { galleryStore } from '$routes/gallery/stores'
import { handleFileInput } from '$lib/gallery' import { handleFileInput } from '$routes/gallery/lib/gallery'
import FileUploadIcon from '$components/icons/FileUploadIcon.svelte' import FileUploadIcon from '$routes/gallery/components/icons/FileUploadIcon.svelte'
import LoadingSpinner from '$components/common/LoadingSpinner.svelte' import LoadingSpinner from '$components/common/LoadingSpinner.svelte'
// Handle files uploaded directly through the file input // Handle files uploaded directly through the file input

View File

@ -1,14 +1,13 @@
import * as uint8arrays from 'uint8arrays'
import { get as getStore } from 'svelte/store' import { get as getStore } from 'svelte/store'
import * as wn from 'webnative' import * as wn from 'webnative'
import { addNotification } from '$lib/notifications' import * as uint8arrays from 'uint8arrays'
import { filesystemStore, galleryStore } from '../stores' 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 { import { filesystemStore } from '$src/stores'
PUBLIC = 'Public', import { AREAS, galleryStore } from '$routes/gallery/stores'
PRIVATE = 'Private', import { addNotification } from '$lib/notifications'
}
export type Image = { export type Image = {
cid: string cid: string
@ -26,9 +25,22 @@ export type Gallery = {
loading: boolean loading: boolean
} }
interface GalleryFile extends PuttableUnixTree, WNFile {
cid: CID
content: Uint8Array
header: {
content: Uint8Array
metadata: Metadata
}
}
type Link = {
size: number
}
export const GALLERY_DIRS = { export const GALLERY_DIRS = {
[AREAS.PUBLIC]: ['public', 'gallery'], [AREAS.PUBLIC]: ['public', 'gallery'],
[AREAS.PRIVATE]: ['private', 'gallery'], [AREAS.PRIVATE]: ['private', 'gallery']
} }
const FILE_SIZE_LIMIT = 5 const FILE_SIZE_LIMIT = 5
@ -38,7 +50,7 @@ const FILE_SIZE_LIMIT = 5
export const getImagesFromWNFS: () => Promise<void> = async () => { export const getImagesFromWNFS: () => Promise<void> = async () => {
try { try {
// Set loading: true on the galleryStore // Set loading: true on the galleryStore
galleryStore.update((store) => ({ ...store, loading: true })) galleryStore.update(store => ({ ...store, loading: true }))
const { selectedArea } = getStore(galleryStore) const { selectedArea } = getStore(galleryStore)
const isPrivate = selectedArea === AREAS.PRIVATE 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`, // The CID for private files is currently located in `file.header.content`,
// whereas the CID for public files is located in `file.cid` // 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` // 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 { return {
cid, cid,
ctime: file.header.metadata.unixMeta.ctime, ctime: (file as GalleryFile).header.metadata.unixMeta.ctime,
name, name,
private: isPrivate, private: isPrivate,
size: links[name].size, size: (links[name] as Link).size,
src, src
} }
}) })
) )
@ -79,19 +96,22 @@ export const getImagesFromWNFS: () => Promise<void> = async () => {
images.sort((a, b) => b.ctime - a.ctime) images.sort((a, b) => b.ctime - a.ctime)
// Push images to the galleryStore // Push images to the galleryStore
galleryStore.update((store) => ({
...store,
...(isPrivate ? {
privateImages: images,
} : {
publicImages: images,
}),
loading: false,
}))
} catch (error) {
galleryStore.update(store => ({ galleryStore.update(store => ({
...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() await fs.publish()
addNotification(`${image.name} image has been published`, 'success') addNotification(`${image.name} image has been published`, 'success')
} catch (error) { } catch (error) {
addNotification(error.message, '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 * Delete an image from the user's private or public WNFS
* @param name * @param name
*/ */
export const deleteImageFromWNFS: (name: string) => Promise<void> = async (name) => { export const deleteImageFromWNFS: (
name: string
) => Promise<void> = async name => {
try { try {
const { selectedArea } = getStore(galleryStore) const { selectedArea } = getStore(galleryStore)
const fs = getStore(filesystemStore) const fs = getStore(filesystemStore)
@ -166,6 +188,7 @@ export const deleteImageFromWNFS: (name: string) => Promise<void> = async (name)
} }
} catch (error) { } catch (error) {
addNotification(error.message, 'error') addNotification(error.message, 'error')
console.error(error)
} }
} }

View File

@ -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,
})

View File

@ -3,8 +3,6 @@ import type { Writable } from 'svelte/store'
import type FileSystem from 'webnative/fs/index' import type FileSystem from 'webnative/fs/index'
import { loadTheme } from '$lib/theme' import { loadTheme } from '$lib/theme'
import { AREAS } from '$lib/gallery'
import type { Gallery } from '$lib/gallery'
import type { Notification } from '$lib/notifications' import type { Notification } from '$lib/notifications'
import type { Session } from '$lib/session' import type { Session } from '$lib/session'
import type { Theme } from '$lib/theme' 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 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([]) export const notificationStore: Writable<Notification[]> = writable([])

View File

@ -25,6 +25,8 @@
"paths": { "paths": {
"$components/*": ["src/components/*"], "$components/*": ["src/components/*"],
"$lib/*": ["src/lib/*"], "$lib/*": ["src/lib/*"],
"$routes/*": ["src/routes/*"],
"$src/*": ["src/*"],
"$static/*": ["static/*"] "$static/*": ["static/*"]
} }
}, },

View File

@ -7,6 +7,8 @@ const config = {
resolve: { resolve: {
alias: { alias: {
$components: resolve('./src/components'), $components: resolve('./src/components'),
$routes: resolve('./src/routes'),
$src: resolve('./src'),
$static: resolve('./static') $static: resolve('./static')
} }
} }