282 lines
8.1 KiB
TypeScript
282 lines
8.1 KiB
TypeScript
import * as _ from "lodash";
|
|
import { generateReducer } from "../redux/generate_reducer";
|
|
import { RestResources, ResourceIndex } from "./interfaces";
|
|
import {
|
|
TaggedResource,
|
|
ResourceName,
|
|
sanityCheck,
|
|
isTaggedResource
|
|
} 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";
|
|
|
|
let consumerReducer = combineReducers<RestResources["consumers"]>({
|
|
regimens,
|
|
sequences,
|
|
farm_designer,
|
|
farmware
|
|
} as any); // tslint:disable-line
|
|
|
|
export function emptyState(): RestResources {
|
|
return {
|
|
consumers: {
|
|
sequences: sequenceState,
|
|
regimens: regimenState,
|
|
farm_designer: designerState,
|
|
farmware: farmwareState
|
|
},
|
|
loaded: [],
|
|
index: {
|
|
all: [],
|
|
byKind: {
|
|
device: [],
|
|
farm_events: [],
|
|
images: [],
|
|
logs: [],
|
|
peripherals: [],
|
|
crops: [],
|
|
points: [],
|
|
regimens: [],
|
|
sequences: [],
|
|
tools: [],
|
|
users: []
|
|
},
|
|
byKindAndId: {},
|
|
references: {}
|
|
}
|
|
};
|
|
}
|
|
|
|
let initialState: RestResources = emptyState();
|
|
|
|
let afterEach = (state: RestResources, a: ReduxAction<object>) => {
|
|
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 }) => {
|
|
let data = arrayWrap(payload);
|
|
let kind = payload.name;
|
|
data.map((body: ResourceReadyPayl) => {
|
|
let crop = body.data as OFCropResponse;
|
|
if (crop.data) {
|
|
let cropInfo = crop.data.attributes;
|
|
addToIndex(s.index, kind, cropInfo, generateUuid(undefined, kind));
|
|
}
|
|
});
|
|
return s;
|
|
})
|
|
.add<TaggedResource>(Actions.SAVE_RESOURCE_OK, (s, { payload }) => {
|
|
let resource = payload;
|
|
resource.dirty = false;
|
|
resource.saving = false;
|
|
if (resource
|
|
&& resource.body) {
|
|
switch (resource.kind) {
|
|
case "sequences":
|
|
case "device":
|
|
case "users":
|
|
case "farm_events":
|
|
case "logs":
|
|
case "peripherals":
|
|
case "crops":
|
|
case "regimens":
|
|
case "tools":
|
|
case "points":
|
|
reindexResource(s.index, resource);
|
|
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 }) => {
|
|
let resource = payload;
|
|
switch (resource.kind) {
|
|
case "device":
|
|
case "users":
|
|
case "farm_events":
|
|
case "logs":
|
|
case "peripherals":
|
|
case "crops":
|
|
case "regimens":
|
|
case "sequences":
|
|
case "tools":
|
|
case "points":
|
|
removeFromIndex(s.index, resource);
|
|
break;
|
|
default:
|
|
whoops(Actions.DESTROY_RESOURCE_OK, payload.kind);
|
|
}
|
|
return s;
|
|
})
|
|
.add<TaggedResource>(Actions.UPDATE_RESOURCE_OK, (s, { payload }) => {
|
|
let uuid = payload.uuid;
|
|
s.index.references[uuid] = payload;
|
|
let tr = s.index.references[uuid];
|
|
if (tr) {
|
|
tr.dirty = false;
|
|
tr.saving = false;
|
|
sanityCheck(tr);
|
|
reindexResource(s.index, tr);
|
|
return s;
|
|
} else {
|
|
throw new Error("BAD UUID IN UPDATE_RESOURCE_OK");
|
|
}
|
|
})
|
|
.add<TaggedResource>(Actions._RESOURCE_NO, (s, { payload }) => {
|
|
let uuid = payload.uuid;
|
|
let tr = _.merge(findByUuid(s.index, uuid), payload);
|
|
tr.dirty = true;
|
|
tr.saving = false;
|
|
sanityCheck(tr);
|
|
return s;
|
|
})
|
|
.add<EditResourceParams>(Actions.EDIT_RESOURCE, (s, { payload }) => {
|
|
let uuid = payload.uuid;
|
|
let { update } = payload;
|
|
let source = _.merge<TaggedResource>(findByUuid(s.index, uuid),
|
|
{ body: update },
|
|
{ dirty: true });
|
|
sanityCheck(source);
|
|
payload && isTaggedResource(source);
|
|
return s;
|
|
})
|
|
.add<EditResourceParams>(Actions.OVERWRITE_RESOURCE, (s, { payload }) => {
|
|
let uuid = payload.uuid;
|
|
let original = findByUuid(s.index, uuid);
|
|
original.body = payload.update as typeof original.body;
|
|
original.dirty = true;
|
|
sanityCheck(original);
|
|
payload && isTaggedResource(original);
|
|
return s;
|
|
})
|
|
.add<TaggedResource>(Actions.INIT_RESOURCE, (s, { payload }) => {
|
|
let tr = payload;
|
|
let uuid = tr.uuid;
|
|
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.
|
|
findByUuid(s.index, uuid).dirty = false;
|
|
} else {
|
|
findByUuid(s.index, uuid).dirty = true;
|
|
}
|
|
sanityCheck(tr);
|
|
return s;
|
|
})
|
|
.add<TaggedResource>(Actions.SAVE_RESOURCE_START, (s, { payload }) => {
|
|
let resource = findByUuid(s.index, payload.uuid);
|
|
resource.saving = true;
|
|
if (!resource.body.id) { delete resource.body.id; }
|
|
return s;
|
|
})
|
|
.add<ResourceReadyPayl>(Actions.RESOURCE_READY, (s, { payload }) => {
|
|
let { name } = payload;
|
|
/** 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. */
|
|
let unwrapped = payload.data;
|
|
let data = arrayWrap(unwrapped);
|
|
let { index } = s;
|
|
s.loaded.push(name);
|
|
index.byKind[name].map(x => {
|
|
let resource = index.references[x];
|
|
resource && removeFromIndex(index, resource);
|
|
});
|
|
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) {
|
|
let tr: TaggedResource = { kind, body, uuid } as any;
|
|
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; }
|
|
index.references[tr.uuid] = tr;
|
|
}
|
|
|
|
export function joinKindAndId(kind: ResourceName, id: number | undefined) {
|
|
return `${kind}.${id || 0}`;
|
|
}
|
|
|
|
let filterOutUuid = (tr: TaggedResource) => (uuid: string) => uuid !== tr.uuid;
|
|
function removeFromIndex(index: ResourceIndex, tr: TaggedResource) {
|
|
let { kind } = tr;
|
|
let id = tr.body.id;
|
|
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)];
|
|
delete index.references[tr.uuid];
|
|
}
|
|
|
|
function whoops(origin: string, kind: string) {
|
|
let msg = `${origin}/${kind}: No handler written for this one yet.`;
|
|
throw new Error(msg);
|
|
}
|
|
|
|
export function findByUuid(index: ResourceIndex, uuid: string): TaggedResource {
|
|
let x = index.references[uuid];
|
|
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);
|
|
}
|