Bug fix: Duplicate key props for folderless sequences
parent
b190fe3609
commit
435273994a
|
@ -1011,5 +1011,11 @@ export enum Actions {
|
|||
SET_CONSISTENCY = "SET_CONSISTENCY",
|
||||
PING_START = "PING_START",
|
||||
PING_OK = "PING_OK",
|
||||
PING_NO = "PING_NO"
|
||||
PING_NO = "PING_NO",
|
||||
|
||||
// Sequence Folders
|
||||
FOLDER_TOGGLE = "FOLDER_TOGGLE",
|
||||
FOLDER_TOGGLE_ALL = "FOLDER_TOGGLE_ALL",
|
||||
FOLDER_TOGGLE_EDIT = "FOLDER_EDIT",
|
||||
|
||||
}
|
||||
|
|
|
@ -2,12 +2,10 @@ import { FolderNode } from "../constants";
|
|||
import { ingest } from "../data_transfer";
|
||||
import {
|
||||
collapseAll,
|
||||
expandAll,
|
||||
findFolder,
|
||||
setFolderColor,
|
||||
toggleFolderOpenState
|
||||
} from "../actions";
|
||||
import { times, sample } from "lodash";
|
||||
import { sample } from "lodash";
|
||||
import { cloneAndClimb, climb } from "../climb";
|
||||
|
||||
// Folder structure used in tests:
|
||||
|
@ -81,13 +79,6 @@ describe("expand/collapse all", () => {
|
|||
node.open = !sample([true, false]);
|
||||
});
|
||||
|
||||
it("expands all folders", async () => {
|
||||
const open = await expandAll(halfOpen);
|
||||
climb(open, (node) => {
|
||||
expect(node.open).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("collapses all folders", async () => {
|
||||
const closed = await collapseAll(halfOpen);
|
||||
climb(closed, (node) => {
|
||||
|
@ -95,23 +86,3 @@ describe("expand/collapse all", () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("toggleFolderOpenState", () => {
|
||||
it("toggles the `open` value of a folder", () => {
|
||||
times(3, async () => {
|
||||
const node = randomNode();
|
||||
if (!node) {
|
||||
throw new Error("Impossible");
|
||||
}
|
||||
const { id } = node;
|
||||
const before = findFolder(GRAPH, id);
|
||||
const nextGraph = await toggleFolderOpenState(GRAPH, id);
|
||||
const after = findFolder(nextGraph, id);
|
||||
if (before && after) {
|
||||
expect(after.open).toEqual(!before.open);
|
||||
} else {
|
||||
fail("Could not find ID.");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -9,6 +9,7 @@ import { initSave, destroy, edit, save } from "../api/crud";
|
|||
import { Folder } from "farmbot/dist/resources/api_resources";
|
||||
import { DeepPartial } from "redux";
|
||||
import { findFolderById } from "../resources/selectors_by_id";
|
||||
import { Actions } from "../constants";
|
||||
|
||||
type TreePromise = Promise<Tree>;
|
||||
|
||||
|
@ -23,23 +24,6 @@ export const findFolder = (tree: Tree, id: number) => {
|
|||
return result;
|
||||
};
|
||||
|
||||
export const toggleFolderOpenState =
|
||||
(tree: Tree, id: number): TreePromise => {
|
||||
return Promise.resolve(cloneAndClimb(tree, (node, halt) => {
|
||||
if (node.id === id) {
|
||||
node.open = !node.open;
|
||||
halt();
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
export const expandAll =
|
||||
(tree: Tree): TreePromise => {
|
||||
return Promise.resolve(cloneAndClimb(tree, (node) => {
|
||||
node.open = true;
|
||||
}));
|
||||
};
|
||||
|
||||
export const collapseAll = (tree: Tree): TreePromise => {
|
||||
return Promise.resolve(cloneAndClimb(tree, (node) => {
|
||||
node.open = false;
|
||||
|
@ -86,7 +70,7 @@ export const createFolder = (config: DeepPartial<Folder> = {}) => {
|
|||
|
||||
export const deleteFolder = (id: number) => {
|
||||
const { index } = store.getState().resources;
|
||||
const folder = findFolderById(index, id)
|
||||
const folder = findFolderById(index, id);
|
||||
const action = destroy(folder.uuid);
|
||||
// tslint:disable-next-line:no-any
|
||||
return store.dispatch(action as any) as ReturnType<typeof action>;
|
||||
|
@ -96,3 +80,15 @@ export const moveFolderItem = (_: Tree) => Promise.reject("WIP");
|
|||
export const moveFolder = (_: Tree) => Promise.reject("WIP");
|
||||
export const searchSequencesAndFolders = (_: Tree) => Promise.reject("WIP");
|
||||
export const searchByNameOrFolder = (_: Tree) => Promise.reject("WIP");
|
||||
|
||||
export const toggleFolderOpenState = (id: number) => Promise
|
||||
.resolve(store.dispatch({ type: Actions.FOLDER_TOGGLE, payload: { id } }));
|
||||
|
||||
export const toggleFolderEditState = (id: number) => Promise
|
||||
.resolve(store.dispatch({
|
||||
type: Actions.FOLDER_TOGGLE_EDIT,
|
||||
payload: { id }
|
||||
}));
|
||||
|
||||
export const toggleAll = (payload: boolean) => Promise
|
||||
.resolve(store.dispatch({ type: Actions.FOLDER_TOGGLE_ALL, payload }));
|
||||
|
|
|
@ -13,8 +13,8 @@ interface FolderUI {
|
|||
* Not going to optimize prematurely -RC */
|
||||
content: string[];
|
||||
color: Color;
|
||||
open?: boolean;
|
||||
editing?: boolean;
|
||||
open: boolean;
|
||||
editing: boolean;
|
||||
}
|
||||
|
||||
/** A top-level directory */
|
||||
|
|
|
@ -46,14 +46,20 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => {
|
|||
...x,
|
||||
kind: "terminal",
|
||||
content: (localMetaAttributes[x.id] || {}).sequences || [],
|
||||
children: []
|
||||
open: true,
|
||||
editing: false,
|
||||
children: [],
|
||||
...(localMetaAttributes[x.id] || {})
|
||||
});
|
||||
|
||||
const medial = (x: FolderNode): FolderNodeMedial => ({
|
||||
...x,
|
||||
kind: "medial",
|
||||
open: true,
|
||||
editing: false,
|
||||
children: childrenOf(x.id).map(terminal),
|
||||
content: (localMetaAttributes[x.id] || {}).sequences || []
|
||||
content: (localMetaAttributes[x.id] || {}).sequences || [],
|
||||
...(localMetaAttributes[x.id] || {})
|
||||
});
|
||||
|
||||
childrenOf(-1).map((root) => {
|
||||
|
@ -61,8 +67,11 @@ export const ingest: IngestFn = ({ folders, localMetaAttributes }) => {
|
|||
return output.folders.push({
|
||||
...root,
|
||||
kind: "initial",
|
||||
open: true,
|
||||
editing: false,
|
||||
children,
|
||||
content: (localMetaAttributes[root.id] || {}).sequences || []
|
||||
content: (localMetaAttributes[root.id] || {}).sequences || [],
|
||||
...(localMetaAttributes[root.id] || {})
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { Page, Col, Row, BlurableInput } from "../ui";
|
|||
import { FolderUnion, RootFolderNode } from "./constants";
|
||||
import { Everything } from "../interfaces";
|
||||
import { connect } from "react-redux";
|
||||
import { createFolder, deleteFolder, setFolderName } from "./actions";
|
||||
import { createFolder, deleteFolder, setFolderName, toggleFolderOpenState } from "./actions";
|
||||
import { TaggedSequence } from "farmbot";
|
||||
import { selectAllSequences } from "../resources/selectors";
|
||||
|
||||
|
@ -24,8 +24,11 @@ const FolderNode = ({ node, sequences }: FolderNodeProps) => {
|
|||
|
||||
const subfolderBtn = <a onClick={creates}>📁</a>;
|
||||
const deleteBtn = <a onClick={deletes}>🗑️</a>;
|
||||
const toggleBtn = <a onClick={() => { }}> {node.open ? "➕" : "➖"} </a>;
|
||||
const toggleBtn = <a onClick={() => toggleFolderOpenState(node.id)}>
|
||||
{node.open ? "➕" : "➖"}
|
||||
</a>;
|
||||
const editBtn = <a onClick={() => alert("TODO")}>✎</a>;
|
||||
|
||||
const inputBox = <BlurableInput
|
||||
value={node.name}
|
||||
onCommit={({ currentTarget }) => {
|
||||
|
@ -33,7 +36,7 @@ const FolderNode = ({ node, sequences }: FolderNodeProps) => {
|
|||
}} />;
|
||||
const names = node
|
||||
.content
|
||||
.map(x => <li key={"Z" + node.id}>*{sequences[x].body.name}</li>);
|
||||
.map(x => <li key={"Z" + node.id + sequences[x].uuid}>*{sequences[x].body.name}</li>);
|
||||
|
||||
const children = <ul> {names} </ul>;
|
||||
const stuff: { jsx: JSX.Element[], margin: number } =
|
||||
|
@ -59,12 +62,12 @@ const FolderNode = ({ node, sequences }: FolderNodeProps) => {
|
|||
}
|
||||
return <div style={{ marginLeft: `${stuff.margin}px` }}>
|
||||
{toggleBtn}
|
||||
{subfolderBtn}
|
||||
{node.kind !== "terminal" && subfolderBtn}
|
||||
{deleteBtn}
|
||||
{editBtn}
|
||||
{inputBox}
|
||||
{children}
|
||||
{stuff.jsx}
|
||||
{!!node.open && children}
|
||||
{!!node.open && stuff.jsx}
|
||||
</div>;
|
||||
|
||||
};
|
||||
|
@ -86,12 +89,8 @@ export class RawFolders extends React.Component<Props, State> {
|
|||
<Col xs={12} sm={6} smOffset={3}>
|
||||
<Row>
|
||||
<input placeholder={"Search"} disabled={true} />
|
||||
<button onClick={() => createFolder()}>
|
||||
➕Folder
|
||||
</button>
|
||||
<button>
|
||||
➕Sequence
|
||||
</button>
|
||||
<button onClick={() => createFolder()}>➕Folder</button>
|
||||
<button>➕Sequence</button>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col xs={12} sm={6} smOffset={3}>
|
||||
|
|
|
@ -8,7 +8,8 @@ import {
|
|||
initResourceReducer,
|
||||
afterEach,
|
||||
beforeEach,
|
||||
folderIndexer
|
||||
folderIndexer,
|
||||
reindexFolders
|
||||
} from "./reducer_support";
|
||||
import { TaggedResource, SpecialStatus } from "farmbot";
|
||||
import { Actions } from "../constants";
|
||||
|
@ -23,6 +24,7 @@ import { farmwareState } from "../farmware/reducer";
|
|||
import { initialState as regimenState } from "../regimens/reducer";
|
||||
import { initialState as sequenceState } from "../sequences/reducer";
|
||||
import { initialState as alertState } from "../messages/reducer";
|
||||
import { climb } from "../folders/climb";
|
||||
|
||||
export const emptyState = (): RestResources => {
|
||||
return {
|
||||
|
@ -157,4 +159,40 @@ export let resourceReducer =
|
|||
payload: resource
|
||||
});
|
||||
}, s);
|
||||
});
|
||||
})
|
||||
.add<{ id: number }>(Actions.FOLDER_TOGGLE, (s, { payload }) => {
|
||||
console.log("WOOSH X 1");
|
||||
const { localMetaAttributes } = s.index.sequenceFolders;
|
||||
const record = localMetaAttributes[parseInt("" + payload.id)];
|
||||
record.open = !record.open;
|
||||
|
||||
climb(s.index.sequenceFolders.folders, (node, halt) => {
|
||||
if (node.id == payload.id) {
|
||||
node.open = !node.open;
|
||||
halt();
|
||||
}
|
||||
});
|
||||
|
||||
reindexFolders(s.index);
|
||||
|
||||
return s;
|
||||
})
|
||||
// .add<boolean>(Actions.FOLDER_TOGGLE_ALL, (s, { payload }) => {
|
||||
// const { localMetaAttributes } = s.index.sequenceFolders;
|
||||
// Object.keys(localMetaAttributes).map((x) => {
|
||||
// localMetaAttributes[parseInt("" + x)].open = payload;
|
||||
// });
|
||||
// reindexFolders(s.index);
|
||||
// return s;
|
||||
// })
|
||||
// .add<{ id: number }>(Actions.FOLDER_TOGGLE_EDIT, (s, { payload }) => {
|
||||
// const { localMetaAttributes } = s.index.sequenceFolders;
|
||||
// Object.keys(localMetaAttributes).map((x) => {
|
||||
// if (x == ("" + payload.id)) {
|
||||
// const record = localMetaAttributes[parseInt("" + x)];
|
||||
// record.editing = !record.editing;
|
||||
// }
|
||||
// });
|
||||
// return s;
|
||||
// })
|
||||
;
|
||||
|
|
|
@ -49,39 +49,42 @@ type IndexDirection =
|
|||
type IndexerCallback = (self: TaggedResource, index: ResourceIndex) => void;
|
||||
export interface Indexer extends Record<IndexDirection, IndexerCallback> { }
|
||||
|
||||
export const reindexFolders = (i: ResourceIndex) => {
|
||||
const folders = betterCompact(selectAllFolders(i)
|
||||
.map((x): FolderNode | undefined => {
|
||||
const { body } = x;
|
||||
if (typeof body.id === "number") {
|
||||
const fn: FolderNode = { id: body.id, ...body };
|
||||
return fn;
|
||||
}
|
||||
}));
|
||||
|
||||
const oldMeta = i.sequenceFolders.localMetaAttributes;
|
||||
const localMetaAttributes: Record<number, FolderMeta> = {};
|
||||
folders.map(x => {
|
||||
localMetaAttributes[x.id] = {
|
||||
...(oldMeta[x.id] || {}),
|
||||
sequences: [], // Clobber and re-init
|
||||
};
|
||||
});
|
||||
|
||||
selectAllSequences(i).map((s) => {
|
||||
const { folder_id } = s.body;
|
||||
if (folder_id) {
|
||||
(localMetaAttributes[folder_id]?.sequences || []).push(s.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
i.sequenceFolders = {
|
||||
folders: ingest({ folders, localMetaAttributes }),
|
||||
localMetaAttributes
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
export const folderIndexer: IndexerCallback = (r, i) => {
|
||||
if (r.kind === "Folder" || r.kind === "Sequence") {
|
||||
const folders = betterCompact(selectAllFolders(i)
|
||||
.map((x): FolderNode | undefined => {
|
||||
const { body } = x;
|
||||
if (typeof body.id === "number") {
|
||||
const fn: FolderNode = { id: body.id, ...body };
|
||||
return fn;
|
||||
}
|
||||
}));
|
||||
|
||||
const oldMeta = i.sequenceFolders.localMetaAttributes;
|
||||
const localMetaAttributes: Record<number, FolderMeta> = {};
|
||||
folders.map(x => {
|
||||
localMetaAttributes[x.id] = {
|
||||
editing: false,
|
||||
open: false,
|
||||
...(oldMeta[x.id] || {}),
|
||||
sequences: [], // Clobber and re-init
|
||||
};
|
||||
});
|
||||
|
||||
selectAllSequences(i).map((s) => {
|
||||
const { folder_id } = s.body;
|
||||
if (folder_id) {
|
||||
(localMetaAttributes[folder_id]?.sequences || []).push(s.uuid);
|
||||
}
|
||||
});
|
||||
|
||||
i.sequenceFolders = {
|
||||
folders: ingest({ folders, localMetaAttributes }),
|
||||
localMetaAttributes
|
||||
};
|
||||
reindexFolders(i);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
Loading…
Reference in New Issue