diff options
| author | Jules Laplace <julescarbon@gmail.com> | 2020-07-22 14:05:15 +0200 |
|---|---|---|
| committer | Jules Laplace <julescarbon@gmail.com> | 2020-07-22 14:05:15 +0200 |
| commit | ef78bc6a084f92b4794e987b5832240d85b6479e (patch) | |
| tree | b314b630800db6aa60f28ef0b115625e6ca176db /animism-align/frontend/app/api | |
| parent | 85d4cb9addf9ca887d3440b2786665d67d9917c4 (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.js | 51 | ||||
| -rw-r--r-- | animism-align/frontend/app/api/crud.fetch.js | 105 | ||||
| -rw-r--r-- | animism-align/frontend/app/api/crud.reducer.js | 194 | ||||
| -rw-r--r-- | animism-align/frontend/app/api/crud.types.js | 35 | ||||
| -rw-r--r-- | animism-align/frontend/app/api/crud.upload.js | 115 | ||||
| -rw-r--r-- | animism-align/frontend/app/api/index.js | 17 |
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 } |
