Farmbot-Web-App/webpack/resources/reducer.ts

295 lines
8.6 KiB
TypeScript
Raw Normal View History

2017-08-17 12:36:33 -06:00
import { merge } from "lodash";
2017-06-29 12:54:02 -06:00
import { generateReducer } from "../redux/generate_reducer";
import { RestResources, ResourceIndex } from "./interfaces";
import {
TaggedResource,
ResourceName,
sanityCheck,
isTaggedResource,
2017-08-15 14:21:41 -06:00
SpecialStatus
2017-06-29 12:54:02 -06:00
} from "./tagged_resources";
import { generateUuid, arrayWrap } from "./util";
import { EditResourceParams } from "../api/interfaces";
import {
initialState as sequenceState,
sequenceReducer as sequences,
} from "../sequences/reducer";
import {
initialState as regimenState,
regimensReducer as regimens
} from "../regimens/reducer";
import { combineReducers } from "redux";
import { ReduxAction } from "../redux/interfaces";
import {
designer as farm_designer,
initialState as designerState
} from "../farm_designer/reducer";
import { ResourceReadyPayl } from "../sync/actions";
import { OFCropResponse } from "../open_farm/index";
import {
famrwareReducer as farmware,
farmwareState
} from "../farmware/reducer";
import { Actions } from "../constants";
import { maybeTagSteps as dontTouchThis } from "./sequence_tagging";
2017-06-29 12:54:02 -06:00
2017-08-28 05:49:13 -06:00
const consumerReducer = combineReducers<RestResources["consumers"]>({
2017-06-29 12:54:02 -06:00
regimens,
sequences,
farm_designer,
farmware
2017-07-17 12:34:46 -06:00
} as any); // tslint:disable-line
2017-06-29 12:54:02 -06:00
export function emptyState(): RestResources {
return {
consumers: {
sequences: sequenceState,
regimens: regimenState,
farm_designer: designerState,
farmware: farmwareState
},
loaded: [],
index: {
all: [],
byKind: {
2017-08-21 07:48:46 -06:00
webcam_feed: [],
2017-06-29 12:54:02 -06:00
device: [],
farm_events: [],
images: [],
logs: [],
peripherals: [],
crops: [],
points: [],
regimens: [],
sequences: [],
tools: [],
users: []
},
byKindAndId: {},
references: {}
}
};
}
2017-08-28 05:49:13 -06:00
const initialState: RestResources = emptyState();
2017-06-29 12:54:02 -06:00
2017-08-28 05:49:13 -06:00
const afterEach = (state: RestResources, a: ReduxAction<object>) => {
2017-06-29 12:54:02 -06:00
state.consumers = consumerReducer({
sequences: state.consumers.sequences,
regimens: state.consumers.regimens,
farm_designer: state.consumers.farm_designer,
farmware: state.consumers.farmware
}, a);
return state;
};
/** Responsible for all RESTful resources. */
export let resourceReducer = generateReducer
<RestResources>(initialState, afterEach)
.add<ResourceReadyPayl>(Actions.SAVE_SPECIAL_RESOURCE, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const data = arrayWrap(payload);
const kind = payload.name;
2017-06-29 12:54:02 -06:00
data.map((body: ResourceReadyPayl) => {
2017-08-28 05:49:13 -06:00
const crop = body.data as OFCropResponse;
2017-06-29 12:54:02 -06:00
if (crop.data) {
2017-08-28 05:49:13 -06:00
const cropInfo = crop.data.attributes;
2017-06-29 12:54:02 -06:00
addToIndex(s.index, kind, cropInfo, generateUuid(undefined, kind));
}
});
2017-06-29 12:54:02 -06:00
return s;
})
.add<TaggedResource>(Actions.SAVE_RESOURCE_OK, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const resource = payload;
2017-08-15 14:21:41 -06:00
resource.specialStatus = undefined;
2017-06-29 12:54:02 -06:00
if (resource
&& resource.body) {
switch (resource.kind) {
2017-08-21 12:56:12 -06:00
case "crops":
2017-07-17 12:34:46 -06:00
case "device":
2017-06-29 12:54:02 -06:00
case "farm_events":
case "logs":
case "peripherals":
2017-08-21 12:56:12 -06:00
case "points":
2017-06-29 12:54:02 -06:00
case "regimens":
2017-08-21 12:56:12 -06:00
case "sequences":
2017-06-29 12:54:02 -06:00
case "tools":
2017-08-21 12:56:12 -06:00
case "users":
case "webcam_feed":
2017-06-29 12:54:02 -06:00
reindexResource(s.index, resource);
dontTouchThis(resource);
2017-06-29 12:54:02 -06:00
s.index.references[resource.uuid] = resource;
break;
default:
whoops(Actions.SAVE_RESOURCE_OK, payload.kind);
}
} else {
throw new Error("Somehow, a resource was created without an ID?");
}
return s;
})
.add<TaggedResource>(Actions.DESTROY_RESOURCE_OK, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const resource = payload;
2017-06-29 12:54:02 -06:00
switch (resource.kind) {
2017-08-21 12:56:12 -06:00
case "crops":
2017-06-29 12:54:02 -06:00
case "device":
case "farm_events":
case "logs":
case "peripherals":
2017-08-21 12:56:12 -06:00
case "points":
2017-06-29 12:54:02 -06:00
case "regimens":
case "sequences":
case "tools":
2017-08-21 12:56:12 -06:00
case "users":
case "webcam_feed":
2017-08-29 21:01:15 -06:00
case "images":
2017-06-29 12:54:02 -06:00
removeFromIndex(s.index, resource);
break;
default:
whoops(Actions.DESTROY_RESOURCE_OK, payload.kind);
}
return s;
})
.add<TaggedResource>(Actions.UPDATE_RESOURCE_OK, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const uuid = payload.uuid;
2017-06-29 12:54:02 -06:00
s.index.references[uuid] = payload;
2017-08-28 05:49:13 -06:00
const tr = s.index.references[uuid];
2017-06-29 12:54:02 -06:00
if (tr) {
2017-08-15 14:21:41 -06:00
tr.specialStatus = undefined;
2017-06-29 12:54:02 -06:00
sanityCheck(tr);
dontTouchThis(tr);
2017-06-29 12:54:02 -06:00
reindexResource(s.index, tr);
return s;
} else {
throw new Error("BAD UUID IN UPDATE_RESOURCE_OK");
}
})
.add<TaggedResource>(Actions._RESOURCE_NO, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const uuid = payload.uuid;
const tr = merge(findByUuid(s.index, uuid), payload);
2017-08-15 14:21:41 -06:00
tr.specialStatus = undefined;
2017-06-29 12:54:02 -06:00
sanityCheck(tr);
return s;
})
.add<EditResourceParams>(Actions.EDIT_RESOURCE, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const uuid = payload.uuid;
const { update } = payload;
const source = merge<TaggedResource>(findByUuid(s.index, uuid),
2017-06-29 12:54:02 -06:00
{ body: update },
2017-08-16 09:15:49 -06:00
{ specialStatus: SpecialStatus.DIRTY });
2017-06-29 12:54:02 -06:00
sanityCheck(source);
payload && isTaggedResource(source);
dontTouchThis(source);
2017-06-29 12:54:02 -06:00
return s;
})
.add<EditResourceParams>(Actions.OVERWRITE_RESOURCE, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const uuid = payload.uuid;
const original = findByUuid(s.index, uuid);
2017-06-29 12:54:02 -06:00
original.body = payload.update as typeof original.body;
original.specialStatus = SpecialStatus.DIRTY;
2017-06-29 12:54:02 -06:00
sanityCheck(original);
payload && isTaggedResource(original);
dontTouchThis(original);
2017-06-29 12:54:02 -06:00
return s;
})
.add<TaggedResource>(Actions.INIT_RESOURCE, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const tr = payload;
const uuid = tr.uuid;
2017-06-29 12:54:02 -06:00
reindexResource(s.index, tr);
if (tr.kind === "logs") {
// Since logs don't come from the API all the time, they are the only
// resource (right now) that can have an id of `undefined` and not dirty.
2017-08-15 14:21:41 -06:00
findByUuid(s.index, uuid).specialStatus = undefined;
2017-06-29 12:54:02 -06:00
} else {
2017-08-15 14:21:41 -06:00
findByUuid(s.index, uuid).specialStatus = SpecialStatus.DIRTY;
2017-06-29 12:54:02 -06:00
}
sanityCheck(tr);
dontTouchThis(tr);
2017-06-29 12:54:02 -06:00
return s;
})
.add<TaggedResource>(Actions.SAVE_RESOURCE_START, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const resource = findByUuid(s.index, payload.uuid);
2017-08-15 14:21:41 -06:00
resource.specialStatus = SpecialStatus.SAVING;
2017-06-29 12:54:02 -06:00
if (!resource.body.id) { delete resource.body.id; }
return s;
})
.add<ResourceReadyPayl>(Actions.RESOURCE_READY, (s, { payload }) => {
2017-08-28 05:49:13 -06:00
const { name } = payload;
2017-06-29 12:54:02 -06:00
/** Problem: Most API resources are plural (array wrapped) resource.
* A small subset are singular (`device` and a few others),
* making `.map()` and friends unsafe.
* Solution: wrap everything in an array on the way in. */
2017-08-28 05:49:13 -06:00
const unwrapped = payload.data;
const data = arrayWrap(unwrapped);
const { index } = s;
2017-06-29 12:54:02 -06:00
s.loaded.push(name);
index.byKind[name].map(x => {
2017-08-28 05:49:13 -06:00
const resource = index.references[x];
if (resource) {
removeFromIndex(index, resource);
dontTouchThis(resource);
}
2017-06-29 12:54:02 -06:00
});
addAllToIndex(index, name, data);
return s;
});
interface HasID {
id?: number | undefined;
}
function addAllToIndex<T extends HasID>(i: ResourceIndex,
kind: ResourceName,
all: T[]) {
all.map(function (tr) {
return addToIndex(i, kind, tr, generateUuid(tr.id, kind));
});
}
function addToIndex<T>(index: ResourceIndex,
kind: ResourceName,
body: T,
uuid: string) {
2017-08-28 05:49:13 -06:00
const tr: TaggedResource =
{ kind, body, uuid, status: undefined } as any;
2017-06-29 12:54:02 -06:00
sanityCheck(tr);
index.all.push(tr.uuid);
index.byKind[tr.kind].push(tr.uuid);
if (tr.body.id) { index.byKindAndId[tr.kind + "." + tr.body.id] = tr.uuid; }
dontTouchThis(tr);
2017-06-29 12:54:02 -06:00
index.references[tr.uuid] = tr;
}
export function joinKindAndId(kind: ResourceName, id: number | undefined) {
return `${kind}.${id || 0}`;
}
2017-08-28 05:49:13 -06:00
const filterOutUuid = (tr: TaggedResource) => (uuid: string) => uuid !== tr.uuid;
2017-06-29 12:54:02 -06:00
function removeFromIndex(index: ResourceIndex, tr: TaggedResource) {
2017-08-28 05:49:13 -06:00
const { kind } = tr;
const id = tr.body.id;
2017-06-29 12:54:02 -06:00
index.all = index.all.filter(filterOutUuid(tr));
index.byKind[tr.kind] = index.byKind[tr.kind].filter(filterOutUuid(tr));
delete index.byKindAndId[joinKindAndId(kind, id)];
delete index.byKindAndId[joinKindAndId(kind, 0)];
2017-06-29 12:54:02 -06:00
delete index.references[tr.uuid];
}
function whoops(origin: string, kind: string) {
2017-08-28 05:49:13 -06:00
const msg = `${origin}/${kind}: No handler written for this one yet.`;
2017-06-29 12:54:02 -06:00
throw new Error(msg);
}
export function findByUuid(index: ResourceIndex, uuid: string): TaggedResource {
2017-08-28 05:49:13 -06:00
const x = index.references[uuid];
2017-06-29 12:54:02 -06:00
if (x && isTaggedResource(x)) {
return x;
} else {
throw new Error("BAD UUID- CANT FIND RESOURCE: " + uuid);
}
}
function reindexResource(i: ResourceIndex, r: TaggedResource) {
removeFromIndex(i, r);
addToIndex(i, r.kind, r.body, r.uuid);
}