202 lines
6.3 KiB
TypeScript
202 lines
6.3 KiB
TypeScript
import {
|
|
TaggedResource,
|
|
ResourceName,
|
|
isTaggedResource,
|
|
TaggedSequence,
|
|
} from "../resources/tagged_resources";
|
|
import { GetState, ReduxAction } from "../redux/interfaces";
|
|
import { API } from "./index";
|
|
import axios from "axios";
|
|
import { updateOK, updateNO, destroyOK, destroyNO } from "../resources/actions";
|
|
import { UnsafeError } from "../interfaces";
|
|
import { findByUuid } from "../resources/reducer";
|
|
import { generateUuid } from "../resources/util";
|
|
import { defensiveClone, HttpData } from "../util";
|
|
import { EditResourceParams } from "./interfaces";
|
|
import { ResourceIndex } from "../resources/interfaces";
|
|
import { SequenceBodyItem } from "farmbot/dist";
|
|
import * as _ from "lodash";
|
|
import { Actions } from "../constants";
|
|
|
|
export function edit(tr: TaggedResource, changes: Partial<typeof tr.body>):
|
|
ReduxAction<EditResourceParams> {
|
|
return {
|
|
type: Actions.EDIT_RESOURCE,
|
|
payload: { uuid: tr.uuid, update: changes }
|
|
};
|
|
}
|
|
|
|
/** Rather than update (patch) a TaggedResource, this method will overwrite
|
|
* everything within the `.body` property. */
|
|
export function overwrite(tr: TaggedResource,
|
|
changeset: typeof tr.body):
|
|
ReduxAction<EditResourceParams> {
|
|
|
|
return {
|
|
type: Actions.OVERWRITE_RESOURCE,
|
|
payload: { uuid: tr.uuid, update: changeset }
|
|
};
|
|
}
|
|
|
|
interface EditStepProps {
|
|
step: Readonly<SequenceBodyItem>;
|
|
sequence: Readonly<TaggedSequence>;
|
|
index: number;
|
|
/** Callback provides a fresh, defensively cloned copy of the
|
|
* original step. Perform modifications to the resource within this
|
|
* callback */
|
|
executor(stepCopy: SequenceBodyItem): void;
|
|
}
|
|
|
|
/** Editing sequence steps is a tedious process. Use this function in place
|
|
* of `edit()` or `overwrite`. */
|
|
export function editStep({ step, sequence, index, executor }: EditStepProps) {
|
|
// https://en.wikipedia.org/wiki/NeXTSTEP
|
|
let nextStep = defensiveClone(step);
|
|
let nextSeq = defensiveClone(sequence);
|
|
// Let the developer safely perform mutations here:
|
|
executor(nextStep);
|
|
nextSeq.body.body = nextSeq.body.body || [];
|
|
nextSeq.body.body[index] = nextStep;
|
|
return overwrite(sequence, nextSeq.body);
|
|
}
|
|
|
|
/** Initialize (but don't save) an indexed / tagged resource. */
|
|
export function init(resource: TaggedResource): ReduxAction<TaggedResource> {
|
|
resource.body.id = 0;
|
|
resource.dirty = true;
|
|
/** Technically, this happens in the reducer, but I like to be extra safe. */
|
|
resource.uuid = generateUuid(resource.body.id, resource.kind);
|
|
return { type: Actions.INIT_RESOURCE, payload: resource };
|
|
}
|
|
|
|
export function initSave(resource: TaggedResource) {
|
|
return function (dispatch: Function, getState: GetState) {
|
|
let action = init(resource);
|
|
if (resource.body.id === 0) { delete resource.body.id; }
|
|
dispatch(action);
|
|
let nextState = getState().resources.index;
|
|
let tr = findByUuid(nextState, action.payload.uuid);
|
|
return dispatch(save(tr.uuid));
|
|
};
|
|
}
|
|
|
|
export function save(uuid: string) {
|
|
return function (dispatch: Function, getState: GetState) {
|
|
let resource = findByUuid(getState().resources.index, uuid);
|
|
dispatch({ type: "SAVE_RESOURCE_START", payload: resource });
|
|
return dispatch(update(uuid));
|
|
};
|
|
}
|
|
|
|
function update(uuid: string) {
|
|
return function (dispatch: Function, getState: GetState) {
|
|
return updateViaAjax(getState().resources.index, uuid, dispatch);
|
|
};
|
|
}
|
|
|
|
export function destroy(uuid: string) {
|
|
return function (dispatch: Function, getState: GetState) {
|
|
let resource = findByUuid(getState().resources.index, uuid);
|
|
let maybeProceed = confirmationChecker(resource);
|
|
return maybeProceed(() => {
|
|
if (resource.body.id) {
|
|
return axios
|
|
.delete(urlFor(resource.kind) + resource.body.id)
|
|
.then(function (resp: HttpData<typeof resource.body>) {
|
|
dispatch(destroyOK(resource));
|
|
})
|
|
.catch(function (err: UnsafeError) {
|
|
dispatch(destroyNO({ err, uuid }));
|
|
return Promise.reject(err);
|
|
});
|
|
} else {
|
|
dispatch(destroyOK(resource));
|
|
return Promise.resolve("");
|
|
}
|
|
}) || Promise.reject("User pressed cancel");
|
|
};
|
|
}
|
|
|
|
export function saveAll(input: TaggedResource[],
|
|
callback: () => void = _.noop,
|
|
errBack: (err: UnsafeError) => void = _.noop) {
|
|
return function (dispatch: Function, getState: GetState) {
|
|
/** Perf issues maybe? RC - Mar 2017 */
|
|
let p = input.filter(x => x.dirty).map(tts => dispatch(save(tts.uuid)));
|
|
Promise.all(p).then(callback, errBack);
|
|
};
|
|
}
|
|
|
|
export function urlFor(tag: ResourceName) {
|
|
const OPTIONS: Partial<Record<ResourceName, string>> = {
|
|
sequences: API.current.sequencesPath,
|
|
tools: API.current.toolsPath,
|
|
farm_events: API.current.farmEventsPath,
|
|
regimens: API.current.regimensPath,
|
|
peripherals: API.current.peripheralsPath,
|
|
points: API.current.pointsPath,
|
|
users: API.current.usersPath,
|
|
device: API.current.devicePath,
|
|
images: API.current.imagesPath,
|
|
logs: API.current.logsPath
|
|
};
|
|
let url = OPTIONS[tag];
|
|
if (url) {
|
|
return url;
|
|
} else {
|
|
throw new Error(`No resource/URL handler for ${tag} yet.
|
|
Consider adding one to crud.ts`);
|
|
}
|
|
}
|
|
|
|
/** Shared functionality in create() and update(). */
|
|
function updateViaAjax(index: ResourceIndex,
|
|
uuid: string,
|
|
dispatch: Function) {
|
|
let resource = findByUuid(index, uuid);
|
|
let { body, kind } = resource;
|
|
let verb: "post" | "put";
|
|
let url = urlFor(kind);
|
|
if (body.id) {
|
|
verb = "put";
|
|
url += body.id;
|
|
} else {
|
|
verb = "post";
|
|
}
|
|
return axios[verb](url, body)
|
|
.then(function (resp: HttpData<typeof resource.body>) {
|
|
let r1 = defensiveClone(resource);
|
|
let r2 = { body: defensiveClone(resp.data) };
|
|
let newTR = _.assign({}, r1, r2);
|
|
if (isTaggedResource(newTR)) {
|
|
dispatch(updateOK(newTR));
|
|
} else {
|
|
throw new Error("Just saved a malformed TR.");
|
|
}
|
|
})
|
|
.catch(function (err: UnsafeError) {
|
|
dispatch(updateNO({ err, uuid }));
|
|
return Promise.reject(err);
|
|
});
|
|
}
|
|
|
|
let MUST_CONFIRM_LIST: ResourceName[] = [
|
|
"farm_events",
|
|
"points",
|
|
"sequences",
|
|
"regimens"
|
|
];
|
|
|
|
let confirmationChecker = (resource: TaggedResource) =>
|
|
<T>(proceed: () => T): T | undefined => {
|
|
if (MUST_CONFIRM_LIST.includes(resource.kind)) {
|
|
if (confirm("Are you sure you want to delete this item?")) {
|
|
return proceed();
|
|
} else {
|
|
return undefined;
|
|
}
|
|
}
|
|
return proceed();
|
|
};
|