summaryrefslogtreecommitdiff
path: root/animism-align/frontend/app/api
diff options
context:
space:
mode:
authorJules Laplace <julescarbon@gmail.com>2020-07-22 14:05:15 +0200
committerJules Laplace <julescarbon@gmail.com>2020-07-22 14:05:15 +0200
commitef78bc6a084f92b4794e987b5832240d85b6479e (patch)
treeb314b630800db6aa60f28ef0b115625e6ca176db /animism-align/frontend/app/api
parent85d4cb9addf9ca887d3440b2786665d67d9917c4 (diff)
refactor app using babel module-resolver
Diffstat (limited to 'animism-align/frontend/app/api')
-rw-r--r--animism-align/frontend/app/api/crud.actions.js51
-rw-r--r--animism-align/frontend/app/api/crud.fetch.js105
-rw-r--r--animism-align/frontend/app/api/crud.reducer.js194
-rw-r--r--animism-align/frontend/app/api/crud.types.js35
-rw-r--r--animism-align/frontend/app/api/crud.upload.js115
-rw-r--r--animism-align/frontend/app/api/index.js17
6 files changed, 517 insertions, 0 deletions
diff --git a/animism-align/frontend/app/api/crud.actions.js b/animism-align/frontend/app/api/crud.actions.js
new file mode 100644
index 0000000..86c2948
--- /dev/null
+++ b/animism-align/frontend/app/api/crud.actions.js
@@ -0,0 +1,51 @@
+import { crud_fetch } from 'app/api/crud.fetch'
+import { as_type } from 'app/api/crud.types'
+import { upload_action } from 'app/api/crud.upload'
+import { store } from 'app/store'
+
+export function crud_actions(type) {
+ const fetch_type = crud_fetch(type)
+ return [
+ 'index',
+ 'show',
+ 'create',
+ 'update',
+ 'destroy',
+ ].reduce((lookup, param) => {
+ lookup[param] = crud_action(type, param, (q) => fetch_type[param](q))
+ return lookup
+ }, {
+ action: (method, fn) => crud_action(type, method, fn),
+ upload: (fd) => upload_action(type, fd),
+ updateOption: (key, value) => dispatch => {
+ dispatch({ type: as_type(type, 'update_option'), key, value })
+ },
+ updateOptions: opt => dispatch => {
+ dispatch({ type: as_type(type, 'update_options'), opt })
+ },
+ })
+}
+
+export const crud_action = (type, method, fn) => (q, load_more) => dispatch => {
+ return new Promise ((resolve, reject) => {
+ if (method === 'index') {
+ if (store.getState()[type].index.loading) {
+ return resolve({})
+ }
+ }
+ dispatch({ type: as_type(type, method + '_loading'), load_more })
+ fn(q).then(data => {
+ if (data.status === 'ok') {
+ dispatch({ type: as_type(type, method), data, load_more })
+ resolve(data)
+ } else {
+ dispatch({ type: as_type(type, method + '_error'), error: data.error })
+ reject(data)
+ }
+ }).catch(e => {
+ console.log(e)
+ dispatch({ type: as_type(type, method + '_error') })
+ reject(e)
+ })
+ })
+}
diff --git a/animism-align/frontend/app/api/crud.fetch.js b/animism-align/frontend/app/api/crud.fetch.js
new file mode 100644
index 0000000..c88225e
--- /dev/null
+++ b/animism-align/frontend/app/api/crud.fetch.js
@@ -0,0 +1,105 @@
+import fetch from 'node-fetch'
+
+export function crud_fetch(type, tag) {
+ const uri = '/api/v1/' + type + '/' + (tag || '')
+ return {
+ index: q => {
+ return fetch(_get_url(uri, q), _get_headers())
+ .then(req => req.json())
+ .catch(error)
+ },
+
+ show: id => {
+ let url;
+ if (typeof id === 'object') {
+ url = _get_url(uri + id[0] + '/', id[1])
+ } else {
+ url = _get_url(uri + id + '/')
+ }
+ return fetch(url, _get_headers())
+ .then(req => req.json())
+ .catch(error)
+ },
+
+ create: data => {
+ return fetch(uri, post(data))
+ .then(req => req.json())
+ .catch(error)
+ },
+
+ update: data => {
+ return fetch(uri + data.id + '/', put(data))
+ .then(req => req.json())
+ .catch(error)
+ },
+
+ destroy: data => {
+ return fetch(uri + data.id + '/', destroy(data))
+ .then(req => req.json())
+ .catch(error)
+ },
+ }
+}
+
+function _get_url(_url, data) {
+ const url = new URL(window.location.origin + _url)
+ if (data) {
+ Object.keys(data).forEach(key => url.searchParams.append(key, data[key]))
+ }
+ return url
+}
+export function _get_headers() {
+ return {
+ method: 'GET',
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json',
+ },
+ }
+}
+export function post(data) {
+ return {
+ method: 'POST',
+ body: JSON.stringify(data),
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ }
+}
+export function postBody(data) {
+ return {
+ method: 'POST',
+ body: data,
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json',
+ },
+ }
+}
+export function put(data) {
+ return {
+ method: 'PUT',
+ body: JSON.stringify(data),
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ }
+}
+export function destroy(data) {
+ return {
+ method: 'DELETE',
+ body: JSON.stringify(data),
+ credentials: 'same-origin',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ },
+ }
+}
+function error(err) {
+ console.warn(err)
+}
diff --git a/animism-align/frontend/app/api/crud.reducer.js b/animism-align/frontend/app/api/crud.reducer.js
new file mode 100644
index 0000000..3ee4974
--- /dev/null
+++ b/animism-align/frontend/app/api/crud.reducer.js
@@ -0,0 +1,194 @@
+import * as types from 'app/types'
+import { getOrderedIds, getOrderedIdsFromLookup } from 'app/utils'
+import { session } from 'app/session'
+
+export const crudState = (type, options) => ({
+ index: {},
+ show: {},
+ create: {},
+ update: {},
+ destroy: {},
+ lookup: [],
+ ...options,
+})
+
+export const crudReducer = (type) => {
+ const crud_type = types[type]
+ return (state, action) => {
+ switch (action.type) {
+ // index
+ case crud_type.index_loading:
+ return {
+ ...state,
+ index: action.load_more
+ ? { ...state.index, loading: true }
+ : { loading: true },
+ }
+ case crud_type.index:
+ if (action.data.res.length) {
+ return {
+ ...state,
+ index: {
+ lookup: action.data.res.reduce((a, b) => { a[b.id] = b; return a }, action.load_more ? state.index.lookup : {}),
+ order: getOrderedIds(action.data.res, state.options.sort, action.load_more ? state.index.order : []),
+ },
+ }
+ } else {
+ Object.keys(action.data.res).forEach(key => {
+ const el = action.data.res[key]
+ el.key = key
+ el.id = el.id || key
+ })
+ return {
+ ...state,
+ index: {
+ lookup: action.data.res,
+ order: getOrderedIdsFromLookup(action.data.res, state.options.sort),
+ },
+ }
+ }
+ case crud_type.index_error:
+ return {
+ ...state,
+ index: { loading: false, error: true },
+ }
+ case crud_type.index_sort:
+ return {
+ ...state,
+ index: {
+ ...state.index,
+ order: action.data.res.map(b => b.id),
+ },
+ }
+
+ // show
+ case crud_type.show_loading:
+ return {
+ ...state,
+ show: { loading: true },
+ }
+ case crud_type.show:
+ if (!action.data) {
+ return {
+ ...state,
+ show: { not_found: true },
+ }
+ }
+ return {
+ ...state,
+ show: action.data,
+ }
+ case crud_type.show_error:
+ return {
+ ...state,
+ show: { loading: false, error: true },
+ }
+
+ //
+ // create
+ case crud_type.create_loading:
+ return {
+ ...state,
+ create: { loading: true },
+ }
+ case crud_type.create:
+ return {
+ ...state,
+ create: action.data,
+ index: addToIndex(state.index, action.data.res, state.options.sort),
+ }
+ case crud_type.create_error:
+ return {
+ ...state,
+ create: action.data,
+ }
+
+ //
+ // update
+ case crud_type.update_loading:
+ return {
+ ...state,
+ update: { loading: true },
+ }
+ case crud_type.update:
+ return {
+ ...state,
+ update: action.data,
+ index: addToIndex(state.index, action.data.res, state.options.sort),
+ show: (state.show.res && state.show.res.id === action.data.res.id) ? {
+ ...state.show.res,
+ ...action.data.res,
+ } : state.show
+ }
+ case crud_type.index_error:
+ return {
+ ...state,
+ update: { loading: false, error: true },
+ }
+
+ //
+ // destroy
+ case crud_type.destroy_loading:
+ return {
+ ...state,
+ destroy: { loading: true },
+ }
+ case crud_type.destroy:
+ return {
+ ...state,
+ index: {
+ ...(() => {
+ if (!state.index.lookup) {
+ return {}
+ }
+ delete state.index.lookup[action.data.id]
+ const _id = parseInt(action.data.id)
+ state.index.order = state.index.order.filter(id => id !== _id)
+ return { ...state.index }
+ })()
+ },
+ destroy: { loading: false },
+ }
+ case crud_type.destroy_error:
+ return {
+ ...state,
+ destroy: { error: true },
+ }
+
+ //
+ // options
+ case crud_type.update_option:
+ session.set(type + "." + action.key, action.value)
+ return {
+ ...state,
+ options: {
+ ...state.options,
+ [action.key]: action.value,
+ }
+ }
+
+ case crud_type.update_options:
+ session.setAll(
+ Object.keys(action.opt).reduce((a,b) => { a[type + '.' + b] = action.opt[b]; return a }, {})
+ )
+ return {
+ ...state,
+ options: {
+ ...action.opt,
+ }
+ }
+
+ default:
+ return state
+ }
+ }
+}
+
+const addToIndex = (index, data, sort) => {
+ const lookup = (index && index.lookup) ? {
+ ...index.lookup,
+ } : {}
+ lookup[data.id] = data
+ const order = getOrderedIdsFromLookup(lookup, sort)
+ return { lookup, order }
+}
diff --git a/animism-align/frontend/app/api/crud.types.js b/animism-align/frontend/app/api/crud.types.js
new file mode 100644
index 0000000..ac9b3f3
--- /dev/null
+++ b/animism-align/frontend/app/api/crud.types.js
@@ -0,0 +1,35 @@
+export const as_type = (a, b) => [a, b].join('_').toUpperCase()
+
+export const with_type = (type, actions) =>
+ actions.reduce((a, b) => (a[b] = as_type(type, b)) && a, {})
+
+export const crud_type = (type, actions=[]) =>
+ with_type(type, actions.concat([
+ 'index_loading',
+ 'index',
+ 'index_error',
+ 'index_sort',
+ 'show_loading',
+ 'show',
+ 'show_error',
+ 'create_loading',
+ 'create',
+ 'create_error',
+ 'update_loading',
+ 'update',
+ 'update_error',
+ 'destroy_loading',
+ 'destroy',
+ 'destroy_error',
+ 'upload_loading',
+ 'upload_progress',
+ 'upload_waiting',
+ 'upload_complete',
+ 'upload_error',
+ 'sort',
+ 'update_option',
+ 'update_options',
+ 'loading',
+ 'loaded',
+ 'error',
+ ]))
diff --git a/animism-align/frontend/app/api/crud.upload.js b/animism-align/frontend/app/api/crud.upload.js
new file mode 100644
index 0000000..b4d9d4e
--- /dev/null
+++ b/animism-align/frontend/app/api/crud.upload.js
@@ -0,0 +1,115 @@
+import { as_type } from 'app/api/crud.types'
+
+export function crud_upload(type, data, dispatch) {
+ return new Promise( (resolve, reject) => {
+ // console.log(type, data)
+ const { id } = data
+
+ const fd = new FormData()
+ if (!data.tag) {
+ data.tag = 'misc'
+ }
+
+ Object.keys(data).forEach(key => {
+ if (key.indexOf('__') !== -1) return
+ if (key === 'id') return
+ const fn_key = '__' + key + '_filename'
+ if (fn_key in data) {
+ fd.append(key, data[key], data[fn_key])
+ } else {
+ fd.append(key, data[key])
+ }
+ })
+
+ let url = id ? '/api/v1/' + type + '/' + id + '/'
+ : '/api/v1/' + type + '/'
+ // console.log(url)
+
+ const xhr = new XMLHttpRequest()
+ xhr.upload.addEventListener("progress", uploadProgress, false)
+ xhr.addEventListener("load", uploadComplete, false)
+ xhr.addEventListener("error", uploadFailed, false)
+ xhr.addEventListener("abort", uploadCancelled, false)
+ xhr.open("POST", url)
+ xhr.send(fd)
+
+ dispatch && dispatch({ type: as_type(type, 'upload_loading')})
+
+ let complete = false
+
+ function uploadProgress (e) {
+ if (e.lengthComputable) {
+ const percent = Math.round(e.loaded * 100 / e.total) || 0
+ if (percent > 99) {
+ dispatch && dispatch({
+ type: as_type(type, 'upload_waiting'),
+ percent,
+ [type]: id,
+ })
+ } else {
+ dispatch && dispatch({
+ type: as_type(type, 'upload_progress'),
+ percent,
+ [type]: id,
+ })
+ }
+ }
+ else {
+ dispatch && dispatch({
+ type: as_type(type, 'upload_error'),
+ error: 'unable to compute upload progress',
+ [type]: id,
+ })
+ }
+ }
+
+ function uploadComplete (e) {
+ let parsed;
+ try {
+ parsed = JSON.parse(e.target.responseText)
+ } catch (e) {
+ dispatch && dispatch({
+ type: as_type(type, 'upload_error'),
+ error: 'upload failed',
+ [type]: id,
+ })
+ reject(e)
+ return
+ }
+ dispatch && dispatch({
+ type: as_type(type, 'upload_complete'),
+ data: parsed,
+ [type]: id,
+ })
+ if (parsed.res) {
+ (parsed.res.length ? parsed.res : [parsed.res]).forEach(file => {
+ dispatch && dispatch({
+ type: as_type('upload', 'create'),
+ data: { res: file },
+ })
+ })
+ }
+ resolve(parsed)
+ }
+
+ function uploadFailed (evt) {
+ dispatch && dispatch({
+ type: as_type(type, 'upload_error'),
+ error: 'upload failed',
+ [type]: id,
+ })
+ reject(evt)
+ }
+
+ function uploadCancelled (evt) {
+ dispatch && dispatch({
+ type: as_type(type, 'upload_error'),
+ error: 'upload cancelled',
+ [type]: id,
+ })
+ reject(evt)
+ }
+ })
+}
+
+export const upload_action = (type, data) => dispatch => crud_upload(type, data, dispatch)
diff --git a/animism-align/frontend/app/api/index.js b/animism-align/frontend/app/api/index.js
new file mode 100644
index 0000000..f4be118
--- /dev/null
+++ b/animism-align/frontend/app/api/index.js
@@ -0,0 +1,17 @@
+// import { crud_actions } from 'api/crud.actions'
+// import * as utils from 'app/utils'
+
+/*
+for our crud events, create corresponding actions
+the actions fire a 'loading' event, call the underlying api method, and then resolve.
+so you can do ...
+ import { folderActions } from '../../api'
+ folderActions.index({ module: 'samplernn' })
+ folderActions.show(12)
+ folderActions.create({ module: 'samplernn', name: 'foo' })
+ folderActions.update(12, { module: 'pix2pix' })
+ folderActions.destroy(12, { confirm: true })
+ folderActions.upload(12, form_data)
+*/
+
+// export { utils }