From 1612b80a59a675a93f8bef4da295940609c42433 Mon Sep 17 00:00:00 2001 From: Nico Schett <52858351+schettn@users.noreply.github.com> Date: Sun, 21 Nov 2021 18:30:55 +0000 Subject: [PATCH] feat(snekfinder): add open-storage-gateway backend Add the OSGBackend that communicates with osg.snek.at to store files. For that most functionality from IPFSBackend is moved to the abstract Backend. The OSGBackend and the IPFSBackend are both subclasses of the Backend class and implement seperate upload methods. --- .../snek-finder/src/backends/IPFSBackend.ts | 53 +----------------- .../snek-finder/src/backends/OSGBackend.ts | 26 +++++++++ packages/snek-finder/src/backends/backend.ts | 55 ++++++++++++++++++- .../components/organisms/SnekStudio/index.tsx | 8 +-- .../SnekFinder/SnekFinder.stories.tsx | 26 +++++++-- .../src/containers/SnekFinder/index.tsx | 20 +++++-- 6 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 packages/snek-finder/src/backends/OSGBackend.ts diff --git a/packages/snek-finder/src/backends/IPFSBackend.ts b/packages/snek-finder/src/backends/IPFSBackend.ts index 7bf69c5..8da2d1f 100644 --- a/packages/snek-finder/src/backends/IPFSBackend.ts +++ b/packages/snek-finder/src/backends/IPFSBackend.ts @@ -9,60 +9,11 @@ export const ipfs = ipfsClient.create({ }) export class IPFSBackend extends Backend { - public initBackendLink!: string - public onBackendLinkChange!: (link: string) => void - constructor(public indexKey: string = 'snek-finder-ipfs-backend') { super() this.indexKey = indexKey } - async init() { - const response = await (await fetch(this.initBackendLink)).json() - - await this.writeIndex(response) - } - - async readIndex() { - if (window) { - const getIndexData = () => { - const indexData = window.localStorage.getItem(this.indexKey) - - return indexData && JSON.parse(indexData) - } - - let indexData = getIndexData() - - if (!indexData) { - await this.init() - indexData = getIndexData() - } - - return {data: indexData} - } else { - throw new Error( - 'window not defined, make sure to load this script in the browser' - ) - } - } - - async writeIndex(index: object) { - if (window) { - // make a file from index including date in name - const indexData = JSON.stringify(index) - const indexFile = new File([indexData], `${Date.now()}.json`) - const indexUrl = await this.upload(indexFile) - - this.onBackendLinkChange(indexUrl) - - window.localStorage.setItem(this.indexKey, indexData) - } else { - throw new Error( - 'window not defined, make sure to load this script in the browser' - ) - } - } - async upload(file: File) { const {cid} = await ipfs.add({path: file.name, content: file.stream()}) @@ -70,6 +21,4 @@ export class IPFSBackend extends Backend { } } -const backend = new IPFSBackend('snek-finder-ipfs-backend-root') - -export default backend +export default new IPFSBackend('snek-finder-ipfs-backend-root') diff --git a/packages/snek-finder/src/backends/OSGBackend.ts b/packages/snek-finder/src/backends/OSGBackend.ts new file mode 100644 index 0000000..653e58e --- /dev/null +++ b/packages/snek-finder/src/backends/OSGBackend.ts @@ -0,0 +1,26 @@ +import {Backend} from './backend' + +export class OSGBackend extends Backend { + constructor(public indexKey: string = 'snek-finder-osg-backend') { + super() + this.indexKey = indexKey + } + + async upload(file: File) { + const url = 'https://osg.snek.at/storage' + + const formData = new FormData() + formData.append('file', file) + + const resp = await fetch(url, { + body: formData, + method: 'POST' + }) + + const json = await resp.json() + + return `${url}/${json.file_id}` + } +} + +export default new OSGBackend('snek-finder-osg-backend-root') diff --git a/packages/snek-finder/src/backends/backend.ts b/packages/snek-finder/src/backends/backend.ts index 5143fd0..33df2f7 100644 --- a/packages/snek-finder/src/backends/backend.ts +++ b/packages/snek-finder/src/backends/backend.ts @@ -1,5 +1,56 @@ export abstract class Backend { - abstract readIndex(): Promise - abstract writeIndex(index: object): Promise + public initBackendLink?: string + public onBackendLinkChange!: (link: string) => void + + public abstract indexKey: string + abstract upload(file: File): Promise + + async init() { + if (this.initBackendLink) { + const response = await (await fetch(this.initBackendLink)).json() + + await this.writeIndex(response) + } + } + + async readIndex() { + if (window) { + const getIndexData = () => { + const indexData = window.localStorage.getItem(this.indexKey) + + return indexData && JSON.parse(indexData) + } + + let indexData = getIndexData() + + if (!indexData) { + await this.init() + indexData = getIndexData() + } + + return {data: indexData} + } else { + throw new Error( + 'window not defined, make sure to load this script in the browser' + ) + } + } + + async writeIndex(index: object) { + if (window) { + // make a file from index including date in name + const indexData = JSON.stringify(index) + const indexFile = new File([indexData], `${Date.now()}.json`) + const indexUrl = await this.upload(indexFile) + + this.onBackendLinkChange(indexUrl) + + window.localStorage.setItem(this.indexKey, indexData) + } else { + throw new Error( + 'window not defined, make sure to load this script in the browser' + ) + } + } } diff --git a/packages/snek-finder/src/components/organisms/SnekStudio/index.tsx b/packages/snek-finder/src/components/organisms/SnekStudio/index.tsx index da485f1..bb36c4e 100644 --- a/packages/snek-finder/src/components/organisms/SnekStudio/index.tsx +++ b/packages/snek-finder/src/components/organisms/SnekStudio/index.tsx @@ -6,7 +6,7 @@ export type SnekStudioProps = { /** * Called when the editor should be closed with saving the editing state */ - onComplete(dataURL: string): void + onComplete(file: Blob | null, dataURL: string): void /** * Called when the editor should be closed without saving the editing state */ @@ -39,9 +39,9 @@ const SnekStudio: React.FC = props => { src={props.src} onClose={props.onClose} onComplete={({canvas}: {canvas: HTMLCanvasElement}) => { - const dataURL = canvas.toDataURL('image/png') - - props.onComplete(dataURL) + canvas.toBlob(blob => { + props.onComplete(blob, canvas.toDataURL('image/png')) + }) }} onBeforeComplete={() => false} /> diff --git a/packages/snek-finder/src/containers/SnekFinder/SnekFinder.stories.tsx b/packages/snek-finder/src/containers/SnekFinder/SnekFinder.stories.tsx index 565f5b0..e472a7a 100644 --- a/packages/snek-finder/src/containers/SnekFinder/SnekFinder.stories.tsx +++ b/packages/snek-finder/src/containers/SnekFinder/SnekFinder.stories.tsx @@ -3,6 +3,7 @@ import React from 'react' import SnekFinder, {SnekFinderProps} from '.' import IPFSBackend from '../../backends/IPFSBackend' +import OSGBackend from '../../backends/OSGBackend' export default { title: 'Applications/SnekFinder', @@ -11,7 +12,7 @@ export default { const Template: Story = args => -export const Primary: Story = Template.bind({}) +export const IPFS: Story = Template.bind({}) IPFSBackend.onBackendLinkChange = (link: string) => { console.log(link) @@ -20,13 +21,30 @@ IPFSBackend.onBackendLinkChange = (link: string) => { IPFSBackend.initBackendLink = 'https://cloudflare-ipfs.com/ipfs/QmSw2QEGRx9PzBXsxt5HoKiong1hkWYN8pNwLKqwNPgaiR' -Primary.args = {backend: IPFSBackend} +IPFS.args = {backend: IPFSBackend} -export const Selector: Story = Template.bind({}) +export const IPFSSelector: Story = Template.bind({}) -Selector.args = { +IPFSSelector.args = { backend: IPFSBackend, mode: 'selector', onSelectorSelect: item => console.log(item), onSelectorClose: () => console.log('close') } + +export const OpenStorage: Story = Template.bind({}) + +OSGBackend.onBackendLinkChange = (link: string) => { + console.log(link) +} + +OpenStorage.args = {backend: OSGBackend, mode: 'browser'} + +export const OpenStorageSelector: Story = Template.bind({}) + +OpenStorageSelector.args = { + backend: OSGBackend, + mode: 'selector', + onSelectorSelect: item => console.log(item), + onSelectorClose: () => console.log('close') +} diff --git a/packages/snek-finder/src/containers/SnekFinder/index.tsx b/packages/snek-finder/src/containers/SnekFinder/index.tsx index ff41533..11db5f0 100644 --- a/packages/snek-finder/src/containers/SnekFinder/index.tsx +++ b/packages/snek-finder/src/containers/SnekFinder/index.tsx @@ -112,11 +112,23 @@ const SnekFinder: React.FC = ({backend, ...props}) => { {showModal && showModal.type === 'SNEK_STUDIO' && file && ( { - const newData = update(data, {[showModal.uuid]: {src: {$set: src}}}) + onComplete={async (blob, dataURL) => { + // convert dataUri to blob - setData(newData) - backend.writeIndex(newData) + setData(update(data, {[showModal.uuid]: {src: {$set: dataURL}}})) + + // upload blob to backend + if (blob) { + const url = await backend.upload(new File([blob], file.name)) + + const newData = update(data, { + [showModal.uuid]: {src: {$set: url}} + }) + + setData(newData) + + backend.writeIndex(newData) + } }} onClose={() => setShowModal({...showModal, type: 'IMAGE_VIEWER'})} />