From 8edaf2b1a2dd600a170aa7db50ab9c5337482f1d Mon Sep 17 00:00:00 2001 From: James Porlante Date: Wed, 5 Dec 2018 16:30:10 +0700 Subject: [PATCH 01/62] (chore) add client to plugin --- .../navigation/ client/containers/index.js | 1 + .../navigationDashboardContainer.js | 14 ++++++++ .../plugins/core/navigation/ client/index.js | 2 ++ .../navigation/ client/templates/index.js | 2 ++ .../templates/navigationDashboard.html | 5 +++ .../ client/templates/navigationDashboard.js | 9 +++++ imports/plugins/core/navigation/register.js | 36 ++++++++++++++++++- 7 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 imports/plugins/core/navigation/ client/containers/index.js create mode 100644 imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js create mode 100644 imports/plugins/core/navigation/ client/index.js create mode 100644 imports/plugins/core/navigation/ client/templates/index.js create mode 100644 imports/plugins/core/navigation/ client/templates/navigationDashboard.html create mode 100644 imports/plugins/core/navigation/ client/templates/navigationDashboard.js diff --git a/imports/plugins/core/navigation/ client/containers/index.js b/imports/plugins/core/navigation/ client/containers/index.js new file mode 100644 index 00000000000..0861ee5318f --- /dev/null +++ b/imports/plugins/core/navigation/ client/containers/index.js @@ -0,0 +1 @@ +import "./navigationDashboardContainer"; diff --git a/imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js b/imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js new file mode 100644 index 00000000000..9b1699fbb38 --- /dev/null +++ b/imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js @@ -0,0 +1,14 @@ +import React, { Component } from "react"; +import { registerComponent } from "@reactioncommerce/reaction-components"; + + +class NavigationDashboardContainer extends Component { + render() { + return

Hello World

; + } +} + + +registerComponent("NavigationDashboard", NavigationDashboardContainer); + +export default NavigationDashboardContainer; diff --git a/imports/plugins/core/navigation/ client/index.js b/imports/plugins/core/navigation/ client/index.js new file mode 100644 index 00000000000..d63207b4606 --- /dev/null +++ b/imports/plugins/core/navigation/ client/index.js @@ -0,0 +1,2 @@ +import "./containers"; +import "./templates"; diff --git a/imports/plugins/core/navigation/ client/templates/index.js b/imports/plugins/core/navigation/ client/templates/index.js new file mode 100644 index 00000000000..d0a047f237a --- /dev/null +++ b/imports/plugins/core/navigation/ client/templates/index.js @@ -0,0 +1,2 @@ +import "./navigationDashboard.html"; +import "./navigationDashboard.js"; diff --git a/imports/plugins/core/navigation/ client/templates/navigationDashboard.html b/imports/plugins/core/navigation/ client/templates/navigationDashboard.html new file mode 100644 index 00000000000..c3adc396221 --- /dev/null +++ b/imports/plugins/core/navigation/ client/templates/navigationDashboard.html @@ -0,0 +1,5 @@ + diff --git a/imports/plugins/core/navigation/ client/templates/navigationDashboard.js b/imports/plugins/core/navigation/ client/templates/navigationDashboard.js new file mode 100644 index 00000000000..b07f531e5cc --- /dev/null +++ b/imports/plugins/core/navigation/ client/templates/navigationDashboard.js @@ -0,0 +1,9 @@ + +import { Components } from "@reactioncommerce/reaction-components"; +import { Template } from "meteor/templating"; + +Template.navigationDashboard.helpers({ + NavigationDashboard() { + return Components.NavigationDashboard; + } +}); diff --git a/imports/plugins/core/navigation/register.js b/imports/plugins/core/navigation/register.js index 0466aaf8bb5..2b6e954097e 100644 --- a/imports/plugins/core/navigation/register.js +++ b/imports/plugins/core/navigation/register.js @@ -13,5 +13,39 @@ Reaction.registerPackage({ resolvers }, mutations, - queries + queries, + registry: [ + { + provides: ["settings"], + label: "Navigation", + description: "Manage navigation", + route: "/dashboard/navigation", + icon: "fa fa-bars", + container: "core", + template: "navigationDashboard", + name: "navigation-dashboard", + workflow: "navigationWorkflow", + priority: 2, + meta: { + actionView: { + dashboardSize: "lg" + } + } + } + ], + layout: [{ + workflow: "navigationWorkflow", + layout: "coreLayout", + theme: "default", + enabled: true, + structure: { + template: "navigationDashboard", + layoutHeader: "NavBar", + layoutFooter: "", + notFound: "notFound", + dashboardControls: "", + dashboardHeaderControls: "", + adminControlsFooter: "adminControlsFooter" + } + }] }); From f5378bec1efd29fd7ef53cf625438386522de00e Mon Sep 17 00:00:00 2001 From: James Porlante Date: Thu, 24 Jan 2019 23:24:23 +0700 Subject: [PATCH 02/62] (chore) add react dnd --- .../collections/schemas/navigationTrees.js | 7 + .../shop/settings/ShopBrandMediaManager.js | 9 +- .../navigationDashboardContainer.js | 14 - .../v1/NavigationDashboard.js | 134 + .../NavigationDashboard/v1/index.js | 1 + .../v1/NavigationItemCard.js | 271 ++ .../components/NavigationItemCard/v1/index.js | 1 + .../NavigationItemForm/NavigationItemForm.js | 180 + .../components/NavigationItemForm/index.js | 1 + .../v1/NavigationItemTabs.js | 90 + .../components/NavigationItemTabs/v1/index.js | 1 + .../v1/NavigationItemsList.js | 59 + .../NavigationItemsList/v1/index.js | 1 + .../v1/NavigationTreeContainer.js | 96 + .../NavigationTreeContainer/v1/index.js | 1 + .../client/components/NavigationTreeNode.js | 96 + .../client/components/TagsList/v1/TagsList.js | 35 + .../client/components/TagsList/v1/index.js | 1 + .../core/navigation/client/constants/index.js | 3 + .../{ client => client}/containers/index.js | 0 .../navigationDashboardContainer.js | 31 + .../client/hocs/defaultNavigationTreeQuery.js | 28 + .../core/navigation/client/hocs/fragments.js | 20 + .../client/hocs/navigationItemsQuery.js | 14 + .../core/navigation/client/hocs/queries.txt | 114 + .../client/hocs/withCreateNavigationItem.js | 42 + .../client/hocs/withDefaultNavigationTree.js | 53 + .../hocs/withDefaultNavigationTreeId.js | 31 + .../client/hocs/withNavigationItems.js | 47 + .../client/hocs/withNavigationUIStore.js | 711 ++++ .../core/navigation/client/hocs/withTags.js | 65 + .../client/hocs/withUpdateNavigationItem.js | 42 + .../navigation/{ client => client}/index.js | 1 + .../core/navigation/client/styles.less | 10 + .../navigation/client/svg/iconChevronDown.js | 23 + .../navigation/client/svg/iconChevronRight.js | 23 + .../navigation/client/svg/iconEllipsisV.js | 31 + .../core/navigation/client/svg/iconFile.js | 23 + .../core/navigation/client/svg/iconPencil.js | 27 + .../core/navigation/client/svg/iconTag.js | 23 + .../core/navigation/client/svg/iconTimes.js | 23 + .../{ client => client}/templates/index.js | 0 .../templates/navigationDashboard.html | 0 .../templates/navigationDashboard.js | 0 .../navigation/client/utils/treeDataUtils.js | 1198 ++++++ .../core/navigation/server/i18n/en.json | 16 + .../core/navigation/server/i18n/index.js | 5 + .../plugins/core/navigation/server/index.js | 1 + .../server/no-meteor/schemas/schema.graphql | 6 + .../xforms/xformNavigationTreeItem.js | 3 +- .../ui-tagnav/client/components/tagNav.js | 40 +- .../core/ui/client/components/app/app.js | 1 - .../client/components/media/mediaGallery.js | 4 +- .../ui/client/components/media/mediaItem.js | 10 +- .../core/ui/client/components/tags/tagItem.js | 13 +- .../ui/client/containers/tagListContainer.js | 34 +- .../ui/client/providers/dragDropProvider.js | 14 - .../client/components/variant.js | 9 +- .../client/containers/variantList.js | 20 +- package-lock.json | 3365 +++++++++++++++-- package.json | 10 +- 61 files changed, 6762 insertions(+), 370 deletions(-) delete mode 100644 imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemForm/NavigationItemForm.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemForm/index.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemsList/v1/NavigationItemsList.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js create mode 100644 imports/plugins/core/navigation/client/components/NavigationTreeNode.js create mode 100644 imports/plugins/core/navigation/client/components/TagsList/v1/TagsList.js create mode 100644 imports/plugins/core/navigation/client/components/TagsList/v1/index.js create mode 100644 imports/plugins/core/navigation/client/constants/index.js rename imports/plugins/core/navigation/{ client => client}/containers/index.js (100%) create mode 100644 imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js create mode 100644 imports/plugins/core/navigation/client/hocs/defaultNavigationTreeQuery.js create mode 100644 imports/plugins/core/navigation/client/hocs/fragments.js create mode 100644 imports/plugins/core/navigation/client/hocs/navigationItemsQuery.js create mode 100644 imports/plugins/core/navigation/client/hocs/queries.txt create mode 100644 imports/plugins/core/navigation/client/hocs/withCreateNavigationItem.js create mode 100644 imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js create mode 100644 imports/plugins/core/navigation/client/hocs/withDefaultNavigationTreeId.js create mode 100644 imports/plugins/core/navigation/client/hocs/withNavigationItems.js create mode 100644 imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js create mode 100644 imports/plugins/core/navigation/client/hocs/withTags.js create mode 100644 imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js rename imports/plugins/core/navigation/{ client => client}/index.js (65%) create mode 100644 imports/plugins/core/navigation/client/styles.less create mode 100644 imports/plugins/core/navigation/client/svg/iconChevronDown.js create mode 100644 imports/plugins/core/navigation/client/svg/iconChevronRight.js create mode 100644 imports/plugins/core/navigation/client/svg/iconEllipsisV.js create mode 100644 imports/plugins/core/navigation/client/svg/iconFile.js create mode 100644 imports/plugins/core/navigation/client/svg/iconPencil.js create mode 100644 imports/plugins/core/navigation/client/svg/iconTag.js create mode 100644 imports/plugins/core/navigation/client/svg/iconTimes.js rename imports/plugins/core/navigation/{ client => client}/templates/index.js (100%) rename imports/plugins/core/navigation/{ client => client}/templates/navigationDashboard.html (100%) rename imports/plugins/core/navigation/{ client => client}/templates/navigationDashboard.js (100%) create mode 100644 imports/plugins/core/navigation/client/utils/treeDataUtils.js create mode 100644 imports/plugins/core/navigation/server/i18n/en.json create mode 100644 imports/plugins/core/navigation/server/i18n/index.js create mode 100644 imports/plugins/core/navigation/server/index.js diff --git a/imports/collections/schemas/navigationTrees.js b/imports/collections/schemas/navigationTrees.js index 4f8451f86a5..8992ec3f248 100644 --- a/imports/collections/schemas/navigationTrees.js +++ b/imports/collections/schemas/navigationTrees.js @@ -11,6 +11,11 @@ const items = { optional: true }; +const expanded = { + type: Boolean, + optional: true +}; + /** * @name NavigationTreeItem * @memberof Schemas @@ -21,10 +26,12 @@ const items = { */ export const NavigationTreeItem = new SimpleSchema({ navigationItemId, + expanded, items, "items.$": { type: new SimpleSchema({ navigationItemId, + expanded, items, "items.$": { type: new SimpleSchema({ diff --git a/imports/plugins/core/dashboard/client/templates/shop/settings/ShopBrandMediaManager.js b/imports/plugins/core/dashboard/client/templates/shop/settings/ShopBrandMediaManager.js index 2c394aafdbf..c1d27265437 100644 --- a/imports/plugins/core/dashboard/client/templates/shop/settings/ShopBrandMediaManager.js +++ b/imports/plugins/core/dashboard/client/templates/shop/settings/ShopBrandMediaManager.js @@ -23,12 +23,9 @@ class ShopBrandMediaManager extends Component { return (
- {/* DragDropProvider is needed to avoid errors but we don't currently support dragging */} - -
- {this.renderBrandImages()} -
-
+
+ {this.renderBrandImages()} +
); diff --git a/imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js b/imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js deleted file mode 100644 index 9b1699fbb38..00000000000 --- a/imports/plugins/core/navigation/ client/containers/navigationDashboardContainer.js +++ /dev/null @@ -1,14 +0,0 @@ -import React, { Component } from "react"; -import { registerComponent } from "@reactioncommerce/reaction-components"; - - -class NavigationDashboardContainer extends Component { - render() { - return

Hello World

; - } -} - - -registerComponent("NavigationDashboard", NavigationDashboardContainer); - -export default NavigationDashboardContainer; diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js new file mode 100644 index 00000000000..19614890b4c --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js @@ -0,0 +1,134 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import Grid from "@material-ui/core/Grid"; +import Modal from "@material-ui/core/Modal"; +import { withStyles } from "@material-ui/core/styles"; +import HTML5Backend from "react-dnd-html5-backend"; +import { DragDropContext } from "react-dnd"; +import NavigationItemForm from "../../NavigationItemForm"; +import NavigationTreeContainer from "../../NavigationTreeContainer/v1"; +import NavigationItemTabs from "../../NavigationItemTabs/v1"; + +const styles = (theme) => ({ + paper: { + position: "absolute", + width: theme.spacing.unit * 80, + backgroundColor: theme.palette.background.paper, + boxShadow: theme.shadows[5], + padding: theme.spacing.unit * 4, + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)" + } +}); + +class NavigationDashboard extends Component { + static propTypes = { + classes: PropTypes.object, + createNavigationItem: PropTypes.func, + navigationItems: PropTypes.array, + navigationTreeRows: PropTypes.array, + onDragHover: PropTypes.func, + onToggleChildrenVisibility: PropTypes.func, + tags: PropTypes.array, + updateNavigationItem: PropTypes.func + } + + state = { + draggingNavigationItemId: "", + navigationItems: [], + isModalOpen: false, + modalMode: "create", + navigationItem: null, + overNavigationItemId: "", + tags: [] + } + + addNavigationItem = () => this.setState({ isModalOpen: true, modalMode: "create", navigationItem: null }); + + handleCloseModal = () => this.setState({ isModalOpen: false }); + + handleSetDraggingNavigationItemId = (draggingNavigationItemId) => this.setState({ draggingNavigationItemId }); + + handleSetOverNavigationItemId = (overNavigationItemId) => this.setState({ overNavigationItemId }); + + updateNavigationItem = (navigationItemDoc) => { + const { _id, draftData } = navigationItemDoc; + const { content, url, isUrlRelative, shouldOpenInNewWindow, classNames } = draftData; + const { value } = content.find((ct) => ct.language === "en"); + const navigationItem = { + _id, + name: value, + url, + isUrlRelative, + shouldOpenInNewWindow, + classNames + }; + this.setState({ isModalOpen: true, navigationItem, modalMode: "edit" }); + } + + render() { + const { + classes, + createNavigationItem, + navigationItems, + navigationTreeRows, + onDragHover, + onToggleChildrenVisibility, + tags, + updateNavigationItem + } = this.props; + + const { + isModalOpen, + modalMode, + navigationItem, + overNavigationItemId + } = this.state; + + return ( +
+ + + + + + + + + +
+ +
+
+
+ ); + } +} + +export default DragDropContext(HTML5Backend)(withStyles(styles)(NavigationDashboard)); diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js new file mode 100644 index 00000000000..3f4160b9351 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js @@ -0,0 +1 @@ +export { default } from "./NavigationDashboard"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js new file mode 100644 index 00000000000..4b4a3e72bbe --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js @@ -0,0 +1,271 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { compose } from "recompose"; +import { DragSource, DropTarget } from "react-dnd"; +import styled from "styled-components"; +import Paper from "@material-ui/core/Paper"; +import Link from "@reactioncommerce/components/Link/v1"; +import iconEllipsisV from "../../../svg/iconEllipsisV"; +import iconChevronDown from "../../../svg/iconChevronDown"; +import iconChevronRight from "../../../svg/iconChevronRight"; +import iconPencil from "../../../svg/iconPencil"; +import iconTimes from "../../../svg/iconTimes"; +import iconFile from "../../../svg/iconFile"; +import iconTag from "../../../svg/iconTag"; + +const CardContainer = styled.div` + margin-bottom: 5px; +`; +const CardContentContainer = styled.div` + display: flex; + padding: 10px 20px; +`; + +const HandleIconSpan = styled.span` + display: inline-block; + height: 16px; + width: 16px; + color: #e6e6e6; + cursor: pointer; +`; + +const ChevronIconContainer = styled.div` + width: 20px; + display: flex; + flex-direction: column; + justify-content: center; +`; + +const ChevronIconSpan = styled.span` + display: inline-block; + height: 16px; + width: 16px; + color: #666666; +`; + +const NavDetailContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; + padding-left: 20px; +`; + +const NavName = styled.p` + font-weight: 700; + margin: 0; +`; + +const NavDesc = styled.p` + font-weight: 400; + margin: 0; + color: #b3b3b3; +`; + +const NavDescSpan = styled.span` + display: inline-block; + height: 16px; + width: 16px; + margin-right: 10px; +`; + +const ActionsContainer = styled.div` + margin-left: auto; + display: flex; +`; + +const ActionIconContainer = styled.div` + width: 50px; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; +`; + +const ActionIconSpan = styled.span` + display: inline-block; + height: 20px; + width: 20px; + color: #666666; +`; + +const navigationItemSource = { + beginDrag(props) { + return props; + } +}; + +/** + * Specifies which props to inject into your component. + */ +function sourceCollect(connect, monitor) { + return { + connectDragPreview: connect.dragPreview(), + connectDragSource: connect.dragSource(), + isDragging: monitor.isDragging() + }; +} + +class NavigationItemCard extends Component { + static propTypes = { + onClickUpdateNavigationItem: PropTypes.func, + onSetDraggingNavigationItemId: PropTypes.func, + onToggleChildrenVisibility: PropTypes.func, + row: PropTypes.object + }; + + componentDidUpdate(prevProps) { + const { isDragging, isOver, navigationItemId, onSetDraggingNavigationItemId, onSetOverNavigationItemId } = this.props; + if (isDragging !== prevProps.isDragging) { + let draggingNavigationItemId = ""; + if (isDragging) { + draggingNavigationItemId = navigationItemId; + } + onSetDraggingNavigationItemId(draggingNavigationItemId); + } + + if (isOver !== prevProps.isOver) { + let overNavigationItemId = ""; + if (isOver) { + overNavigationItemId = navigationItemId; + } + onSetOverNavigationItemId(overNavigationItemId); + } + } + + get type() { + const { row } = this.props; + let type; + if (row) { + const { node: { navigationItem, tag } } = row; + if (tag) { + type = "tag"; + } else if (navigationItem) { + type = "item"; + } + } + return type; + } + + get isInTree() { + const { row } = this.props; + if (row) { + const { treeIndex } = row; + if (treeIndex !== undefined) { + return true; + } + } + return false; + } + + handleClickEdit = () => { + const { onClickUpdateNavigationItem, row: { node: { navigationItem } } } = this.props; + onClickUpdateNavigationItem(navigationItem); + } + + handleToggleVisibility = () => { + const { onToggleChildrenVisibility, row: { path } } = this.props; + onToggleChildrenVisibility(path); + } + + renderActionButtons() { + const removeActionButton = ( + + + {iconTimes} + + + ); + + const editActionButton = ( + + + {iconPencil} + + + ); + + if (this.isInTree || this.type === "item") { + return ( + + { this.type === "item" ? editActionButton : null } + { this.isInTree ? removeActionButton : null } + + ); + } + return null; + } + + renderChildrenToggleButton() { + const { row: { node: { expanded, items } } } = this.props; + if (this.isInTree && items && items.length > 0) { + return ( + + + {expanded ? iconChevronDown : iconChevronRight } + + + ); + } + return null; + } + + render() { + const { + connectDragPreview, + connectDragSource, + isDragging, + row + } = this.props; + + let title; + let description; + let type; + + if (row) { + const { node: { tag, navigationItem } } = row; + if (tag) { + const { name } = tag; + title = name; + description = name; + type = "tag"; + } else if (navigationItem) { + const { draftData } = navigationItem; + const { value } = draftData.content.find((content) => content.language === "en"); + title = value; + ({ url: description } = draftData); + type = "item"; + } + } + + const toRender = ( +
+ + + + { + connectDragSource(
+ {iconEllipsisV} +
) + } + {this.renderChildrenToggleButton()} + + {title} + + + {type === "tag" ? iconTag : iconFile} + + {description} + + + {this.renderActionButtons()} +
+
+
+
+ ); + + return connectDragPreview(
{toRender}
); + } +} + +export default compose(DragSource("CARD", navigationItemSource, sourceCollect))(NavigationItemCard); diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js new file mode 100644 index 00000000000..0dbdc9153ce --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js @@ -0,0 +1 @@ +export { default } from "./NavigationItemCard"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm/NavigationItemForm.js b/imports/plugins/core/navigation/client/components/NavigationItemForm/NavigationItemForm.js new file mode 100644 index 00000000000..a241381f7e6 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemForm/NavigationItemForm.js @@ -0,0 +1,180 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { uniqueId } from "lodash"; +import SimpleSchema from "simpl-schema"; +import styled from "styled-components"; +import Grid from "@material-ui/core/Grid"; +import { Form } from "reacto-form"; +import Link from "@reactioncommerce/components/Link/v1"; +import Button from "@reactioncommerce/components/Button/v1"; +import Checkbox from "@reactioncommerce/components/Checkbox/v1"; +import ErrorsBlock from "@reactioncommerce/components/ErrorsBlock/v1"; +import Field from "@reactioncommerce/components/Field/v1"; +import TextInput from "@reactioncommerce/components/TextInput/v1"; +import { i18next } from "/client/api"; +import iconTimes from "../../svg/iconTimes"; + + +const Wrapper = styled.div` + .close-icon-container { + text-align: right; + } + .close-icon { + display: inline-block; + height: 12px; + width: 12px; + } + .buttons-container { + text-align: right; + margin-top: 20px; + + .button-save { + margin-left: 10px; + } + } +`; + +const navigationItemFormSchema = new SimpleSchema({ + name: String, + url: String, + classNames: { + type: String, + optional: true + }, + isUrlRelative: Boolean, + shouldOpenInNewWindow: Boolean +}); + +const navigationItemValidator = navigationItemFormSchema.getFormValidator(); + +class NavigationItemForm extends Component { + static propTypes = { + createNavigationItem: PropTypes.func, + mode: PropTypes.oneOf(["create", "edit"]), + navigationItem: PropTypes.object, + onCloseForm: PropTypes.func, + updateNavigationItem: PropTypes.func + } + + uniqueInstanceIdentifier = uniqueId("NavigationItemForm"); + + handleFormValidate = (doc) => navigationItemValidator(navigationItemFormSchema.clean(doc)); + + handleFormSubmit = (input) => { + const { createNavigationItem, mode, navigationItem, onCloseForm, updateNavigationItem } = this.props; + const { name, url, isUrlRelative, shouldOpenInNewWindow, classNames } = input; + + const navigationItemUpdate = { + draftData: { + content: { + value: name, + language: "en" + }, + url, + isUrlRelative, + shouldOpenInNewWindow, + classNames + } + }; + + if (mode === "create") { + createNavigationItem({ + variables: { + input: { + navigationItem: navigationItemUpdate + } + } + }); + } else { + updateNavigationItem({ + variables: { + input: { + _id: navigationItem._id, + navigationItem: navigationItemUpdate + } + } + }); + } + return onCloseForm(); + } + + handleClickSave = () => { + if (this.form) { + this.form.submit(); + } + } + + renderActionTitle() { + const { mode } = this.props; + if (mode === "create") { + return

Add Navigation Item

; + } + return

Edit Navigation Item

; + } + + render() { + const { onCloseForm, navigationItem } = this.props; + + const nameInputId = `name_${this.uniqueInstanceIdentifier}`; + const urlInputId = `url_${this.uniqueInstanceIdentifier}`; + const classNamesInputId = `classNames_${this.uniqueInstanceIdentifier}`; + + navigationItemFormSchema.labels({ + name: i18next.t("navigationItem.displayName"), + url: i18next.t("navigationItem.url"), + isUrlRelative: i18next.t("navigationItem.isUrlRelative"), + shouldOpenInNewWindow: i18next.t("navigationItem.shouldOpenInNewWindow"), + classNames: i18next.t("navigationItem.classNames") + }); + + return ( + + + + {this.renderActionTitle()} + + + + {iconTimes} + + + + + +
{ this.form = formRef; }} onSubmit={this.handleFormSubmit} validator={this.handleFormValidate} value={navigationItem}> + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + + + + +
+ ); + } +} + +export default NavigationItemForm; + diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm/index.js b/imports/plugins/core/navigation/client/components/NavigationItemForm/index.js new file mode 100644 index 00000000000..ba0fff65876 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemForm/index.js @@ -0,0 +1 @@ +export { default } from "./NavigationItemForm"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js new file mode 100644 index 00000000000..33d0edf0850 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js @@ -0,0 +1,90 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { withStyles } from "@material-ui/core/styles"; +import Paper from "@material-ui/core/Paper"; +import Tabs from "@material-ui/core/Tabs"; +import Tab from "@material-ui/core/Tab"; +import NavigationItemsList from "../../NavigationItemsList/v1"; +import TagsList from "../../TagsList/v1"; + +const styles = (theme) => ({ + root: { + flexGrow: 1, + backgroundColor: theme.palette.background.paper + }, + tabRoot: { + textTransform: "initial", + fontSize: "14px", + height: "60px", + marginTop: "22px", + fontFamily: "Source Sans Pro, Helvetica Neue, Helvetica, sans-serif" + } +}); + +class NavigationItemTabs extends React.Component { + static propTypes = { + navigationItems: PropTypes.array, + onClickAddNavigationItem: PropTypes.func, + onClickUpdateNavigationItem: PropTypes.func, + onSetDraggingNavigationItemId: PropTypes.func, + tags: PropTypes.array, + updateNavigationItem: PropTypes.func + } + + state = { + value: 0 + }; + + handleChange = (event, value) => { + this.setState({ value }); + }; + + renderNavigationItemsTab() { + const { value } = this.state; + const { + navigationItems, + onClickAddNavigationItem, + onClickUpdateNavigationItem, + onSetDraggingNavigationItemId, + tags, + updateNavigationItem + } = this.props; + if (value === 0) { + return ; + } + return ( + + ); + } + + render() { + const { classes } = this.props; + const { value } = this.state; + + return ( +
+ + + + + + + {this.renderNavigationItemsTab()} +
+ ); + } +} + +export default withStyles(styles)(NavigationItemTabs); diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js new file mode 100644 index 00000000000..3f2352499db --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js @@ -0,0 +1 @@ +export { default } from "./NavigationItemTabs"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/NavigationItemsList.js b/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/NavigationItemsList.js new file mode 100644 index 00000000000..87f2777d53f --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/NavigationItemsList.js @@ -0,0 +1,59 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import styled from "styled-components"; +import Link from "@reactioncommerce/components/Link/v1"; +import NavigationItemCard from "../../NavigationItemCard/v1"; + +const Wrapper = styled.div` + padding: 20px; +`; + +const LinkContainer = styled.div` + margin: 20px 0px; + text-align: right; + + .add-nav-item-link { + font-weight: 700; + } +`; + +class PagesList extends Component { + static propTypes = { + navigationItems: PropTypes.array, + onClickAddNavigationItem: PropTypes.func, + onClickUpdateNavigationItem: PropTypes.func, + onSetDraggingNavigationItemId: PropTypes.func + } + + renderNavigationItems() { + const { navigationItems, onClickUpdateNavigationItem, onSetDraggingNavigationItemId } = this.props; + if (navigationItems) { + return navigationItems.map((navigationItem) => { + const row = { node: { navigationItem } }; + return ( + + ); + }); + } + return null; + } + + render() { + const { onClickAddNavigationItem } = this.props; + return ( + + + Add navigation item + + {this.renderNavigationItems()} + + ); + } +} + +export default PagesList; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js new file mode 100644 index 00000000000..c08551923bd --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js @@ -0,0 +1 @@ +export { default } from "./NavigationItemsList"; diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js new file mode 100644 index 00000000000..44ac80d53fa --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js @@ -0,0 +1,96 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import styled from "styled-components"; +import Button from "@reactioncommerce/components/Button/v1"; +import Grid from "@material-ui/core/Grid"; +import NavigationTreeNode from "../../NavigationTreeNode"; + +const Wrapper = styled.div` + border-left: 1px solid #ccc; +`; + +const HeaderWrapper = styled.div` + border-bottom: 1px solid #ccc; + padding: 20px; + + .nav-save-changes { + margin-right: 20px; + } +`; + +const ContentWrapper = styled.div` + padding: 40px 80px; + min-height: calc(100vh - 140px); +`; + +const NavigationName = styled.h4` + margin: 0; + margin-top: 12px; +`; + +const NavigationItemsListContainer = styled.div` + margin-top: 50px; +`; + +class NavigationTreeContainer extends Component { + static propTypes = { + navigationTreeRows: PropTypes.array, + onClickUpdateNavigationItem: PropTypes.func, + onDragHover: PropTypes.func, + onSetOverNavigationItemId: PropTypes.func, + onToggleChildrenVisibility: PropTypes.func, + overNavigationItemId: PropTypes.string + } + + renderRows() { + const { navigationTreeRows, onClickUpdateNavigationItem, onDragHover, onSetOverNavigationItemId, onToggleChildrenVisibility } = this.props; + let rows = null; + if (navigationTreeRows) { + rows = navigationTreeRows.map((row, index) => ( + + )); + } + return rows; + } + + render() { + return ( + + + + + Main Nav + + + + + + + + + + + + +

Drag and drop pages and tags from the left column into the navigation structure.

+
+
+ + {this.renderRows()} + +
+
+ ); + } +} + +export default NavigationTreeContainer; diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js new file mode 100644 index 00000000000..54d85800997 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js @@ -0,0 +1 @@ +export { default } from "./NavigationTreeContainer"; diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeNode.js b/imports/plugins/core/navigation/client/components/NavigationTreeNode.js new file mode 100644 index 00000000000..bba9dc1fcc9 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/NavigationTreeNode.js @@ -0,0 +1,96 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import { findDOMNode } from "react-dom"; +import { compose } from "recompose"; +import Grid from "@material-ui/core/Grid"; +import { DropTarget } from "react-dnd"; +import NavigationItemCard from "./NavigationItemCard/v1"; + +const navigationTreeRowTarget = { + drop(props) { + console.log(props); + }, + hover(props, monitor, component) { + // find the middle of things + const { onDragHover, row: { treeIndex: targetTreeIndex } } = props; + const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); + const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; + const rowLength = hoverBoundingRect.right - hoverBoundingRect.left; + const clientOffset = monitor.getClientOffset(); + const hoverClientX = clientOffset.x - hoverBoundingRect.left; + const hoverClientY = clientOffset.y - hoverBoundingRect.top; + + const treeIndex = hoverClientY < hoverMiddleY ? targetTreeIndex : targetTreeIndex + 1; + let depth = 2; + if (hoverClientX < 0.08 * rowLength) { + depth = 0; + } else if (hoverClientX < 0.17 * rowLength) { + depth = 1; + } + const draggedNode = monitor.getItem().row.node; + onDragHover({ depth, draggedNode, treeIndex }); + } +}; + +/** + * @name targetCollect + * @summary a collecting function to connect nodes to the DnD backend + * @param {Object} connect - an instance of DropTargetConnector + * @param {Object} monitor - an instance of DropTargetMonitor + * @returns {Object} props to be injected into the component + */ +function targetCollect(connect, monitor) { + return { + connectDropTarget: connect.dropTarget(), + isOver: monitor.isOver() + }; +} + +class NavigationTreeNode extends Component { + static propTypes = { + connectDropTarget: PropTypes.func, + onClickUpdateNavigationItem: PropTypes.func, + onDragHover: PropTypes.func, + onSetOverNavigationItemId: PropTypes.func, + onToggleChildrenVisibility: PropTypes.func, + row: PropTypes.object + } + + renderNavigationItemCard(row) { + const { onClickUpdateNavigationItem, onSetOverNavigationItemId, onToggleChildrenVisibility } = this.props; + return ( + + ); + } + + render() { + const { connectDropTarget, row } = this.props; + const { path } = row; + const offset = path.length - 1; + const offsets = path.map((item, index) => { + if (index === 0) { + return null; + } + return (); + }); + + const toRender = ( + + {offsets} + + {this.renderNavigationItemCard(row)} + + + ); + + return connectDropTarget(
{toRender}
); + } +} + +export default compose(DropTarget("CARD", navigationTreeRowTarget, targetCollect))(NavigationTreeNode); diff --git a/imports/plugins/core/navigation/client/components/TagsList/v1/TagsList.js b/imports/plugins/core/navigation/client/components/TagsList/v1/TagsList.js new file mode 100644 index 00000000000..aa3664f4f3e --- /dev/null +++ b/imports/plugins/core/navigation/client/components/TagsList/v1/TagsList.js @@ -0,0 +1,35 @@ +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import styled from "styled-components"; +import NavigationItemCard from "../../NavigationItemCard/v1"; + +const Wrapper = styled.div` + padding: 80px 20px; +`; + +class TagsList extends Component { + static propTypes = { + tags: PropTypes.arrayOf(PropTypes.object) + } + + renderTags() { + const { tags } = this.props; + if (tags) { + return tags.map((tag) => { + const row = { node: { tag: tag.node } }; + return ; + }); + } + return null; + } + + render() { + return ( + + {this.renderTags()} + + ); + } +} + +export default TagsList; diff --git a/imports/plugins/core/navigation/client/components/TagsList/v1/index.js b/imports/plugins/core/navigation/client/components/TagsList/v1/index.js new file mode 100644 index 00000000000..c010bfe2cfd --- /dev/null +++ b/imports/plugins/core/navigation/client/components/TagsList/v1/index.js @@ -0,0 +1 @@ +export { default } from "./TagsList"; diff --git a/imports/plugins/core/navigation/client/constants/index.js b/imports/plugins/core/navigation/client/constants/index.js new file mode 100644 index 00000000000..718c6296d02 --- /dev/null +++ b/imports/plugins/core/navigation/client/constants/index.js @@ -0,0 +1,3 @@ +export default { + cardDNDType: "CARD" +}; diff --git a/imports/plugins/core/navigation/ client/containers/index.js b/imports/plugins/core/navigation/client/containers/index.js similarity index 100% rename from imports/plugins/core/navigation/ client/containers/index.js rename to imports/plugins/core/navigation/client/containers/index.js diff --git a/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js b/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js new file mode 100644 index 00000000000..49774c1f7b9 --- /dev/null +++ b/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js @@ -0,0 +1,31 @@ +import { compose } from "recompose"; +import { registerComponent } from "@reactioncommerce/reaction-components"; +import NavigationDashboard from "../components/NavigationDashboard/v1"; +import withCreateNavigationItem from "../hocs/withCreateNavigationItem"; +import withUpdateNavigationItem from "../hocs/withUpdateNavigationItem"; +import withDefaultNavigationTree from "../hocs/withDefaultNavigationTree"; +import withNavigationItems from "../hocs/withNavigationItems"; +import withDefaultNavigationTreeId from "../hocs/withDefaultNavigationTreeId"; +import withNavigationUIStore from "../hocs/withNavigationUIStore"; +import withTags from "../hocs/withTags"; + + +registerComponent("NavigationDashboard", NavigationDashboard, [ + withNavigationUIStore, + withCreateNavigationItem, + withUpdateNavigationItem, + withDefaultNavigationTreeId, + withDefaultNavigationTree, + withNavigationItems, + withTags +]); + +export default compose( + withNavigationUIStore, + withCreateNavigationItem, + withUpdateNavigationItem, + withDefaultNavigationTreeId, + withDefaultNavigationTree, + withNavigationItems, + withTags +)(NavigationDashboard); diff --git a/imports/plugins/core/navigation/client/hocs/defaultNavigationTreeQuery.js b/imports/plugins/core/navigation/client/hocs/defaultNavigationTreeQuery.js new file mode 100644 index 00000000000..01c8b23d13d --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/defaultNavigationTreeQuery.js @@ -0,0 +1,28 @@ +import gql from "graphql-tag"; +import { navigationItemFragment } from "./fragments"; + +export default gql` + query defaultNavigationTreeQuery($id: ID!, $language: String!) { + navigationTreeById(id: $id, language: $language) { + name + draftItems { + expanded + navigationItem { + ...NavigationItemCommon + } + items { + expanded + navigationItem { + ...NavigationItemCommon + } + items { + navigationItem { + ...NavigationItemCommon + } + } + } + } + } + } + ${navigationItemFragment.navigationItem} +`; diff --git a/imports/plugins/core/navigation/client/hocs/fragments.js b/imports/plugins/core/navigation/client/hocs/fragments.js new file mode 100644 index 00000000000..89f98c318a9 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/fragments.js @@ -0,0 +1,20 @@ +import gql from "graphql-tag"; + +export const navigationItemFragment = { + navigationItem: gql` + fragment NavigationItemCommon on NavigationItem { + _id + draftData { + content { + language + value + } + url + isUrlRelative + shouldOpenInNewWindow + classNames + } + metadata + } + ` +}; diff --git a/imports/plugins/core/navigation/client/hocs/navigationItemsQuery.js b/imports/plugins/core/navigation/client/hocs/navigationItemsQuery.js new file mode 100644 index 00000000000..c951bfa47c9 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/navigationItemsQuery.js @@ -0,0 +1,14 @@ +import gql from "graphql-tag"; +import { navigationItemFragment } from "./fragments"; + +export default gql` + query navigationItemsQuery($shopId: ID!, $first: ConnectionLimitInt, $after: ConnectionCursor) { + navigationItemsByShopId(shopId: $shopId, first: $first, after: $after) { + totalCount + nodes { + ...NavigationItemCommon + } + } + } + ${navigationItemFragment.navigationItem} +`; diff --git a/imports/plugins/core/navigation/client/hocs/queries.txt b/imports/plugins/core/navigation/client/hocs/queries.txt new file mode 100644 index 00000000000..0534fed739a --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/queries.txt @@ -0,0 +1,114 @@ +mutation createNavigationItem($input: CreateNavigationItemInput!) { + createNavigationItem(input: $input) { + navigationItem { + _id + } + } +} + +{ + "input": { + "navigationItem": { + "draftData": { + "content": [ + { + "language": "en", + "value": "Blog" + } + ], + "url": "/blog", + "isUrlRelative": true + } + } + } +} + +query NavigationItemsByShopId($shopId: ID!){ + navigationItemsByShopId(shopId: $shopId) { + edges { + cursor + node { + _id + data { + content { + language + value + } + } + } + } + } +} + +{ + "shopId": "cmVhY3Rpb24vc2hvcDpKOEJocTN1VHRkZ3daeDNyeg==" +} + +mutation UpdateNavigationTree($input: UpdateNavigationTreeInput!) { + updateNavigationTree(input: $input) { + navigationTree { + _id + } + } +} + +========== PRIMARY SHOP + +{ + "data": { + "primaryShop": { + "_id": "cmVhY3Rpb24vc2hvcDpKOEJocTN1VHRkZ3daeDNyeg==", + "defaultNavigationTreeId": "cmVhY3Rpb24vbmF2aWdhdGlvblRyZWU6bktpNTZSY2hwdEx6ZDdSc1g=" + } + } +} + +========== NAVIGATION ITEMS + +{ + "data": { + "navigationItemsByShopId": { + "edges": [ + { + "cursor": "UVBTQkVTc2R2MkpHR1Roemc=", + "node": { + "_id": "cmVhY3Rpb24vbmF2aWdhdGlvbkl0ZW06UVBTQkVTc2R2MkpHR1Roemc=", + "data": { + "content": null + } + } + }, + { + "cursor": "ZHprNjlTaG1BbWg2V1pOQUw=", + "node": { + "_id": "cmVhY3Rpb24vbmF2aWdhdGlvbkl0ZW06ZHprNjlTaG1BbWg2V1pOQUw=", + "data": { + "content": [ + { + "language": "en", + "value": "Home" + } + ] + } + } + } + ] + } + } +} + +{ + "input": { + "_id": "cmVhY3Rpb24vbmF2aWdhdGlvblRyZWU6bktpNTZSY2hwdEx6ZDdSc1g=", + "navigationTree": { + "name":"MainNav", + "draftItems": [ + { + "navigationItemId": "cmVhY3Rpb24vbmF2aWdhdGlvbkl0ZW06ZHprNjlTaG1BbWg2V1pOQUw=", + "items": + } + + ] + } + } +} \ No newline at end of file diff --git a/imports/plugins/core/navigation/client/hocs/withCreateNavigationItem.js b/imports/plugins/core/navigation/client/hocs/withCreateNavigationItem.js new file mode 100644 index 00000000000..7097e1addac --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withCreateNavigationItem.js @@ -0,0 +1,42 @@ +import React from "react"; +import PropTypes from "prop-types"; +import gql from "graphql-tag"; +import { Mutation } from "react-apollo"; +import { navigationItemFragment } from "./fragments"; + +const createNavigationItemMutation = gql` + mutation createNavigationItemMutation($input: CreateNavigationItemInput!) { + createNavigationItem(input: $input) { + navigationItem { + ...NavigationItemCommon + } + } + } + ${navigationItemFragment.navigationItem} +`; + +export default (Component) => ( + class WithCreateNavigationItem extends React.Component { + static propTypes = { + onAddNavigationItem: PropTypes.func + } + + addNavigationItem = (data) => { + const { createNavigationItem: { navigationItem } } = data; + this.props.onAddNavigationItem(navigationItem); + } + + render() { + return ( + + {(createNavigationItem) => ( + + )} + + ); + } + } +); diff --git a/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js b/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js new file mode 100644 index 00000000000..5a2702990e0 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js @@ -0,0 +1,53 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Query } from "react-apollo"; +import defaultNavigationTreeQuery from "./defaultNavigationTreeQuery"; + +export default (Component) => ( + class WithDefaultNavigationTree extends React.Component { + static propTypes = { + defaultNavigationTreeId: PropTypes.string.isRequired, + onSetNavigationTree: PropTypes.func + } + + static defaultProps = { + defaultNavigationTreeId: "" + } + + handleSetNavigationTree = (data) => { + const { navigationTreeById: { draftItems } } = data; + this.props.onSetNavigationTree(draftItems); + } + + render() { + const props = { ...this.props }; + const { defaultNavigationTreeId } = this.props; + + if (!defaultNavigationTreeId) { + return ; + } + + const variables = { + id: defaultNavigationTreeId, + language: "en" + }; + + return ( + + {({ data, loading }) => { + if (!loading) { + const { navigationTreeById: { name } } = data; + props.navigationTreeName = name; + } + return ; + }} + + ); + } + } +); diff --git a/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTreeId.js b/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTreeId.js new file mode 100644 index 00000000000..b50b8a47988 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTreeId.js @@ -0,0 +1,31 @@ +import React from "react"; +import gql from "graphql-tag"; +import { Query } from "react-apollo"; + +const defaultNavigationTreeIdQuery = gql` + query defaultNavigationTreeIdQuery { + primaryShop { + _id + defaultNavigationTreeId + } + } +`; + +export default (Component) => ( + class WithDefaultNavigationTreeId extends React.Component { + render() { + return ( + + {({ data, loading }) => { + const props = { ...this.props }; + if (!loading) { + props.shopId = data.primaryShop._id; + props.defaultNavigationTreeId = data.primaryShop.defaultNavigationTreeId; + } + return ; + }} + + ); + } + } +); diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationItems.js b/imports/plugins/core/navigation/client/hocs/withNavigationItems.js new file mode 100644 index 00000000000..6d064457de0 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withNavigationItems.js @@ -0,0 +1,47 @@ + +import React from "react"; +import PropTypes from "prop-types"; +import { Query } from "react-apollo"; +import navigationItemsQuery from "./navigationItemsQuery"; + +export default (Component) => ( + class WithNavigationItems extends React.Component { + static propTypes = { + onSetNavigationItems: PropTypes.func, + shopId: PropTypes.string.isRequired + } + + static defaultProps = { + shopId: "" + } + + handleSetNavigationItems = (data) => { + const { navigationItemsByShopId: { nodes } } = data; + this.props.onSetNavigationItems(nodes); + } + + render() { + const props = { ...this.props }; + const { shopId } = this.props; + + if (!shopId) { + return ; + } + + const variables = { + shopId + }; + + return ( + + {() => ()} + + ); + } + } +); diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js new file mode 100644 index 00000000000..5f0fde2ff2f --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -0,0 +1,711 @@ +import React from "react"; + +export default (Component) => ( + class WithNavigationUIStore extends React.Component { + state = { + navigationItems: [], + draggingNavigationTree: null, + navigationTree: [], + tags: [], + targetDepth: 0, + targetTreeIndex: -1, + dragging: false + } + + getNodeDataAtTreeIndexOrNextIndex({ + targetIndex, + node, + currentIndex, + path = [], + lowerSiblingCounts = [], + ignoreCollapsed = true, + isPseudoRoot = false + }) { + // The pseudo-root is not considered in the path + const selfPath = !isPseudoRoot + ? [...path, currentIndex] + : []; + + // Return target node when found + if (currentIndex === targetIndex) { + return { + node, + lowerSiblingCounts, + path: selfPath + }; + } + + // Add one and continue for nodes with no children or hidden children + if (!node.items || (ignoreCollapsed && node.expanded !== true)) { + return { nextIndex: currentIndex + 1 }; + } + + // Iterate over each child and their descendants and return the + // target node if childIndex reaches the targetIndex + let childIndex = currentIndex + 1; + const childCount = node.items.length; + for (let i = 0; i < childCount; i += 1) { + const result = this.getNodeDataAtTreeIndexOrNextIndex({ + ignoreCollapsed, + targetIndex, + node: node.items[i], + currentIndex: childIndex, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath + }); + + if (result.node) { + return result; + } + + childIndex = result.nextIndex; + } + + // If the target node is not found, return the farthest traversed index + return { nextIndex: childIndex }; + } + + getDescendantCount({ node, ignoreCollapsed = true }) { + return ( + this.getNodeDataAtTreeIndexOrNextIndex({ + ignoreCollapsed, + node, + currentIndex: 0, + targetIndex: -1 + }).nextIndex - 1 + ); + } + + changeNodeAtPath({ navigationTree, path, newNode, ignoreCollapsed = true }) { + const RESULT_MISS = "RESULT_MISS"; + const traverse = ({ + isPseudoRoot = false, + node, + currentTreeIndex, + pathIndex + }) => { + if ( + !isPseudoRoot && + currentTreeIndex !== path[pathIndex] + ) { + return RESULT_MISS; + } + + if (pathIndex >= path.length - 1) { + // If this is the final location in the path, return its changed form + return typeof newNode === "function" + ? newNode({ node, treeIndex: currentTreeIndex }) + : newNode; + } + + if (!node.items) { + // If this node is part of the path, but has no children, return the unchanged node + throw new Error("Path referenced children of node with no children."); + } + + let nextTreeIndex = currentTreeIndex + 1; + for (let i = 0; i < node.items.length; i += 1) { + const result = traverse({ + node: node.items[i], + currentTreeIndex: nextTreeIndex, + pathIndex: pathIndex + 1 + }); + + // If the result went down the correct path + if (result !== RESULT_MISS) { + if (result) { + // If the result was truthy (in this case, an object), + // pass it to the next level of recursion up + return { + ...node, + items: [ + ...node.items.slice(0, i), + result, + ...node.items.slice(i + 1) + ] + }; + } + // If the result was falsy (returned from the newNode function), then + // delete the node from the array. + return { + ...node, + items: [ + ...node.items.slice(0, i), + ...node.items.slice(i + 1) + ] + }; + } + + nextTreeIndex += 1 + this.getDescendantCount({ node: node.items[i], ignoreCollapsed }); + } + + return RESULT_MISS; + }; + + // Use a pseudo-root node in the beginning traversal + const result = traverse({ + node: { items: navigationTree }, + currentTreeIndex: -1, + pathIndex: -1, + isPseudoRoot: true + }); + + return result.items; + } + + walkDescendants = ({ + callback, + ignoreCollapsed, + isPseudoRoot = false, + node, + parentNode = null, + currentIndex, + path = [], + lowerSiblingCounts = [] + }) => { + // The pseudo-root is not considered in the path + + const selfPath = isPseudoRoot + ? [] + : [...path, currentIndex]; + const selfInfo = isPseudoRoot + ? null + : { + node, + parentNode, + path: selfPath, + lowerSiblingCounts, + treeIndex: currentIndex + }; + + if (!isPseudoRoot) { + const callbackResult = callback(selfInfo); + + // Cut walk short if the callback returned false + if (callbackResult === false) { + return false; + } + } + + // Return self on nodes with no children or hidden children + if ( + !node.items || + (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return currentIndex; + } + + // Get all descendants + let childIndex = currentIndex; + const childCount = node.items.length; + if (typeof node.items !== "function") { + for (let i = 0; i < childCount; i += 1) { + childIndex = this.walkDescendants({ + callback, + ignoreCollapsed, + node: node.items[i], + parentNode: isPseudoRoot ? null : node, + currentIndex: childIndex + 1, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath + }); + // Cut walk short if the callback returned false + if (childIndex === false) { + return false; + } + } + } + return childIndex; + } + + walk = ({ navigationTree, ignoreCollapsed = true, callback }) => { + if (!navigationTree || navigationTree.length < 1) { + return; + } + + this.walkDescendants({ + callback, + ignoreCollapsed, + isPseudoRoot: true, + node: { items: navigationTree }, + currentIndex: -1, + path: [], + lowerSiblingCounts: [] + }); + } + + addNodeAtDepthAndIndex = ({ + targetDepth, + targetTreeIndex, + newNode, + ignoreCollapsed, + expandParent, + isPseudoRoot = false, + isLastChild, + node, + currentIndex, + currentDepth, + path = [] + }) => { + const selfPath = () => (isPseudoRoot ? [] : [...path, currentIndex]); + + // If the current position is the only possible place to add, add it + if ( + currentIndex >= targetTreeIndex - 1 || + (isLastChild && !(node.items && node.items.length)) + ) { + if (typeof node.items === "function") { + throw new Error("Cannot add to children defined by a function"); + } else { + const extraNodeProps = expandParent ? { expanded: true } : {}; + const nextNode = { + ...node, + + ...extraNodeProps, + items: node.items ? [newNode, ...node.items] : [newNode] + }; + + return { + node: nextNode, + nextIndex: currentIndex + 2, + insertedTreeIndex: currentIndex + 1, + parentPath: selfPath(nextNode), + parentNode: isPseudoRoot ? null : nextNode + }; + } + } + + // If this is the target depth for the insertion, + // i.e., where the newNode can be added to the current node's children + if (currentDepth >= targetDepth - 1) { + // Skip over nodes with no children or hidden children + if ( + !node.items || + typeof node.items === "function" || + (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return { node, nextIndex: currentIndex + 1 }; + } + + // Scan over the children to see if there's a place among them that fulfills + // the minimumTreeIndex requirement + let childIndex = currentIndex + 1; + let insertedTreeIndex = null; + let insertIndex = null; + for (let i = 0; i < node.items.length; i += 1) { + // If a valid location is found, mark it as the insertion location and + // break out of the loop + if (childIndex >= targetTreeIndex) { + insertedTreeIndex = childIndex; + insertIndex = i; + break; + } + + // Increment the index by the child itself plus the number of descendants it has + childIndex += + 1 + this.getDescendantCount({ node: node.items[i], ignoreCollapsed }); + } + + // If no valid indices to add the node were found + if (insertIndex === null) { + // If the last position in this node's children is less than the minimum index + // and there are more children on the level of this node, return without insertion + if (childIndex < targetTreeIndex && !isLastChild) { + return { node, nextIndex: childIndex }; + } + + // Use the last position in the children array to insert the newNode + insertedTreeIndex = childIndex; + insertIndex = node.items.length; + } + + // Insert the newNode at the insertIndex + const nextNode = { + ...node, + items: [ + ...node.items.slice(0, insertIndex), + newNode, + ...node.items.slice(insertIndex) + ] + }; + + // Return node with successful insert result + return { + node: nextNode, + nextIndex: childIndex, + insertedTreeIndex, + parentPath: selfPath(nextNode), + parentNode: isPseudoRoot ? null : nextNode + }; + } + + // Skip over nodes with no children or hidden children + if ( + !node.items || + typeof node.items === "function" || + (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return { node, nextIndex: currentIndex + 1 }; + } + + // Get all descendants + let insertedTreeIndex = null; + let pathFragment = null; + let parentNode = null; + let childIndex = currentIndex + 1; + let newChildren = node.items; + if (typeof newChildren !== "function") { + newChildren = newChildren.map((child, i) => { + if (insertedTreeIndex !== null) { + return child; + } + + const mapResult = this.addNodeAtDepthAndIndex({ + targetDepth, + targetTreeIndex, + newNode, + ignoreCollapsed, + expandParent, + isLastChild: isLastChild && i === newChildren.length - 1, + node: child, + currentIndex: childIndex, + currentDepth: currentDepth + 1, + path: [] // Cannot determine the parent path until the children have been processed + }); + + if ("insertedTreeIndex" in mapResult) { + ({ + insertedTreeIndex, + parentNode, + parentPath: pathFragment + } = mapResult); + } + + childIndex = mapResult.nextIndex; + + return mapResult.node; + }); + } + + const nextNode = { ...node, children: newChildren }; + const result = { + node: nextNode, + nextIndex: childIndex + }; + + if (insertedTreeIndex !== null) { + result.insertedTreeIndex = insertedTreeIndex; + result.parentPath = [...selfPath(nextNode), ...pathFragment]; + result.parentNode = parentNode; + } + + return result; + } + + insertNode = ({ + navigationTree, + targetDepth, + targetTreeIndex, + newNode, + ignoreCollapsed = true, + expandParent = false + }) => { + if (!navigationTree && targetDepth === 0) { + return { + navigationTree: [newNode], + treeIndex: 0, + path: [0], + parentNode: null + }; + } + const insertResult = this.addNodeAtDepthAndIndex({ + targetDepth, + targetTreeIndex, + newNode, + ignoreCollapsed, + expandParent, + isPseudoRoot: true, + isLastChild: true, + node: { items: navigationTree }, + currentIndex: -1, + currentDepth: -1 + }); + + if (!("insertedTreeIndex" in insertResult)) { + throw new Error("No suitable position found to insert."); + } + + const treeIndex = insertResult.insertedTreeIndex; + return { + navigationTree: insertResult.node.items, + treeIndex, + path: [ + ...insertResult.parentPath, + treeIndex + ], + parentNode: insertResult.parentNode + }; + } + + find = ({ + searchQuery, + searchMethod, + searchFocusOffset, + expandAllMatchPaths = false, + expandFocusMatchPaths = true + }) => { + let matchCount = 0; + const trav = ({ isPseudoRoot = false, node, currentIndex, path = [] }) => { + let matches = []; + let isSelfMatch = false; + let hasFocusMatch = false; + // The pseudo-root is not considered in the path + const selfPath = isPseudoRoot + ? [] + : [...path, currentIndex]; + const extraInfo = isPseudoRoot + ? null + : { + path: selfPath, + treeIndex: currentIndex + }; + + // Nodes with with children that aren't lazy + const hasChildren = + node.items && + typeof node.items !== "function" && + node.items.length > 0; + + // Examine the current node to see if it is a match + if (!isPseudoRoot && searchMethod({ ...extraInfo, node, searchQuery })) { + if (matchCount === searchFocusOffset) { + hasFocusMatch = true; + } + + // Keep track of the number of matching nodes, so we know when the searchFocusOffset + // is reached + matchCount += 1; + + // We cannot add this node to the matches right away, as it may be changed + // during the search of the descendants. The entire node is used in + // comparisons between nodes inside the `matches` and `treeData` results + // of this method (`find`) + isSelfMatch = true; + } + + let childIndex = currentIndex; + const newNode = { ...node }; + if (hasChildren) { + // Get all descendants + newNode.items = newNode.items.map((child) => { + const mapResult = trav({ + node: child, + currentIndex: childIndex + 1, + path: selfPath + }); + + // Ignore hidden nodes by only advancing the index counter to the returned treeIndex + // if the child is expanded. + // + // The child could have been expanded from the start, + // or expanded due to a matching node being found in its descendants + if (mapResult.node.expanded) { + childIndex = mapResult.treeIndex; + } else { + childIndex += 1; + } + + if (mapResult.matches.length > 0 || mapResult.hasFocusMatch) { + matches = [...matches, ...mapResult.matches]; + if (mapResult.hasFocusMatch) { + hasFocusMatch = true; + } + + // Expand the current node if it has descendants matching the search + // and the settings are set to do so. + if ( + (expandAllMatchPaths && mapResult.matches.length > 0) || + ((expandAllMatchPaths || expandFocusMatchPaths) && + mapResult.hasFocusMatch) + ) { + newNode.expanded = true; + } + } + + return mapResult.node; + }); + } + + // Cannot assign a treeIndex to hidden nodes + if (!isPseudoRoot && !newNode.expanded) { + matches = matches.map((match) => ({ + ...match, + treeIndex: null + })); + } + + // Add this node to the matches if it fits the search criteria. + // This is performed at the last minute so newNode can be sent in its final form. + if (isSelfMatch) { + matches = [{ ...extraInfo, node: newNode }, ...matches]; + } + + return { + node: matches.length > 0 ? newNode : node, + matches, + hasFocusMatch, + treeIndex: childIndex + }; + }; + + const { navigationTree } = this.state; + const result = trav({ + node: { items: navigationTree }, + isPseudoRoot: true, + currentIndex: -1 + }); + + return { + matches: result.matches, + navigationTree: result.node.items + }; + } + + getFlatDataFromNavigationTree = (navigationTree, ignoreCollapsed) => { + if (!navigationTree || navigationTree.length < 1) { + return []; + } + + const flattened = []; + this.walk({ + navigationTree, + ignoreCollapsed, + callback: (nodeInfo) => { + flattened.push(nodeInfo); + } + }); + + return flattened; + } + + getRows = (navigationTree) => this.getFlatDataFromNavigationTree(navigationTree, true); + + handleSetTags = (tags) => { + this.setState({ tags }); + } + + handleSetNavigationItems = (navigationItems) => { + this.setState({ navigationItems }); + } + + handleAddNavigationItem = (navigationItem) => { + this.setState((prevState) => ({ + navigationItems: [navigationItem, ...prevState.navigationItems] + })); + } + + handleDragHover = ({ + depth: targetDepth, + draggedNode, + treeIndex: targetTreeIndex + }) => { + const { targetDepth: currentTargetDepth, targetTreeIndex: currentTargetTreeIndex } = this.state; + if ( + targetDepth === currentTargetDepth && + targetTreeIndex === currentTargetTreeIndex + ) { + return; + } + + this.setState(({ navigationTree, draggingNavigationTree }) => { + const addedResult = this.insertNode({ + navigationTree, + newNode: draggedNode, + targetDepth, + targetTreeIndex, + expandParent: true + }); + + const rows = this.getRows(addedResult.navigationTree); + const expandedParentPath = rows[addedResult.treeIndex].path; + console.log("ROWS", rows); + console.log(expandedParentPath); + return { + draggedNode, + targetDepth, + targetTreeIndex, + draggingNavigationTree: this.changeNodeAtPath({ + navigationTree: draggingNavigationTree, + path: expandedParentPath.slice(0, -1), + newNode: ({ node }) => ({ ...node, expanded: true }) + }), + // reset the scroll focus so it doesn't jump back + // to a search result while dragging + searchFocusTreeIndex: null, + dragging: true + }; + }); + } + + handleToggleChildrenVisibility = (path) => { + const { navigationTree: currentNavigationTree } = this.state; + const navigationTree = this.changeNodeAtPath({ + navigationTree: currentNavigationTree, + path, + newNode: ({ node }) => ({ ...node, expanded: !node.expanded }) + }); + + this.setState({ navigationTree }); + } + + handleUpdateNavigationItem = (navigationItem) => { + this.setState((prevState) => { + const { navigationTree: prevNavigationTree } = prevState; + const result = this.find({ + searchMethod: ({ node, searchQuery }) => node.navigationItem._id === searchQuery, + searchQuery: navigationItem._id, + searchFocusOffset: 0 + }); + const navigationTree = result.matches.reduce((newNavigationTree, row) => { + const { path } = row; + return this.changeNodeAtPath({ + navigationTree: newNavigationTree, + path, + newNode: ({ node }) => ({ ...node, navigationItem }), + ignoreCollapsed: false + }); + }, prevNavigationTree); + return { navigationTree }; + }); + } + + handleSetNavigationTree = (navigationTree) => { + this.setState({ navigationTree }); + } + + render() { + const { navigationItems, navigationTree, dragging, draggingNavigationTree, tags } = this.state; + let currentNavigationTree = navigationTree; + if (dragging) { + currentNavigationTree = draggingNavigationTree; + } + console.log(this.state); + const navigationTreeRows = this.getFlatDataFromNavigationTree(currentNavigationTree, true); + return ( + + ); + } + } +); diff --git a/imports/plugins/core/navigation/client/hocs/withTags.js b/imports/plugins/core/navigation/client/hocs/withTags.js new file mode 100644 index 00000000000..1a0b5e5711f --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withTags.js @@ -0,0 +1,65 @@ + +import React from "react"; +import PropTypes from "prop-types"; +import gql from "graphql-tag"; +import { Query } from "react-apollo"; + +const tagsQuery = gql` + query tagsQuery($shopId: ID!, $cursor: ConnectionCursor) { + tags(shopId: $shopId, first: 200, after: $cursor) { + pageInfo { + endCursor + startCursor + hasNextPage + } + edges { + cursor + node { + _id + name + } + } + } + } +`; + +export default (Component) => ( + class WithTags extends React.Component { + static propTypes = { + onSetTags: PropTypes.func, + shopId: PropTypes.string.isRequired + } + + static defaultProps = { + shopId: "" + } + + handleSetTags = (data) => { + const { tags: { edges } } = data; + this.props.onSetTags(edges); + } + + render() { + const props = { ...this.props }; + const { shopId } = this.props; + if (!shopId) { + return ; + } + + const variables = { + shopId + }; + + return ( + + {() => ()} + + ); + } + } +); diff --git a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js new file mode 100644 index 00000000000..e4e5651b194 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js @@ -0,0 +1,42 @@ +import React from "react"; +import PropTypes from "prop-types"; +import gql from "graphql-tag"; +import { Mutation } from "react-apollo"; +import { navigationItemFragment } from "./fragments"; + +const updateNavigationItemMutation = gql` + mutation updateNavigationItemMutation($input: UpdateNavigationItemInput!) { + updateNavigationItem(input: $input) { + navigationItem { + ...NavigationItemCommon + } + } + } + ${navigationItemFragment.navigationItem} +`; + +export default (Component) => ( + class WithUpdateNavigationItem extends React.Component { + static propTypes = { + onUpdateNavigationItem: PropTypes.func + } + + handleUpdateNavigationItem = (data) => { + const { updateNavigationItem: { navigationItem } } = data; + this.props.onUpdateNavigationItem(navigationItem); + } + + render() { + return ( + + {(updateNavigationItem) => ( + + )} + + ); + } + } +); diff --git a/imports/plugins/core/navigation/ client/index.js b/imports/plugins/core/navigation/client/index.js similarity index 65% rename from imports/plugins/core/navigation/ client/index.js rename to imports/plugins/core/navigation/client/index.js index d63207b4606..0f1e3e531b5 100644 --- a/imports/plugins/core/navigation/ client/index.js +++ b/imports/plugins/core/navigation/client/index.js @@ -1,2 +1,3 @@ import "./containers"; import "./templates"; +import "./styles.less"; diff --git a/imports/plugins/core/navigation/client/styles.less b/imports/plugins/core/navigation/client/styles.less new file mode 100644 index 00000000000..f33324d8fc0 --- /dev/null +++ b/imports/plugins/core/navigation/client/styles.less @@ -0,0 +1,10 @@ +.nav-items-header-container { + border-bottom: 2px solid rgb(204, 204, 204); + min-height: 60px; + padding-top: 10px; + padding-left: 20px; +} + +.nav-items-header-container-right { + border-left: 2px solid rgb(204, 204, 204); +} diff --git a/imports/plugins/core/navigation/client/svg/iconChevronDown.js b/imports/plugins/core/navigation/client/svg/iconChevronDown.js new file mode 100644 index 00000000000..34afc3de81b --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconChevronDown.js @@ -0,0 +1,23 @@ +import React from "react"; +import styled from "styled-components"; + +const IconChevronDownSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconChevronDown = ( + // credit: https://fontawesome.com/icons/chevron-down?style=solid + + + +); + +export default IconChevronDown; diff --git a/imports/plugins/core/navigation/client/svg/iconChevronRight.js b/imports/plugins/core/navigation/client/svg/iconChevronRight.js new file mode 100644 index 00000000000..40f66515f0e --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconChevronRight.js @@ -0,0 +1,23 @@ +import React from "react"; +import styled from "styled-components"; + +const IconChevronRightSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconChevronRight = ( + // credit: https://fontawesome.com/icons/chevron-right?style=solid + + + +); + +export default IconChevronRight; diff --git a/imports/plugins/core/navigation/client/svg/iconEllipsisV.js b/imports/plugins/core/navigation/client/svg/iconEllipsisV.js new file mode 100644 index 00000000000..d678cb7981f --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconEllipsisV.js @@ -0,0 +1,31 @@ +import React from "react"; +import styled from "styled-components"; + +const IconEllipsisVSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconEllipsisV = ( + // credit: https://fontawesome.com/icons/ellipsis-v?style=solid + + + + + +); + +export default IconEllipsisV; diff --git a/imports/plugins/core/navigation/client/svg/iconFile.js b/imports/plugins/core/navigation/client/svg/iconFile.js new file mode 100644 index 00000000000..aec0dfca09d --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconFile.js @@ -0,0 +1,23 @@ +import React from "react"; +import styled from "styled-components"; + +const IconFileSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconFile = ( + // credit: https://fontawesome.com/icons/file?style=solid + + + +); + +export default IconFile; diff --git a/imports/plugins/core/navigation/client/svg/iconPencil.js b/imports/plugins/core/navigation/client/svg/iconPencil.js new file mode 100644 index 00000000000..b760e470fed --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconPencil.js @@ -0,0 +1,27 @@ +import React from "react"; +import styled from "styled-components"; + +const IconPencilSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconPencil = ( + // credit: https://fontawesome.com/icons/pencil?style=solid + + + + +); + +export default IconPencil; diff --git a/imports/plugins/core/navigation/client/svg/iconTag.js b/imports/plugins/core/navigation/client/svg/iconTag.js new file mode 100644 index 00000000000..eddaff6efdf --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconTag.js @@ -0,0 +1,23 @@ +import React from "react"; +import styled from "styled-components"; + +const IconTagSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconTag = ( + // credit: https://fontawesome.com/icons/tag?style=solid + + + +); + +export default IconTag; diff --git a/imports/plugins/core/navigation/client/svg/iconTimes.js b/imports/plugins/core/navigation/client/svg/iconTimes.js new file mode 100644 index 00000000000..8fe643b6eb4 --- /dev/null +++ b/imports/plugins/core/navigation/client/svg/iconTimes.js @@ -0,0 +1,23 @@ +import React from "react"; +import styled from "styled-components"; + +const IconTimesSvg = styled.svg` + height: 100%; + max-height: 100%; + vertical-align: middle; +`; + +const IconTimes = ( + // credit: https://fontawesome.com/icons/times?style=solid + + + +); + +export default IconTimes; diff --git a/imports/plugins/core/navigation/ client/templates/index.js b/imports/plugins/core/navigation/client/templates/index.js similarity index 100% rename from imports/plugins/core/navigation/ client/templates/index.js rename to imports/plugins/core/navigation/client/templates/index.js diff --git a/imports/plugins/core/navigation/ client/templates/navigationDashboard.html b/imports/plugins/core/navigation/client/templates/navigationDashboard.html similarity index 100% rename from imports/plugins/core/navigation/ client/templates/navigationDashboard.html rename to imports/plugins/core/navigation/client/templates/navigationDashboard.html diff --git a/imports/plugins/core/navigation/ client/templates/navigationDashboard.js b/imports/plugins/core/navigation/client/templates/navigationDashboard.js similarity index 100% rename from imports/plugins/core/navigation/ client/templates/navigationDashboard.js rename to imports/plugins/core/navigation/client/templates/navigationDashboard.js diff --git a/imports/plugins/core/navigation/client/utils/treeDataUtils.js b/imports/plugins/core/navigation/client/utils/treeDataUtils.js new file mode 100644 index 00000000000..55f7b9d61d9 --- /dev/null +++ b/imports/plugins/core/navigation/client/utils/treeDataUtils.js @@ -0,0 +1,1198 @@ +/** + * Performs a depth-first traversal over all of the node descendants, + * incrementing currentIndex by 1 for each + */ +function getNodeDataAtTreeIndexOrNextIndex({ + targetIndex, + node, + currentIndex, + getNodeKey, + path = [], + lowerSiblingCounts = [], + ignoreCollapsed = true, + isPseudoRoot = false +}) { + // The pseudo-root is not considered in the path + const selfPath = !isPseudoRoot + ? [...path, getNodeKey({ node, treeIndex: currentIndex })] + : []; + + // Return target node when found + if (currentIndex === targetIndex) { + return { + node, + lowerSiblingCounts, + path: selfPath + }; + } + + // Add one and continue for nodes with no children or hidden children + if (!node.children || (ignoreCollapsed && node.expanded !== true)) { + return { nextIndex: currentIndex + 1 }; + } + + // Iterate over each child and their descendants and return the + // target node if childIndex reaches the targetIndex + let childIndex = currentIndex + 1; + const childCount = node.children.length; + for (let i = 0; i < childCount; i += 1) { + const result = getNodeDataAtTreeIndexOrNextIndex({ + ignoreCollapsed, + getNodeKey, + targetIndex, + node: node.children[i], + currentIndex: childIndex, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath + }); + + if (result.node) { + return result; + } + + childIndex = result.nextIndex; + } + + // If the target node is not found, return the farthest traversed index + return { nextIndex: childIndex }; +} + +export function getDescendantCount({ node, ignoreCollapsed = true }) { + return ( + getNodeDataAtTreeIndexOrNextIndex({ + getNodeKey: () => {}, + ignoreCollapsed, + node, + currentIndex: 0, + targetIndex: -1 + }).nextIndex - 1 + ); +} + +/** + * Walk all descendants of the given node, depth-first + * + * @param {Object} args - Function parameters + * @param {function} args.callback - Function to call on each node + * @param {function} args.getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean} args.ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * @param {boolean=} args.isPseudoRoot - If true, this node has no real data, and only serves + * as the parent of all the nodes in the tree + * @param {Object} args.node - A tree node + * @param {Object=} args.parentNode - The parent node of `node` + * @param {number} args.currentIndex - The treeIndex of `node` + * @param {number[]|string[]} args.path - Array of keys leading up to node to be changed + * @param {number[]} args.lowerSiblingCounts - An array containing the count of siblings beneath the + * previous nodes in this path + * + * @return {number|false} nextIndex - Index of the next sibling of `node`, + * or false if the walk should be terminated + */ +function walkDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + isPseudoRoot = false, + node, + parentNode = null, + currentIndex, + path = [], + lowerSiblingCounts = [] +}) { + // The pseudo-root is not considered in the path + const selfPath = isPseudoRoot + ? [] + : [...path, getNodeKey({ node, treeIndex: currentIndex })]; + const selfInfo = isPseudoRoot + ? null + : { + node, + parentNode, + path: selfPath, + lowerSiblingCounts, + treeIndex: currentIndex + }; + + if (!isPseudoRoot) { + const callbackResult = callback(selfInfo); + + // Cut walk short if the callback returned false + if (callbackResult === false) { + return false; + } + } + + // Return self on nodes with no children or hidden children + if ( + !node.children || + (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return currentIndex; + } + + // Get all descendants + let childIndex = currentIndex; + const childCount = node.children.length; + if (typeof node.children !== "function") { + for (let i = 0; i < childCount; i += 1) { + childIndex = walkDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + node: node.children[i], + parentNode: isPseudoRoot ? null : node, + currentIndex: childIndex + 1, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath + }); + + // Cut walk short if the callback returned false + if (childIndex === false) { + return false; + } + } + } + + return childIndex; +} + +/** + * Perform a change on the given node and all its descendants, traversing the tree depth-first + * + * @param {Object} args - Function parameters + * @param {function} args.callback - Function to call on each node + * @param {function} args.getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean} args.ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * @param {boolean=} args.isPseudoRoot - If true, this node has no real data, and only serves + * as the parent of all the nodes in the tree + * @param {Object} args.node - A tree node + * @param {Object=} args.parentNode - The parent node of `node` + * @param {number} args.currentIndex - The treeIndex of `node` + * @param {number[]|string[]} args.path - Array of keys leading up to node to be changed + * @param {number[]} args.lowerSiblingCounts - An array containing the count of siblings beneath the + * previous nodes in this path + * + * @return {number|false} nextIndex - Index of the next sibling of `node`, + * or false if the walk should be terminated + */ +function mapDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + isPseudoRoot = false, + node, + parentNode = null, + currentIndex, + path = [], + lowerSiblingCounts = [] +}) { + const nextNode = { ...node }; + + // The pseudo-root is not considered in the path + const selfPath = isPseudoRoot + ? [] + : [...path, getNodeKey({ node: nextNode, treeIndex: currentIndex })]; + const selfInfo = { + node: nextNode, + parentNode, + path: selfPath, + lowerSiblingCounts, + treeIndex: currentIndex + }; + + // Return self on nodes with no children or hidden children + if ( + !nextNode.children || + (nextNode.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return { + treeIndex: currentIndex, + node: callback(selfInfo) + }; + } + + // Get all descendants + let childIndex = currentIndex; + const childCount = nextNode.children.length; + if (typeof nextNode.children !== "function") { + nextNode.children = nextNode.children.map((child, i) => { + const mapResult = mapDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + node: child, + parentNode: isPseudoRoot ? null : nextNode, + currentIndex: childIndex + 1, + lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], + path: selfPath + }); + childIndex = mapResult.treeIndex; + + return mapResult.node; + }); + } + + return { + node: callback(selfInfo), + treeIndex: childIndex + }; +} + +/** + * Count all the visible (expanded) descendants in the tree data. + * + * @param {!Object[]} treeData - Tree data + * + * @return {number} count + */ +export function getVisibleNodeCount({ treeData }) { + const traverse = (node) => { + if ( + !node.children || + node.expanded !== true || + typeof node.children === "function" + ) { + return 1; + } + + return ( + 1 + + node.children.reduce( + (total, currentNode) => total + traverse(currentNode), + 0 + ) + ); + }; + + return treeData.reduce( + (total, currentNode) => total + traverse(currentNode), + 0 + ); +} + +/** + * Get the th visible node in the tree data. + * + * @param {!Object[]} treeData - Tree data + * @param {!number} targetIndex - The index of the node to search for + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * + * @return {{ + * node: Object, + * path: []string|[]number, + * lowerSiblingCounts: []number + * }|null} node - The node at targetIndex, or null if not found + */ +export function getVisibleNodeInfoAtIndex({ + treeData, + index: targetIndex, + getNodeKey +}) { + if (!treeData || treeData.length < 1) { + return null; + } + + // Call the tree traversal with a pseudo-root node + const result = getNodeDataAtTreeIndexOrNextIndex({ + targetIndex, + getNodeKey, + node: { + children: treeData, + expanded: true + }, + currentIndex: -1, + path: [], + lowerSiblingCounts: [], + isPseudoRoot: true + }); + + if (result.node) { + return result; + } + + return null; +} + +/** + * Walk descendants depth-first and call a callback on each + * + * @param {!Object[]} treeData - Tree data + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {function} callback - Function to call on each node + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return void + */ +export function walk({ + treeData, + getNodeKey, + callback, + ignoreCollapsed = true +}) { + if (!treeData || treeData.length < 1) { + return; + } + + walkDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + isPseudoRoot: true, + node: { children: treeData }, + currentIndex: -1, + path: [], + lowerSiblingCounts: [] + }); +} + +/** + * Perform a depth-first transversal of the descendants and + * make a change to every node in the tree + * + * @param {!Object[]} treeData - Tree data + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {function} callback - Function to call on each node + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return {Object[]} changedTreeData - The changed tree data + */ +export function map({ + treeData, + getNodeKey, + callback, + ignoreCollapsed = true +}) { + if (!treeData || treeData.length < 1) { + return []; + } + + return mapDescendants({ + callback, + getNodeKey, + ignoreCollapsed, + isPseudoRoot: true, + node: { children: treeData }, + currentIndex: -1, + path: [], + lowerSiblingCounts: [] + }).node.children; +} + +/** + * Expand or close every node in the tree + * + * @param {!Object[]} treeData - Tree data + * @param {?boolean} expanded - Whether the node is expanded or not + * + * @return {Object[]} changedTreeData - The changed tree data + */ +export function toggleExpandedForAll({ treeData, expanded = true }) { + return map({ + treeData, + callback: ({ node }) => ({ ...node, expanded }), + getNodeKey: ({ treeIndex }) => treeIndex, + ignoreCollapsed: false + }); +} + +/** + * Replaces node at path with object, or callback-defined object + * + * @param {!Object[]} treeData + * @param {number[]|string[]} path - Array of keys leading up to node to be changed + * @param {function|any} newNode - Node to replace the node at the path with, or a function producing the new node + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return {Object[]} changedTreeData - The changed tree data + */ +export function changeNodeAtPath({ + treeData, + path, + newNode, + getNodeKey, + ignoreCollapsed = true +}) { + const RESULT_MISS = "RESULT_MISS"; + const traverse = ({ + isPseudoRoot = false, + node, + currentTreeIndex, + pathIndex + }) => { + if ( + !isPseudoRoot && + getNodeKey({ node, treeIndex: currentTreeIndex }) !== path[pathIndex] + ) { + return RESULT_MISS; + } + + if (pathIndex >= path.length - 1) { + // If this is the final location in the path, return its changed form + return typeof newNode === "function" + ? newNode({ node, treeIndex: currentTreeIndex }) + : newNode; + } + if (!node.children) { + // If this node is part of the path, but has no children, return the unchanged node + throw new Error("Path referenced children of node with no children."); + } + + let nextTreeIndex = currentTreeIndex + 1; + for (let i = 0; i < node.children.length; i += 1) { + const result = traverse({ + node: node.children[i], + currentTreeIndex: nextTreeIndex, + pathIndex: pathIndex + 1 + }); + + // If the result went down the correct path + if (result !== RESULT_MISS) { + if (result) { + // If the result was truthy (in this case, an object), + // pass it to the next level of recursion up + return { + ...node, + children: [ + ...node.children.slice(0, i), + result, + ...node.children.slice(i + 1) + ] + }; + } + // If the result was falsy (returned from the newNode function), then + // delete the node from the array. + return { + ...node, + children: [ + ...node.children.slice(0, i), + ...node.children.slice(i + 1) + ] + }; + } + + nextTreeIndex += + 1 + getDescendantCount({ node: node.children[i], ignoreCollapsed }); + } + + return RESULT_MISS; + }; + + // Use a pseudo-root node in the beginning traversal + const result = traverse({ + node: { children: treeData }, + currentTreeIndex: -1, + pathIndex: -1, + isPseudoRoot: true + }); + + if (result === RESULT_MISS) { + throw new Error("No node found at the given path."); + } + + return result.children; +} + +/** + * Removes the node at the specified path and returns the resulting treeData. + * + * @param {!Object[]} treeData + * @param {number[]|string[]} path - Array of keys leading up to node to be deleted + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return {Object[]} changedTreeData - The tree data with the node removed + */ +export function removeNodeAtPath({ + treeData, + path, + getNodeKey, + ignoreCollapsed = true +}) { + return changeNodeAtPath({ + treeData, + path, + getNodeKey, + ignoreCollapsed, + newNode: null // Delete the node + }); +} + +/** + * Removes the node at the specified path and returns the resulting treeData. + * + * @param {!Object[]} treeData + * @param {number[]|string[]} path - Array of keys leading up to node to be deleted + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return {Object} result + * @return {Object[]} result.treeData - The tree data with the node removed + * @return {Object} result.node - The node that was removed + * @return {number} result.treeIndex - The previous treeIndex of the removed node + */ +export function removeNode({ + treeData, + path, + getNodeKey, + ignoreCollapsed = true +}) { + let removedNode = null; + let removedTreeIndex = null; + const nextTreeData = changeNodeAtPath({ + treeData, + path, + getNodeKey, + ignoreCollapsed, + newNode: ({ node, treeIndex }) => { + // Store the target node and delete it from the tree + removedNode = node; + removedTreeIndex = treeIndex; + + return null; + } + }); + + return { + treeData: nextTreeData, + node: removedNode, + treeIndex: removedTreeIndex + }; +} + +/** + * Gets the node at the specified path + * + * @param {!Object[]} treeData + * @param {number[]|string[]} path - Array of keys leading up to node to be deleted + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return {Object|null} nodeInfo - The node info at the given path, or null if not found + */ +export function getNodeAtPath({ + treeData, + path, + getNodeKey, + ignoreCollapsed = true +}) { + let foundNodeInfo = null; + + try { + changeNodeAtPath({ + treeData, + path, + getNodeKey, + ignoreCollapsed, + newNode: ({ node, treeIndex }) => { + foundNodeInfo = { node, treeIndex }; + return node; + } + }); + } catch (err) { + // Ignore the error -- the null return will be explanation enough + } + + return foundNodeInfo; +} + +/** + * Adds the node to the specified parent and returns the resulting treeData. + * + * @param {!Object[]} treeData + * @param {!Object} newNode - The node to insert + * @param {number|string} parentKey - The key of the to-be parentNode of the node + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * @param {boolean=} expandParent - If true, expands the parentNode specified by parentPath + * @param {boolean=} addAsFirstChild - If true, adds new node as first child of tree + * + * @return {Object} result + * @return {Object[]} result.treeData - The updated tree data + * @return {number} result.treeIndex - The tree index at which the node was inserted + */ +export function addNodeUnderParent({ + treeData, + newNode, + parentKey = null, + getNodeKey, + ignoreCollapsed = true, + expandParent = false, + addAsFirstChild = false +}) { + if (parentKey === null) { + return { + treeData: [...(treeData || []), newNode], + treeIndex: (treeData || []).length + }; + } + + let insertedTreeIndex = null; + let hasBeenAdded = false; + const changedTreeData = map({ + treeData, + getNodeKey, + ignoreCollapsed, + callback: ({ node, treeIndex, path }) => { + const key = path ? path[path.length - 1] : null; + // Return nodes that are not the parent as-is + if (hasBeenAdded || key !== parentKey) { + return node; + } + hasBeenAdded = true; + + const parentNode = { + ...node + }; + + if (expandParent) { + parentNode.expanded = true; + } + + // If no children exist yet, just add the single newNode + if (!parentNode.children) { + insertedTreeIndex = treeIndex + 1; + return { + ...parentNode, + children: [newNode] + }; + } + + if (typeof parentNode.children === "function") { + throw new Error("Cannot add to children defined by a function"); + } + + let nextTreeIndex = treeIndex + 1; + for (let i = 0; i < parentNode.children.length; i += 1) { + nextTreeIndex += + 1 + + getDescendantCount({ node: parentNode.children[i], ignoreCollapsed }); + } + + insertedTreeIndex = nextTreeIndex; + + const children = addAsFirstChild + ? [newNode, ...parentNode.children] + : [...parentNode.children, newNode]; + + return { + ...parentNode, + children + }; + } + }); + + if (!hasBeenAdded) { + throw new Error("No node found with the given key."); + } + + return { + treeData: changedTreeData, + treeIndex: insertedTreeIndex + }; +} + +function addNodeAtDepthAndIndex({ + targetDepth, + minimumTreeIndex, + newNode, + ignoreCollapsed, + expandParent, + isPseudoRoot = false, + isLastChild, + node, + currentIndex, + currentDepth, + getNodeKey, + path = [] +}) { + const selfPath = (n) => + (isPseudoRoot + ? [] + : [...path, getNodeKey({ node: n, treeIndex: currentIndex })]); + + // If the current position is the only possible place to add, add it + if ( + currentIndex >= minimumTreeIndex - 1 || + (isLastChild && !(node.children && node.children.length)) + ) { + if (typeof node.children === "function") { + throw new Error("Cannot add to children defined by a function"); + } else { + const extraNodeProps = expandParent ? { expanded: true } : {}; + const nextNode = { + ...node, + + ...extraNodeProps, + children: node.children ? [newNode, ...node.children] : [newNode] + }; + + return { + node: nextNode, + nextIndex: currentIndex + 2, + insertedTreeIndex: currentIndex + 1, + parentPath: selfPath(nextNode), + parentNode: isPseudoRoot ? null : nextNode + }; + } + } + + // If this is the target depth for the insertion, + // i.e., where the newNode can be added to the current node's children + if (currentDepth >= targetDepth - 1) { + // Skip over nodes with no children or hidden children + if ( + !node.children || + typeof node.children === "function" || + (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return { node, nextIndex: currentIndex + 1 }; + } + + // Scan over the children to see if there's a place among them that fulfills + // the minimumTreeIndex requirement + let childIndex = currentIndex + 1; + let insertedTreeIndex = null; + let insertIndex = null; + for (let i = 0; i < node.children.length; i += 1) { + // If a valid location is found, mark it as the insertion location and + // break out of the loop + if (childIndex >= minimumTreeIndex) { + insertedTreeIndex = childIndex; + insertIndex = i; + break; + } + + // Increment the index by the child itself plus the number of descendants it has + childIndex += + 1 + getDescendantCount({ node: node.children[i], ignoreCollapsed }); + } + + // If no valid indices to add the node were found + if (insertIndex === null) { + // If the last position in this node's children is less than the minimum index + // and there are more children on the level of this node, return without insertion + if (childIndex < minimumTreeIndex && !isLastChild) { + return { node, nextIndex: childIndex }; + } + + // Use the last position in the children array to insert the newNode + insertedTreeIndex = childIndex; + insertIndex = node.children.length; + } + + // Insert the newNode at the insertIndex + const nextNode = { + ...node, + children: [ + ...node.children.slice(0, insertIndex), + newNode, + ...node.children.slice(insertIndex) + ] + }; + + // Return node with successful insert result + return { + node: nextNode, + nextIndex: childIndex, + insertedTreeIndex, + parentPath: selfPath(nextNode), + parentNode: isPseudoRoot ? null : nextNode + }; + } + + // Skip over nodes with no children or hidden children + if ( + !node.children || + typeof node.children === "function" || + (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) + ) { + return { node, nextIndex: currentIndex + 1 }; + } + + // Get all descendants + let insertedTreeIndex = null; + let pathFragment = null; + let parentNode = null; + let childIndex = currentIndex + 1; + let newChildren = node.children; + if (typeof newChildren !== "function") { + newChildren = newChildren.map((child, i) => { + if (insertedTreeIndex !== null) { + return child; + } + + const mapResult = addNodeAtDepthAndIndex({ + targetDepth, + minimumTreeIndex, + newNode, + ignoreCollapsed, + expandParent, + isLastChild: isLastChild && i === newChildren.length - 1, + node: child, + currentIndex: childIndex, + currentDepth: currentDepth + 1, + getNodeKey, + path: [] // Cannot determine the parent path until the children have been processed + }); + + if ("insertedTreeIndex" in mapResult) { + ({ + insertedTreeIndex, + parentNode, + parentPath: pathFragment + } = mapResult); + } + + childIndex = mapResult.nextIndex; + + return mapResult.node; + }); + } + + const nextNode = { ...node, children: newChildren }; + const result = { + node: nextNode, + nextIndex: childIndex + }; + + if (insertedTreeIndex !== null) { + result.insertedTreeIndex = insertedTreeIndex; + result.parentPath = [...selfPath(nextNode), ...pathFragment]; + result.parentNode = parentNode; + } + + return result; +} + +/** + * Insert a node into the tree at the given depth, after the minimum index + * + * @param {!Object[]} treeData - Tree data + * @param {!number} depth - The depth to insert the node at (the first level of the array being depth 0) + * @param {!number} minimumTreeIndex - The lowest possible treeIndex to insert the node at + * @param {!Object} newNode - The node to insert into the tree + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * @param {boolean=} expandParent - If true, expands the parent of the inserted node + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * + * @return {Object} result + * @return {Object[]} result.treeData - The tree data with the node added + * @return {number} result.treeIndex - The tree index at which the node was inserted + * @return {number[]|string[]} result.path - Array of keys leading to the node location after insertion + * @return {Object} result.parentNode - The parent node of the inserted node + */ +export function insertNode({ + treeData, + depth: targetDepth, + minimumTreeIndex, + newNode, + getNodeKey = () => {}, + ignoreCollapsed = true, + expandParent = false +}) { + if (!treeData && targetDepth === 0) { + return { + treeData: [newNode], + treeIndex: 0, + path: [getNodeKey({ node: newNode, treeIndex: 0 })], + parentNode: null + }; + } + + const insertResult = addNodeAtDepthAndIndex({ + targetDepth, + minimumTreeIndex, + newNode, + ignoreCollapsed, + expandParent, + getNodeKey, + isPseudoRoot: true, + isLastChild: true, + node: { children: treeData }, + currentIndex: -1, + currentDepth: -1 + }); + + if (!("insertedTreeIndex" in insertResult)) { + throw new Error("No suitable position found to insert."); + } + + const treeIndex = insertResult.insertedTreeIndex; + return { + treeData: insertResult.node.children, + treeIndex, + path: [ + ...insertResult.parentPath, + getNodeKey({ node: newNode, treeIndex }) + ], + parentNode: insertResult.parentNode + }; +} + +/** + * Get tree data flattened. + * + * @param {!Object[]} treeData - Tree data + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` + * + * @return {{ + * node: Object, + * path: []string|[]number, + * lowerSiblingCounts: []number + * }}[] nodes - The node array + */ +export function getFlatDataFromTree({ + treeData, + getNodeKey, + ignoreCollapsed = true +}) { + if (!treeData || treeData.length < 1) { + return []; + } + + const flattened = []; + walk({ + treeData, + getNodeKey, + ignoreCollapsed, + callback: (nodeInfo) => { + flattened.push(nodeInfo); + } + }); + + return flattened; +} + +/** + * Generate a tree structure from flat data. + * + * @param {!Object[]} flatData + * @param {!function=} getKey - Function to get the key from the nodeData + * @param {!function=} getParentKey - Function to get the parent key from the nodeData + * @param {string|number=} rootKey - The value returned by `getParentKey` that corresponds to the root node. + * For example, if your nodes have id 1-99, you might use rootKey = 0 + * + * @return {Object[]} treeData - The flat data represented as a tree + */ +export function getTreeFromFlatData({ + flatData, + getKey = (node) => node.id, + getParentKey = (node) => node.parentId, + rootKey = "0" +}) { + if (!flatData) { + return []; + } + + const childrenToParents = {}; + flatData.forEach((child) => { + const parentKey = getParentKey(child); + + if (parentKey in childrenToParents) { + childrenToParents[parentKey].push(child); + } else { + childrenToParents[parentKey] = [child]; + } + }); + + if (!(rootKey in childrenToParents)) { + return []; + } + + const trav = (parent) => { + const parentKey = getKey(parent); + if (parentKey in childrenToParents) { + return { + ...parent, + children: childrenToParents[parentKey].map((child) => trav(child)) + }; + } + + return { ...parent }; + }; + + return childrenToParents[rootKey].map((child) => trav(child)); +} + +/** + * Check if a node is a descendant of another node. + * + * @param {!Object} older - Potential ancestor of younger node + * @param {!Object} younger - Potential descendant of older node + * + * @return {boolean} + */ +export function isDescendant(older, younger) { + return ( + !!older.children && + typeof older.children !== "function" && + older.children.some((child) => child === younger || isDescendant(child, younger)) + ); +} + +/** + * Get the maximum depth of the children (the depth of the root node is 0). + * + * @param {!Object} node - Node in the tree + * @param {?number} depth - The current depth + * + * @return {number} maxDepth - The deepest depth in the tree + */ +export function getDepth(node, depth = 0) { + if (!node.children) { + return depth; + } + + if (typeof node.children === "function") { + return depth + 1; + } + + return node.children.reduce( + (deepest, child) => Math.max(deepest, getDepth(child, depth + 1)), + depth + ); +} + +/** + * Find nodes matching a search query in the tree, + * + * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index + * @param {!Object[]} treeData - Tree data + * @param {?string|number} searchQuery - Function returning a boolean to indicate whether the node is a match or not + * @param {!function} searchMethod - Function returning a boolean to indicate whether the node is a match or not + * @param {?number} searchFocusOffset - The offset of the match to focus on + * (e.g., 0 focuses on the first match, 1 on the second) + * @param {boolean=} expandAllMatchPaths - If true, expands the paths to any matched node + * @param {boolean=} expandFocusMatchPaths - If true, expands the path to the focused node + * + * @return {Object[]} matches - An array of objects containing the matching `node`s, their `path`s and `treeIndex`s + * @return {Object[]} treeData - The original tree data with all relevant nodes expanded. + * If expandAllMatchPaths and expandFocusMatchPaths are both false, + * it will be the same as the original tree data. + */ +export function find({ + getNodeKey, + treeData, + searchQuery, + searchMethod, + searchFocusOffset, + expandAllMatchPaths = false, + expandFocusMatchPaths = true +}) { + let matchCount = 0; + const trav = ({ isPseudoRoot = false, node, currentIndex, path = [] }) => { + let matches = []; + let isSelfMatch = false; + let hasFocusMatch = false; + // The pseudo-root is not considered in the path + const selfPath = isPseudoRoot + ? [] + : [...path, getNodeKey({ node, treeIndex: currentIndex })]; + const extraInfo = isPseudoRoot + ? null + : { + path: selfPath, + treeIndex: currentIndex + }; + + // Nodes with with children that aren't lazy + const hasChildren = + node.children && + typeof node.children !== "function" && + node.children.length > 0; + + // Examine the current node to see if it is a match + if (!isPseudoRoot && searchMethod({ ...extraInfo, node, searchQuery })) { + if (matchCount === searchFocusOffset) { + hasFocusMatch = true; + } + + // Keep track of the number of matching nodes, so we know when the searchFocusOffset + // is reached + matchCount += 1; + + // We cannot add this node to the matches right away, as it may be changed + // during the search of the descendants. The entire node is used in + // comparisons between nodes inside the `matches` and `treeData` results + // of this method (`find`) + isSelfMatch = true; + } + + let childIndex = currentIndex; + const newNode = { ...node }; + if (hasChildren) { + // Get all descendants + newNode.children = newNode.children.map((child) => { + const mapResult = trav({ + node: child, + currentIndex: childIndex + 1, + path: selfPath + }); + + // Ignore hidden nodes by only advancing the index counter to the returned treeIndex + // if the child is expanded. + // + // The child could have been expanded from the start, + // or expanded due to a matching node being found in its descendants + if (mapResult.node.expanded) { + childIndex = mapResult.treeIndex; + } else { + childIndex += 1; + } + + if (mapResult.matches.length > 0 || mapResult.hasFocusMatch) { + matches = [...matches, ...mapResult.matches]; + if (mapResult.hasFocusMatch) { + hasFocusMatch = true; + } + + // Expand the current node if it has descendants matching the search + // and the settings are set to do so. + if ( + (expandAllMatchPaths && mapResult.matches.length > 0) || + ((expandAllMatchPaths || expandFocusMatchPaths) && + mapResult.hasFocusMatch) + ) { + newNode.expanded = true; + } + } + + return mapResult.node; + }); + } + + // Cannot assign a treeIndex to hidden nodes + if (!isPseudoRoot && !newNode.expanded) { + matches = matches.map((match) => ({ + ...match, + treeIndex: null + })); + } + + // Add this node to the matches if it fits the search criteria. + // This is performed at the last minute so newNode can be sent in its final form. + if (isSelfMatch) { + matches = [{ ...extraInfo, node: newNode }, ...matches]; + } + + return { + node: matches.length > 0 ? newNode : node, + matches, + hasFocusMatch, + treeIndex: childIndex + }; + }; + + const result = trav({ + node: { children: treeData }, + isPseudoRoot: true, + currentIndex: -1 + }); + + return { + matches: result.matches, + treeData: result.node.children + }; +} diff --git a/imports/plugins/core/navigation/server/i18n/en.json b/imports/plugins/core/navigation/server/i18n/en.json new file mode 100644 index 00000000000..820d41a861d --- /dev/null +++ b/imports/plugins/core/navigation/server/i18n/en.json @@ -0,0 +1,16 @@ +[{ + "language": "English", + "i18n": "en", + "ns": "navigation", + "translation": { + "navigation": { + "navigationItem": { + "displayName": "Display Name", + "url": "URL", + "isUrlRelative": "This URL is relative", + "shouldOpenInNewWindow": "Open in a new tab", + "classNames": "CSS Class Names" + } + } + } +}] diff --git a/imports/plugins/core/navigation/server/i18n/index.js b/imports/plugins/core/navigation/server/i18n/index.js new file mode 100644 index 00000000000..b9674e1cbb8 --- /dev/null +++ b/imports/plugins/core/navigation/server/i18n/index.js @@ -0,0 +1,5 @@ +import { loadTranslations } from "/imports/plugins/core/core/server/startup/i18n"; + +import en from "./en.json"; + +loadTranslations([en]); diff --git a/imports/plugins/core/navigation/server/index.js b/imports/plugins/core/navigation/server/index.js new file mode 100644 index 00000000000..3979f964b5a --- /dev/null +++ b/imports/plugins/core/navigation/server/index.js @@ -0,0 +1 @@ +import "./i18n"; diff --git a/imports/plugins/core/navigation/server/no-meteor/schemas/schema.graphql b/imports/plugins/core/navigation/server/no-meteor/schemas/schema.graphql index bb84db6d85d..8da760b639e 100644 --- a/imports/plugins/core/navigation/server/no-meteor/schemas/schema.graphql +++ b/imports/plugins/core/navigation/server/no-meteor/schemas/schema.graphql @@ -115,6 +115,9 @@ type NavigationTreeItem { "The navigation item" navigationItem: NavigationItem! + "Whether the navigation item should display its children" + expanded: Boolean + "The child navigation items" items: [NavigationTreeItem] } @@ -227,6 +230,9 @@ input NavigationTreeItemInput { "The ID of the navigation item" navigationItemId: ID! + "Whether the navigation item should display its children" + expanded: Boolean + "The child navigation items" items: [NavigationTreeItemInput] } diff --git a/imports/plugins/core/navigation/server/no-meteor/xforms/xformNavigationTreeItem.js b/imports/plugins/core/navigation/server/no-meteor/xforms/xformNavigationTreeItem.js index 146a04e7807..c14b4fd6fbf 100644 --- a/imports/plugins/core/navigation/server/no-meteor/xforms/xformNavigationTreeItem.js +++ b/imports/plugins/core/navigation/server/no-meteor/xforms/xformNavigationTreeItem.js @@ -11,7 +11,7 @@ import getNavigationItemContentForLanguage from "../util/getNavigationItemConten export default async function xformNavigationTreeItem(context, language, item) { const { collections } = context; const { NavigationItems } = collections; - const { navigationItemId } = item; + const { expanded, navigationItemId } = item; let { items = [] } = item; const navigationItem = await NavigationItems.findOne({ _id: navigationItemId }); @@ -33,6 +33,7 @@ export default async function xformNavigationTreeItem(context, language, item) { return { navigationItem, + expanded, items }; } diff --git a/imports/plugins/core/ui-tagnav/client/components/tagNav.js b/imports/plugins/core/ui-tagnav/client/components/tagNav.js index 5d9f703e85e..aa1ff586b62 100644 --- a/imports/plugins/core/ui-tagnav/client/components/tagNav.js +++ b/imports/plugins/core/ui-tagnav/client/components/tagNav.js @@ -106,27 +106,25 @@ class TagNav extends Component { {this.renderShopSelect()}
- - -
- -
-
-
+ +
+ +
+
{this.props.canEdit && this.renderEditButton()}
diff --git a/imports/plugins/core/ui/client/components/app/app.js b/imports/plugins/core/ui/client/components/app/app.js index 25adc9d81cc..dcc19265369 100644 --- a/imports/plugins/core/ui/client/components/app/app.js +++ b/imports/plugins/core/ui/client/components/app/app.js @@ -121,7 +121,6 @@ class App extends Component { const { currentRoute } = this.props; const layout = currentRoute && currentRoute.route && currentRoute.route.options && currentRoute.route.options.layout; - if (this.isAdminApp && layout !== "printLayout" && !this.noAdminControls) { if (currentRoute.route.path.startsWith("/operator")) { return this.renderOperatorApp(); diff --git a/imports/plugins/core/ui/client/components/media/mediaGallery.js b/imports/plugins/core/ui/client/components/media/mediaGallery.js index 28f6fc10e9f..51c3cf4cc50 100644 --- a/imports/plugins/core/ui/client/components/media/mediaGallery.js +++ b/imports/plugins/core/ui/client/components/media/mediaGallery.js @@ -235,9 +235,9 @@ class MediaGallery extends Component { // Note that only editable mode actually uses drag-drop, but since both views render // MediaItems, which are SortableItems, there is an error if it isn't in the ancester tree return ( - +
{gallery} - +
); } } diff --git a/imports/plugins/core/ui/client/components/media/mediaItem.js b/imports/plugins/core/ui/client/components/media/mediaItem.js index d3f2070c35e..39f2530505d 100644 --- a/imports/plugins/core/ui/client/components/media/mediaItem.js +++ b/imports/plugins/core/ui/client/components/media/mediaItem.js @@ -4,13 +4,11 @@ import PropTypes from "prop-types"; import ReactImageMagnify from "react-image-magnify"; import { Components, registerComponent } from "@reactioncommerce/reaction-components"; import { Reaction } from "/client/api"; -import { SortableItem } from "/imports/plugins/core/ui/client/containers"; import Hint from "./hint"; class MediaItem extends Component { static propTypes = { connectDragSource: PropTypes.func, - connectDropTarget: PropTypes.func, defaultSource: PropTypes.string, editable: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming mediaHeight: PropTypes.number, @@ -166,7 +164,7 @@ class MediaItem extends Component { } render() { - const { connectDragSource, connectDropTarget, editable, zoomable } = this.props; + const { editable, zoomable } = this.props; const classes = { "gallery-image": true, @@ -190,13 +188,13 @@ class MediaItem extends Component { ); if (editable) { - return connectDragSource(connectDropTarget(mediaElement)); + return mediaElement; } return mediaElement; } } -registerComponent("MediaItem", MediaItem, SortableItem("media")); +registerComponent("MediaItem", MediaItem); -export default SortableItem("media")(MediaItem); +export default MediaItem; diff --git a/imports/plugins/core/ui/client/components/tags/tagItem.js b/imports/plugins/core/ui/client/components/tags/tagItem.js index 1325cad2027..25432b74467 100644 --- a/imports/plugins/core/ui/client/components/tags/tagItem.js +++ b/imports/plugins/core/ui/client/components/tags/tagItem.js @@ -5,7 +5,6 @@ import Autosuggest from "react-autosuggest"; import { registerComponent } from "@reactioncommerce/reaction-components"; import { i18next } from "/client/api"; import { Button, Handle } from "/imports/plugins/core/ui/client/components"; -import { SortableItem } from "/imports/plugins/core/ui/client/containers"; import { Router } from "@reactioncommerce/reaction-router"; import { highlightInput } from "../../helpers/animations"; @@ -208,13 +207,13 @@ class TagItem extends Component { }); return ( - this.props.connectDropTarget(
+
- + {this.renderAutosuggestInput()}
-
) +
); } @@ -302,8 +301,6 @@ class TagItem extends Component { TagItem.propTypes = { blank: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming - connectDragSource: PropTypes.func, - connectDropTarget: PropTypes.func, draggable: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming editable: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming fullWidth: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming @@ -326,6 +323,6 @@ TagItem.propTypes = { tag: PropTypes.object }; -registerComponent("TagItem", TagItem, SortableItem("tag")); +registerComponent("TagItem", TagItem); -export default SortableItem("tag")(TagItem); +export default TagItem; diff --git a/imports/plugins/core/ui/client/containers/tagListContainer.js b/imports/plugins/core/ui/client/containers/tagListContainer.js index 5afa3aec105..60b3719521d 100644 --- a/imports/plugins/core/ui/client/containers/tagListContainer.js +++ b/imports/plugins/core/ui/client/containers/tagListContainer.js @@ -194,24 +194,22 @@ const wrapComponent = (Comp) => ( render() { return ( - - - + ); } } diff --git a/imports/plugins/core/ui/client/providers/dragDropProvider.js b/imports/plugins/core/ui/client/providers/dragDropProvider.js index 6ec3da6e47c..76b83e6d659 100644 --- a/imports/plugins/core/ui/client/providers/dragDropProvider.js +++ b/imports/plugins/core/ui/client/providers/dragDropProvider.js @@ -1,28 +1,14 @@ import { Component, Children } from "react"; import PropTypes from "prop-types"; -import { DragDropManager } from "dnd-core"; -import HTML5Backend from "react-dnd-html5-backend"; import { registerComponent } from "@reactioncommerce/reaction-components"; -const defaultManager = new DragDropManager(HTML5Backend); - class DragDropProvider extends Component { - getChildContext() { - return { - dragDropManager: defaultManager - }; - } - render() { // `Children.only` enables us not to add a
for nothing return Children.only(this.props.children); } } -DragDropProvider.childContextTypes = { - dragDropManager: PropTypes.object.isRequired -}; - DragDropProvider.propTypes = { children: PropTypes.node }; diff --git a/imports/plugins/included/product-detail-simple/client/components/variant.js b/imports/plugins/included/product-detail-simple/client/components/variant.js index 074868989f5..7a465053bf3 100644 --- a/imports/plugins/included/product-detail-simple/client/components/variant.js +++ b/imports/plugins/included/product-detail-simple/client/components/variant.js @@ -3,7 +3,6 @@ import PropTypes from "prop-types"; import classnames from "classnames"; import { Components, registerComponent } from "@reactioncommerce/reaction-components"; import { Validation } from "@reactioncommerce/schemas"; -import { SortableItem } from "/imports/plugins/core/ui/client/containers"; import { ReactionProduct } from "/lib/api"; @@ -157,7 +156,7 @@ class Variant extends Component { ); if (this.props.editable) { - return this.props.connectDragSource(this.props.connectDropTarget(variantElement)); + return variantElement; } return variantElement; @@ -165,8 +164,6 @@ class Variant extends Component { } Variant.propTypes = { - connectDragSource: PropTypes.func, - connectDropTarget: PropTypes.func, displayPrice: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), editButton: PropTypes.node, editable: PropTypes.bool, // eslint-disable-line react/boolean-prop-naming @@ -177,6 +174,6 @@ Variant.propTypes = { visibilityButton: PropTypes.node }; -registerComponent("Variant", Variant, SortableItem("product-variant")); +registerComponent("Variant", Variant); -export default SortableItem("product-variant")(Variant); +export default Variant; diff --git a/imports/plugins/included/product-detail-simple/client/containers/variantList.js b/imports/plugins/included/product-detail-simple/client/containers/variantList.js index a93465efa7b..5dca5e65ec9 100644 --- a/imports/plugins/included/product-detail-simple/client/containers/variantList.js +++ b/imports/plugins/included/product-detail-simple/client/containers/variantList.js @@ -159,17 +159,15 @@ class VariantListContainer extends Component { render() { return ( - - - + ); } } diff --git a/package-lock.json b/package-lock.json index af47ebe9306..5e7697886eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4835,22 +4835,6 @@ "integrity": "sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=", "dev": true }, - "disposables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/disposables/-/disposables-1.0.2.tgz", - "integrity": "sha1-NsamdEdfVaLWkTVnpgFETkh7S24=" - }, - "dnd-core": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-2.6.0.tgz", - "integrity": "sha1-ErrWbVh0LG5ffPKUP7aFlED4CcQ=", - "requires": { - "asap": "^2.0.6", - "invariant": "^2.0.0", - "lodash": "^4.2.0", - "redux": "^3.7.1" - } - }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -6206,6 +6190,30 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, + "frontend-collective-react-dnd-scrollzone": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/frontend-collective-react-dnd-scrollzone/-/frontend-collective-react-dnd-scrollzone-1.0.1.tgz", + "integrity": "sha512-N1i4hkN4z3BABWGixx+wYaF8OcTX/hamJnwa47ydmI6cMuKf3vJtBPfMfE4rsnHd0VukvV/b6wNMInyqFIHFsg==", + "requires": { + "hoist-non-react-statics": "^3.1.0", + "lodash.throttle": "^4.0.1", + "prop-types": "^15.5.9", + "raf": "^3.2.0", + "react": "^16.3.0", + "react-display-name": "^0.2.0", + "react-dom": "^16.3.0" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz", + "integrity": "sha512-TFsu3TV3YLY+zFTZDrN8L2DTFanObwmBLpWvJs1qfUuEQ5bTAdFcwfx2T/bsCXfM9QHSLvjfP+nihEl0yvozxw==", + "requires": { + "react-is": "^16.3.2" + } + } + } + }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -7807,6 +7815,11 @@ } } }, + "install": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/install/-/install-0.12.2.tgz", + "integrity": "sha512-+7thTb4Rpvs9mnlhHKGZFJbGOO6kyMgy+gg0sgM5vFzIFK0wrCYXqdlaM71Bi289DTuPHf61puMFsaZBcwDIrg==" + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -10372,11 +10385,6 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==" }, - "lodash-es": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.10.tgz", - "integrity": "sha512-iesFYPmxYYGTcmQK0sL8bX3TGHyM6b2qREaB4kamHfQyfPJP0xgoGxp19nsH16nsfquLdiyKyX3mQkfiSGV8Rg==" - }, "lodash._basecallback": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/lodash._basecallback/-/lodash._basecallback-3.3.1.tgz", @@ -11589,6 +11597,30 @@ "minimist": "0.0.8" } }, + "mobx": { + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/mobx/-/mobx-5.8.0.tgz", + "integrity": "sha512-NsZB+9bF5j+nv9Qwk6bNeE3np26a4TbTGkMpOLf6o1zXoM9BtHPQn/00px4uZ2AXJXtQML5P4MEWdMm6icMIfQ==" + }, + "mobx-react": { + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/mobx-react/-/mobx-react-5.4.3.tgz", + "integrity": "sha512-WC8yFlwvJ91hy8j6CrydAuFteUafcuvdITFQeHl3LRIf5ayfT/4W3M/byhEYD2BcJWejeXr8y4Rh2H26RunCRQ==", + "requires": { + "hoist-non-react-statics": "^3.0.0", + "react-lifecycles-compat": "^3.0.2" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz", + "integrity": "sha512-TFsu3TV3YLY+zFTZDrN8L2DTFanObwmBLpWvJs1qfUuEQ5bTAdFcwfx2T/bsCXfM9QHSLvjfP+nihEl0yvozxw==", + "requires": { + "react-is": "^16.3.2" + } + } + } + }, "modelo": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/modelo/-/modelo-4.2.3.tgz", @@ -12161,225 +12193,2936 @@ "resolved": "https://registry.npmjs.org/nouislider-algolia-fork/-/nouislider-algolia-fork-10.0.0.tgz", "integrity": "sha1-ZtCpQaqgymTlg3nme9MglW88884=" }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "requires": { - "path-key": "^2.0.0" - } - }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "nth-check": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", - "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", - "dev": true, - "requires": { - "boolbase": "~1.0.0" - } - }, - "num2fraction": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", - "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "nwsapi": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", - "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "npm": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/npm/-/npm-6.5.0.tgz", + "integrity": "sha512-SPq8zG2Kto+Xrq55E97O14Jla13PmQT5kSnvwBj88BmJZ5Nvw++OmlWfhjkB67pcgP5UEXljEtnGFKZtOgt6MQ==", + "requires": { + "JSONStream": "^1.3.4", + "abbrev": "~1.1.1", + "ansicolors": "~0.3.2", + "ansistyles": "~0.1.3", + "aproba": "~1.2.0", + "archy": "~1.0.0", + "bin-links": "^1.1.2", + "bluebird": "^3.5.3", + "byte-size": "^4.0.3", + "cacache": "^11.2.0", + "call-limit": "~1.1.0", + "chownr": "~1.0.1", + "ci-info": "^1.6.0", + "cli-columns": "^3.1.2", + "cli-table3": "^0.5.0", + "cmd-shim": "~2.0.2", + "columnify": "~1.5.4", + "config-chain": "^1.1.12", + "debuglog": "*", + "detect-indent": "~5.0.0", + "detect-newline": "^2.1.0", + "dezalgo": "~1.0.3", + "editor": "~1.0.0", + "figgy-pudding": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "fs-vacuum": "~1.2.10", + "fs-write-stream-atomic": "~1.0.10", + "gentle-fs": "^2.0.1", + "glob": "^7.1.3", + "graceful-fs": "^4.1.15", + "has-unicode": "~2.0.1", + "hosted-git-info": "^2.7.1", + "iferr": "^1.0.2", + "imurmurhash": "*", + "inflight": "~1.0.6", + "inherits": "~2.0.3", + "ini": "^1.3.5", + "init-package-json": "^1.10.3", + "is-cidr": "^2.0.6", + "json-parse-better-errors": "^1.0.2", + "lazy-property": "~1.0.0", + "libcipm": "^2.0.2", + "libnpmhook": "^4.0.1", + "libnpx": "^10.2.0", + "lock-verify": "^2.0.2", + "lockfile": "^1.0.4", + "lodash._baseindexof": "*", + "lodash._baseuniq": "~4.6.0", + "lodash._bindcallback": "*", + "lodash._cacheindexof": "*", + "lodash._createcache": "*", + "lodash._getnative": "*", + "lodash.clonedeep": "~4.5.0", + "lodash.restparam": "*", + "lodash.union": "~4.6.0", + "lodash.uniq": "~4.5.0", + "lodash.without": "~4.4.0", + "lru-cache": "^4.1.3", + "meant": "~1.0.1", + "mississippi": "^3.0.0", + "mkdirp": "~0.5.1", + "move-concurrently": "^1.0.1", + "node-gyp": "^3.8.0", + "nopt": "~4.0.1", + "normalize-package-data": "~2.4.0", + "npm-audit-report": "^1.3.1", + "npm-cache-filename": "~1.0.2", + "npm-install-checks": "~3.0.0", + "npm-lifecycle": "^2.1.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.12", + "npm-pick-manifest": "^2.1.0", + "npm-profile": "^3.0.2", + "npm-registry-client": "^8.6.0", + "npm-registry-fetch": "^1.1.0", + "npm-user-validate": "~1.0.0", + "npmlog": "~4.1.2", + "once": "~1.4.0", + "opener": "^1.5.1", + "osenv": "^0.1.5", + "pacote": "^8.1.6", + "path-is-inside": "~1.0.2", + "promise-inflight": "~1.0.1", + "qrcode-terminal": "^0.12.0", + "query-string": "^6.1.0", + "qw": "~1.0.1", + "read": "~1.0.7", + "read-cmd-shim": "~1.0.1", + "read-installed": "~4.0.3", + "read-package-json": "^2.0.13", + "read-package-tree": "^5.2.1", + "readable-stream": "^2.3.6", + "readdir-scoped-modules": "*", + "request": "^2.88.0", + "retry": "^0.12.0", + "rimraf": "~2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.1", + "sha": "~2.0.1", + "slide": "~1.1.6", + "sorted-object": "~2.0.1", + "sorted-union-stream": "~2.1.3", + "ssri": "^6.0.1", + "stringify-package": "^1.0.0", + "tar": "^4.4.8", + "text-table": "~0.2.0", + "tiny-relative-date": "^1.3.0", + "uid-number": "0.0.6", + "umask": "~1.1.0", + "unique-filename": "~1.1.0", + "unpipe": "~1.0.0", + "update-notifier": "^2.5.0", + "uuid": "^3.3.2", + "validate-npm-package-license": "^3.0.4", + "validate-npm-package-name": "~3.0.0", + "which": "^1.3.1", + "worker-farm": "^1.6.0", + "write-file-atomic": "^2.3.0" }, "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "JSONStream": { + "version": "1.3.4", + "bundled": true, "requires": { - "is-descriptor": "^0.1.0" + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" } - } - } - }, - "object-hash": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", - "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==" - }, - "object-inspect": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", - "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", - "dev": true - }, - "object-is": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", - "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" - }, - "object-keys": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", - "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - }, - "dependencies": { - "object-keys": { - "version": "1.0.12", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", - "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" - } - } - }, - "object.entries": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", - "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.omit": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", - "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", - "requires": { - "is-extendable": "^1.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "object.values": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", - "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.6.1", - "function-bind": "^1.1.0", - "has": "^1.0.1" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "dev": true, - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "opn": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", - "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", - "dev": true, - "requires": { - "is-wsl": "^1.1.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", + }, + "abbrev": { + "version": "1.1.1", + "bundled": true + }, + "agent-base": { + "version": "4.2.0", + "bundled": true, + "requires": { + "es6-promisify": "^5.0.0" + } + }, + "agentkeepalive": { + "version": "3.4.1", + "bundled": true, + "requires": { + "humanize-ms": "^1.2.1" + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ansi-align": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.0.0" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "ansicolors": { + "version": "0.3.2", + "bundled": true + }, + "ansistyles": { + "version": "0.1.3", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true + }, + "archy": { + "version": "1.0.0", + "bundled": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bin-links": { + "version": "1.1.2", + "bundled": true, + "requires": { + "bluebird": "^3.5.0", + "cmd-shim": "^2.0.2", + "gentle-fs": "^2.0.0", + "graceful-fs": "^4.1.11", + "write-file-atomic": "^2.3.0" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "requires": { + "inherits": "~2.0.0" + } + }, + "bluebird": { + "version": "3.5.3", + "bundled": true + }, + "boxen": { + "version": "1.3.0", + "bundled": true, + "requires": { + "ansi-align": "^2.0.0", + "camelcase": "^4.0.0", + "chalk": "^2.0.1", + "cli-boxes": "^1.0.0", + "string-width": "^2.0.0", + "term-size": "^1.2.0", + "widest-line": "^2.0.0" + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true + }, + "builtins": { + "version": "1.0.3", + "bundled": true + }, + "byline": { + "version": "5.0.0", + "bundled": true + }, + "byte-size": { + "version": "4.0.3", + "bundled": true + }, + "cacache": { + "version": "11.2.0", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "figgy-pudding": "^3.1.0", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^6.0.0", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + } + }, + "call-limit": { + "version": "1.1.0", + "bundled": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true + }, + "capture-stack-trace": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chownr": { + "version": "1.0.1", + "bundled": true + }, + "ci-info": { + "version": "1.6.0", + "bundled": true + }, + "cidr-regex": { + "version": "2.0.9", + "bundled": true, + "requires": { + "ip-regex": "^2.1.0" + } + }, + "cli-boxes": { + "version": "1.0.0", + "bundled": true + }, + "cli-columns": { + "version": "3.1.2", + "bundled": true, + "requires": { + "string-width": "^2.0.0", + "strip-ansi": "^3.0.1" + } + }, + "cli-table3": { + "version": "0.5.0", + "bundled": true, + "requires": { + "colors": "^1.1.2", + "object-assign": "^4.1.0", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "clone": { + "version": "1.0.4", + "bundled": true + }, + "cmd-shim": { + "version": "2.0.2", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "mkdirp": "~0.5.0" + } + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-name": { + "version": "1.1.3", + "bundled": true + }, + "colors": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "columnify": { + "version": "1.5.4", + "bundled": true, + "requires": { + "strip-ansi": "^3.0.0", + "wcwidth": "^1.0.0" + } + }, + "combined-stream": { + "version": "1.0.6", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "config-chain": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ini": "^1.3.4", + "proto-list": "~1.2.1" + } + }, + "configstore": { + "version": "3.1.2", + "bundled": true, + "requires": { + "dot-prop": "^4.1.0", + "graceful-fs": "^4.1.2", + "make-dir": "^1.0.0", + "unique-string": "^1.0.0", + "write-file-atomic": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "copy-concurrently": { + "version": "1.0.5", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "fs-write-stream-atomic": "^1.0.8", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.0" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "create-error-class": { + "version": "3.0.2", + "bundled": true, + "requires": { + "capture-stack-trace": "^1.0.0" + } + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "crypto-random-string": { + "version": "1.0.0", + "bundled": true + }, + "cyclist": { + "version": "0.2.2", + "bundled": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "bundled": true + } + } + }, + "debuglog": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true + }, + "deep-extend": { + "version": "0.5.1", + "bundled": true + }, + "defaults": { + "version": "1.0.3", + "bundled": true, + "requires": { + "clone": "^1.0.2" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true + }, + "detect-indent": { + "version": "5.0.0", + "bundled": true + }, + "detect-newline": { + "version": "2.1.0", + "bundled": true + }, + "dezalgo": { + "version": "1.0.3", + "bundled": true, + "requires": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "dot-prop": { + "version": "4.2.0", + "bundled": true, + "requires": { + "is-obj": "^1.0.0" + } + }, + "dotenv": { + "version": "5.0.1", + "bundled": true + }, + "duplexer3": { + "version": "0.1.4", + "bundled": true + }, + "duplexify": { + "version": "3.6.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.0.0", + "inherits": "^2.0.1", + "readable-stream": "^2.0.0", + "stream-shift": "^1.0.0" + } + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "editor": { + "version": "1.0.0", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "end-of-stream": { + "version": "1.4.1", + "bundled": true, + "requires": { + "once": "^1.4.0" + } + }, + "err-code": { + "version": "1.1.2", + "bundled": true + }, + "errno": { + "version": "0.1.7", + "bundled": true, + "requires": { + "prr": "~1.0.1" + } + }, + "es6-promise": { + "version": "4.2.4", + "bundled": true + }, + "es6-promisify": { + "version": "5.0.0", + "bundled": true, + "requires": { + "es6-promise": "^4.0.3" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + } + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "figgy-pudding": { + "version": "3.5.1", + "bundled": true + }, + "find-npm-prefix": { + "version": "1.0.2", + "bundled": true + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flush-write-stream": { + "version": "1.0.3", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.4" + } + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true + }, + "form-data": { + "version": "2.3.2", + "bundled": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "1.0.6", + "mime-types": "^2.1.12" + } + }, + "from2": { + "version": "2.3.0", + "bundled": true, + "requires": { + "inherits": "^2.0.1", + "readable-stream": "^2.0.0" + } + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs-vacuum": { + "version": "1.2.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "path-is-inside": "^1.0.1", + "rimraf": "^2.5.2" + } + }, + "fs-write-stream-atomic": { + "version": "1.0.10", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "iferr": "^0.1.5", + "imurmurhash": "^0.1.4", + "readable-stream": "1 || 2" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "inherits": "~2.0.0", + "mkdirp": ">=0.5 0", + "rimraf": "2" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "genfun": { + "version": "4.0.1", + "bundled": true + }, + "gentle-fs": { + "version": "2.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.2", + "fs-vacuum": "^1.2.10", + "graceful-fs": "^4.1.11", + "iferr": "^0.1.5", + "mkdirp": "^0.5.1", + "path-is-inside": "^1.0.2", + "read-cmd-shim": "^1.0.1", + "slide": "^1.1.6" + }, + "dependencies": { + "iferr": { + "version": "0.1.5", + "bundled": true + } + } + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "global-dirs": { + "version": "0.1.1", + "bundled": true, + "requires": { + "ini": "^1.3.4" + } + }, + "got": { + "version": "6.7.1", + "bundled": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.1.15", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true + }, + "hosted-git-info": { + "version": "2.7.1", + "bundled": true + }, + "http-cache-semantics": { + "version": "3.8.1", + "bundled": true + }, + "http-proxy-agent": { + "version": "2.1.0", + "bundled": true, + "requires": { + "agent-base": "4", + "debug": "3.1.0" + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "https-proxy-agent": { + "version": "2.2.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "debug": "^3.1.0" + } + }, + "humanize-ms": { + "version": "1.2.1", + "bundled": true, + "requires": { + "ms": "^2.0.0" + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "iferr": { + "version": "1.0.2", + "bundled": true + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "import-lazy": { + "version": "2.1.0", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true + }, + "init-package-json": { + "version": "1.10.3", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "npm-package-arg": "^4.0.0 || ^5.0.0 || ^6.0.0", + "promzard": "^0.3.0", + "read": "~1.0.1", + "read-package-json": "1 || 2", + "semver": "2.x || 3.x || 4 || 5", + "validate-npm-package-license": "^3.0.1", + "validate-npm-package-name": "^3.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true + }, + "ip": { + "version": "1.1.5", + "bundled": true + }, + "ip-regex": { + "version": "2.1.0", + "bundled": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-ci": { + "version": "1.1.0", + "bundled": true, + "requires": { + "ci-info": "^1.0.0" + } + }, + "is-cidr": { + "version": "2.0.6", + "bundled": true, + "requires": { + "cidr-regex": "^2.0.8" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-installed-globally": { + "version": "0.1.0", + "bundled": true, + "requires": { + "global-dirs": "^0.1.0", + "is-path-inside": "^1.0.0" + } + }, + "is-npm": { + "version": "1.0.0", + "bundled": true + }, + "is-obj": { + "version": "1.0.1", + "bundled": true + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-redirect": { + "version": "1.0.0", + "bundled": true + }, + "is-retry-allowed": { + "version": "1.1.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true + }, + "jsonparse": { + "version": "1.3.1", + "bundled": true + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "latest-version": { + "version": "3.1.0", + "bundled": true, + "requires": { + "package-json": "^4.0.0" + } + }, + "lazy-property": { + "version": "1.0.0", + "bundled": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "libcipm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "bin-links": "^1.1.2", + "bluebird": "^3.5.1", + "find-npm-prefix": "^1.0.2", + "graceful-fs": "^4.1.11", + "lock-verify": "^2.0.2", + "mkdirp": "^0.5.1", + "npm-lifecycle": "^2.0.3", + "npm-logical-tree": "^1.2.1", + "npm-package-arg": "^6.1.0", + "pacote": "^8.1.6", + "protoduck": "^5.0.0", + "read-package-json": "^2.0.13", + "rimraf": "^2.6.2", + "worker-farm": "^1.6.0" + } + }, + "libnpmhook": { + "version": "4.0.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.1.0", + "npm-registry-fetch": "^3.0.0" + }, + "dependencies": { + "npm-registry-fetch": { + "version": "3.1.1", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^3.1.0", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^4.0.0", + "npm-package-arg": "^6.0.0" + } + } + } + }, + "libnpx": { + "version": "10.2.0", + "bundled": true, + "requires": { + "dotenv": "^5.0.1", + "npm-package-arg": "^6.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.0", + "update-notifier": "^2.3.0", + "which": "^1.3.0", + "y18n": "^4.0.0", + "yargs": "^11.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lock-verify": { + "version": "2.0.2", + "bundled": true, + "requires": { + "npm-package-arg": "^5.1.2 || 6", + "semver": "^5.4.1" + } + }, + "lockfile": { + "version": "1.0.4", + "bundled": true, + "requires": { + "signal-exit": "^3.0.2" + } + }, + "lodash._baseindexof": { + "version": "3.1.0", + "bundled": true + }, + "lodash._baseuniq": { + "version": "4.6.0", + "bundled": true, + "requires": { + "lodash._createset": "~4.0.0", + "lodash._root": "~3.0.0" + } + }, + "lodash._bindcallback": { + "version": "3.0.1", + "bundled": true + }, + "lodash._cacheindexof": { + "version": "3.0.2", + "bundled": true + }, + "lodash._createcache": { + "version": "3.1.2", + "bundled": true, + "requires": { + "lodash._getnative": "^3.0.0" + } + }, + "lodash._createset": { + "version": "4.0.3", + "bundled": true + }, + "lodash._getnative": { + "version": "3.9.1", + "bundled": true + }, + "lodash._root": { + "version": "3.0.1", + "bundled": true + }, + "lodash.clonedeep": { + "version": "4.5.0", + "bundled": true + }, + "lodash.restparam": { + "version": "3.6.1", + "bundled": true + }, + "lodash.union": { + "version": "4.6.0", + "bundled": true + }, + "lodash.uniq": { + "version": "4.5.0", + "bundled": true + }, + "lodash.without": { + "version": "4.4.0", + "bundled": true + }, + "lowercase-keys": { + "version": "1.0.1", + "bundled": true + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "make-dir": { + "version": "1.3.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "make-fetch-happen": { + "version": "4.0.1", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^11.0.1", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.1", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^4.0.0", + "ssri": "^6.0.0" + } + }, + "meant": { + "version": "1.0.1", + "bundled": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "mime-db": { + "version": "1.35.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.19", + "bundled": true, + "requires": { + "mime-db": "~1.35.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.3", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.2", + "bundled": true + } + } + }, + "minizlib": { + "version": "1.1.1", + "bundled": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mississippi": { + "version": "3.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^3.0.0", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "move-concurrently": { + "version": "1.0.1", + "bundled": true, + "requires": { + "aproba": "^1.1.1", + "copy-concurrently": "^1.0.0", + "fs-write-stream-atomic": "^1.0.8", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.4", + "run-queue": "^1.0.3" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true + }, + "node-fetch-npm": { + "version": "2.0.2", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "json-parse-better-errors": "^1.0.0", + "safe-buffer": "^5.1.1" + } + }, + "node-gyp": { + "version": "3.8.0", + "bundled": true, + "requires": { + "fstream": "^1.0.0", + "glob": "^7.0.3", + "graceful-fs": "^4.1.2", + "mkdirp": "^0.5.0", + "nopt": "2 || 3", + "npmlog": "0 || 1 || 2 || 3 || 4", + "osenv": "0", + "request": "^2.87.0", + "rimraf": "2", + "semver": "~5.3.0", + "tar": "^2.0.0", + "which": "1" + }, + "dependencies": { + "nopt": { + "version": "3.0.6", + "bundled": true, + "requires": { + "abbrev": "1" + } + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "requires": { + "block-stream": "*", + "fstream": "^1.0.2", + "inherits": "2" + } + } + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-audit-report": { + "version": "1.3.1", + "bundled": true, + "requires": { + "cli-table3": "^0.5.0", + "console-control-strings": "^1.1.0" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true + }, + "npm-cache-filename": { + "version": "1.0.2", + "bundled": true + }, + "npm-install-checks": { + "version": "3.0.0", + "bundled": true, + "requires": { + "semver": "^2.3.0 || 3.x || 4 || 5" + } + }, + "npm-lifecycle": { + "version": "2.1.0", + "bundled": true, + "requires": { + "byline": "^5.0.0", + "graceful-fs": "^4.1.11", + "node-gyp": "^3.8.0", + "resolve-from": "^4.0.0", + "slide": "^1.1.6", + "uid-number": "0.0.6", + "umask": "^1.1.0", + "which": "^1.3.1" + } + }, + "npm-logical-tree": { + "version": "1.2.1", + "bundled": true + }, + "npm-package-arg": { + "version": "6.1.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.6.0", + "osenv": "^0.1.5", + "semver": "^5.5.0", + "validate-npm-package-name": "^3.0.0" + } + }, + "npm-packlist": { + "version": "1.1.12", + "bundled": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npm-pick-manifest": { + "version": "2.1.0", + "bundled": true, + "requires": { + "npm-package-arg": "^6.0.0", + "semver": "^5.4.1" + } + }, + "npm-profile": { + "version": "3.0.2", + "bundled": true, + "requires": { + "aproba": "^1.1.2 || 2", + "make-fetch-happen": "^2.5.0 || 3 || 4" + } + }, + "npm-registry-client": { + "version": "8.6.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.2", + "graceful-fs": "^4.1.6", + "normalize-package-data": "~1.0.1 || ^2.0.0", + "npm-package-arg": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", + "npmlog": "2 || ^3.1.0 || ^4.0.0", + "once": "^1.3.3", + "request": "^2.74.0", + "retry": "^0.10.0", + "safe-buffer": "^5.1.1", + "semver": "2 >=2.2.1 || 3.x || 4 || 5", + "slide": "^1.1.3", + "ssri": "^5.2.4" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-registry-fetch": { + "version": "1.1.0", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "figgy-pudding": "^2.0.1", + "lru-cache": "^4.1.2", + "make-fetch-happen": "^3.0.0", + "npm-package-arg": "^6.0.0", + "safe-buffer": "^5.1.1" + }, + "dependencies": { + "cacache": { + "version": "10.0.4", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "chownr": "^1.0.1", + "glob": "^7.1.2", + "graceful-fs": "^4.1.11", + "lru-cache": "^4.1.1", + "mississippi": "^2.0.0", + "mkdirp": "^0.5.1", + "move-concurrently": "^1.0.1", + "promise-inflight": "^1.0.1", + "rimraf": "^2.6.2", + "ssri": "^5.2.4", + "unique-filename": "^1.1.0", + "y18n": "^4.0.0" + }, + "dependencies": { + "mississippi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "concat-stream": "^1.5.0", + "duplexify": "^3.4.2", + "end-of-stream": "^1.1.0", + "flush-write-stream": "^1.0.0", + "from2": "^2.1.0", + "parallel-transform": "^1.1.0", + "pump": "^2.0.1", + "pumpify": "^1.3.3", + "stream-each": "^1.1.0", + "through2": "^2.0.0" + } + } + } + }, + "figgy-pudding": { + "version": "2.0.1", + "bundled": true + }, + "make-fetch-happen": { + "version": "3.0.0", + "bundled": true, + "requires": { + "agentkeepalive": "^3.4.1", + "cacache": "^10.0.4", + "http-cache-semantics": "^3.8.1", + "http-proxy-agent": "^2.1.0", + "https-proxy-agent": "^2.2.0", + "lru-cache": "^4.1.2", + "mississippi": "^3.0.0", + "node-fetch-npm": "^2.0.2", + "promise-retry": "^1.1.1", + "socks-proxy-agent": "^3.0.1", + "ssri": "^5.2.4" + } + }, + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "smart-buffer": { + "version": "1.1.15", + "bundled": true + }, + "socks": { + "version": "1.1.10", + "bundled": true, + "requires": { + "ip": "^1.1.4", + "smart-buffer": "^1.0.13" + } + }, + "socks-proxy-agent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "agent-base": "^4.1.0", + "socks": "^1.1.10" + } + }, + "ssri": { + "version": "5.3.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.1" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "npm-user-validate": { + "version": "1.0.0", + "bundled": true + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "opener": { + "version": "1.5.1", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true + }, + "package-json": { + "version": "4.0.1", + "bundled": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + } + }, + "pacote": { + "version": "8.1.6", + "bundled": true, + "requires": { + "bluebird": "^3.5.1", + "cacache": "^11.0.2", + "get-stream": "^3.0.0", + "glob": "^7.1.2", + "lru-cache": "^4.1.3", + "make-fetch-happen": "^4.0.1", + "minimatch": "^3.0.4", + "minipass": "^2.3.3", + "mississippi": "^3.0.0", + "mkdirp": "^0.5.1", + "normalize-package-data": "^2.4.0", + "npm-package-arg": "^6.1.0", + "npm-packlist": "^1.1.10", + "npm-pick-manifest": "^2.1.0", + "osenv": "^0.1.5", + "promise-inflight": "^1.0.1", + "promise-retry": "^1.1.1", + "protoduck": "^5.0.0", + "rimraf": "^2.6.2", + "safe-buffer": "^5.1.2", + "semver": "^5.5.0", + "ssri": "^6.0.0", + "tar": "^4.4.3", + "unique-filename": "^1.1.0", + "which": "^1.3.0" + } + }, + "parallel-transform": { + "version": "1.1.0", + "bundled": true, + "requires": { + "cyclist": "~0.2.2", + "inherits": "^2.0.3", + "readable-stream": "^2.1.5" + } + }, + "path-exists": { + "version": "3.0.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true + }, + "performance-now": { + "version": "2.1.0", + "bundled": true + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "prepend-http": { + "version": "1.0.4", + "bundled": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true + }, + "promise-inflight": { + "version": "1.0.1", + "bundled": true + }, + "promise-retry": { + "version": "1.1.1", + "bundled": true, + "requires": { + "err-code": "^1.0.0", + "retry": "^0.10.0" + }, + "dependencies": { + "retry": { + "version": "0.10.1", + "bundled": true + } + } + }, + "promzard": { + "version": "0.3.0", + "bundled": true, + "requires": { + "read": "1" + } + }, + "proto-list": { + "version": "1.2.4", + "bundled": true + }, + "protoduck": { + "version": "5.0.0", + "bundled": true, + "requires": { + "genfun": "^4.0.1" + } + }, + "prr": { + "version": "1.0.1", + "bundled": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true + }, + "pump": { + "version": "3.0.0", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "pumpify": { + "version": "1.5.1", + "bundled": true, + "requires": { + "duplexify": "^3.6.0", + "inherits": "^2.0.3", + "pump": "^2.0.0" + }, + "dependencies": { + "pump": { + "version": "2.0.1", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + } + } + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qrcode-terminal": { + "version": "0.12.0", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true + }, + "query-string": { + "version": "6.1.0", + "bundled": true, + "requires": { + "decode-uri-component": "^0.2.0", + "strict-uri-encode": "^2.0.0" + } + }, + "qw": { + "version": "1.0.1", + "bundled": true + }, + "rc": { + "version": "1.2.7", + "bundled": true, + "requires": { + "deep-extend": "^0.5.1", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "read": { + "version": "1.0.7", + "bundled": true, + "requires": { + "mute-stream": "~0.0.4" + } + }, + "read-cmd-shim": { + "version": "1.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2" + } + }, + "read-installed": { + "version": "4.0.3", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "graceful-fs": "^4.1.2", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + } + }, + "read-package-json": { + "version": "2.0.13", + "bundled": true, + "requires": { + "glob": "^7.1.1", + "graceful-fs": "^4.1.2", + "json-parse-better-errors": "^1.0.1", + "normalize-package-data": "^2.0.0", + "slash": "^1.0.0" + } + }, + "read-package-tree": { + "version": "5.2.1", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "once": "^1.3.0", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readdir-scoped-modules": { + "version": "1.0.2", + "bundled": true, + "requires": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, + "registry-auth-token": { + "version": "3.3.2", + "bundled": true, + "requires": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "registry-url": { + "version": "3.1.0", + "bundled": true, + "requires": { + "rc": "^1.0.1" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true + }, + "resolve-from": { + "version": "4.0.0", + "bundled": true + }, + "retry": { + "version": "0.12.0", + "bundled": true + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "run-queue": { + "version": "1.0.3", + "bundled": true, + "requires": { + "aproba": "^1.1.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "semver": { + "version": "5.5.1", + "bundled": true + }, + "semver-diff": { + "version": "2.1.0", + "bundled": true, + "requires": { + "semver": "^5.0.3" + } + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true + }, + "sha": { + "version": "2.0.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "readable-stream": "^2.0.2" + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true + }, + "slide": { + "version": "1.1.6", + "bundled": true + }, + "smart-buffer": { + "version": "4.0.1", + "bundled": true + }, + "socks": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ip": "^1.1.5", + "smart-buffer": "^4.0.1" + } + }, + "socks-proxy-agent": { + "version": "4.0.1", + "bundled": true, + "requires": { + "agent-base": "~4.2.0", + "socks": "~2.2.0" + } + }, + "sorted-object": { + "version": "2.0.1", + "bundled": true + }, + "sorted-union-stream": { + "version": "2.1.3", + "bundled": true, + "requires": { + "from2": "^1.3.0", + "stream-iterate": "^1.1.0" + }, + "dependencies": { + "from2": { + "version": "1.3.0", + "bundled": true, + "requires": { + "inherits": "~2.0.1", + "readable-stream": "~1.1.10" + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true + }, + "sshpk": { + "version": "1.14.2", + "bundled": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "ssri": { + "version": "6.0.1", + "bundled": true, + "requires": { + "figgy-pudding": "^3.5.1" + } + }, + "stream-each": { + "version": "1.2.2", + "bundled": true, + "requires": { + "end-of-stream": "^1.1.0", + "stream-shift": "^1.0.0" + } + }, + "stream-iterate": { + "version": "1.2.0", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "stream-shift": "^1.0.0" + } + }, + "stream-shift": { + "version": "1.0.0", + "bundled": true + }, + "strict-uri-encode": { + "version": "2.0.0", + "bundled": true + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "stringify-package": { + "version": "1.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + }, + "dependencies": { + "chownr": { + "version": "1.1.1", + "bundled": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "yallist": { + "version": "3.0.3", + "bundled": true + } + } + }, + "term-size": { + "version": "1.2.0", + "bundled": true, + "requires": { + "execa": "^0.7.0" + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + }, + "through2": { + "version": "2.0.3", + "bundled": true, + "requires": { + "readable-stream": "^2.1.5", + "xtend": "~4.0.1" + } + }, + "timed-out": { + "version": "4.0.1", + "bundled": true + }, + "tiny-relative-date": { + "version": "1.3.0", + "bundled": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "typedarray": { + "version": "0.0.6", + "bundled": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true + }, + "umask": { + "version": "1.1.0", + "bundled": true + }, + "unique-filename": { + "version": "1.1.0", + "bundled": true, + "requires": { + "unique-slug": "^2.0.0" + } + }, + "unique-slug": { + "version": "2.0.0", + "bundled": true, + "requires": { + "imurmurhash": "^0.1.4" + } + }, + "unique-string": { + "version": "1.0.0", + "bundled": true, + "requires": { + "crypto-random-string": "^1.0.0" + } + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "unzip-response": { + "version": "2.0.1", + "bundled": true + }, + "update-notifier": { + "version": "2.5.0", + "bundled": true, + "requires": { + "boxen": "^1.2.1", + "chalk": "^2.0.1", + "configstore": "^3.0.0", + "import-lazy": "^2.1.0", + "is-ci": "^1.0.10", + "is-installed-globally": "^0.1.0", + "is-npm": "^1.0.0", + "latest-version": "^3.0.0", + "semver-diff": "^2.0.0", + "xdg-basedir": "^3.0.0" + } + }, + "url-parse-lax": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prepend-http": "^1.0.1" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "util-extend": { + "version": "1.0.3", + "bundled": true + }, + "uuid": { + "version": "3.3.2", + "bundled": true + }, + "validate-npm-package-license": { + "version": "3.0.4", + "bundled": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "validate-npm-package-name": { + "version": "3.0.0", + "bundled": true, + "requires": { + "builtins": "^1.0.3" + } + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "bundled": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "requires": { + "string-width": "^1.0.2" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "widest-line": { + "version": "2.0.0", + "bundled": true, + "requires": { + "string-width": "^2.1.1" + } + }, + "worker-farm": { + "version": "1.6.0", + "bundled": true, + "requires": { + "errno": "~0.1.7" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "bundled": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write-file-atomic": { + "version": "2.3.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "xdg-basedir": { + "version": "3.0.0", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + }, + "y18n": { + "version": "4.0.0", + "bundled": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true + }, + "yargs": { + "version": "11.0.0", + "bundled": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "y18n": { + "version": "3.2.1", + "bundled": true + } + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "requires": { + "path-key": "^2.0.0" + } + }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "nth-check": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", + "integrity": "sha1-mSms32KPwsQQmN6rgqxYDPFJquQ=", + "dev": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "num2fraction": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/num2fraction/-/num2fraction-1.2.2.tgz", + "integrity": "sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4=" + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "nwsapi": { + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.0.9.tgz", + "integrity": "sha512-nlWFSCTYQcHk/6A9FFnfhKc14c3aFhfdNBXgo8Qgi9QTBu/qg3Ww+Uiz9wMzXd1T8GFxPc2QIHB6Qtf2XFryFQ==", + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", + "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-hash": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-1.3.0.tgz", + "integrity": "sha512-05KzQ70lSeGSrZJQXE5wNDiTkBJDlUT/myi6RX9dVIvz7a7Qh4oH93BQdiPMn27nldYvVQCKMUaM83AfizZlsQ==" + }, + "object-inspect": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", + "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", + "dev": true + }, + "object-is": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.0.1.tgz", + "integrity": "sha1-CqYOyZiaCz7Xlc9NBvYs8a1lObY=" + }, + "object-keys": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-0.4.0.tgz", + "integrity": "sha1-KKaq50KN0sOpLz2V8hM13SBOAzY=" + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "dependencies": { + "object-keys": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz", + "integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==" + } + } + }, + "object.entries": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.0.4.tgz", + "integrity": "sha1-G/mk3SKI9bM/Opk9JXZh8F0WGl8=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, + "object.getownpropertydescriptors": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", + "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.5.1" + } + }, + "object.omit": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz", + "integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==", + "requires": { + "is-extendable": "^1.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "object.values": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.0.4.tgz", + "integrity": "sha1-5STaCbT2b/Bd9FdUbscqyZ8TBpo=", + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.6.1", + "function-bind": "^1.1.0", + "has": "^1.0.1" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", + "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "opn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", + "integrity": "sha512-bYJHo/LOmoTd+pfiYhfZDnf9zekVJrY+cnS2a5F2x+w5ppvTqObojTP7WiFG+kVZs9Inw+qQ/lw7TroWwhdd2g==", + "dev": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "optimist": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", + "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "optionator": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", "dev": true, "requires": { @@ -13292,25 +16035,113 @@ "react-with-styles-interface-css": "^4.0.2" } }, + "react-display-name": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/react-display-name/-/react-display-name-0.2.4.tgz", + "integrity": "sha512-zvU6iouW+SWwHTyThwxGICjJYCMZFk/6r/+jmOdC7ntQoPlS/Pqb81MkxaMf2bHTSq9TN3K3zX2/ayMW/jCtyA==" + }, "react-dnd": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-2.6.0.tgz", - "integrity": "sha1-f6JWds+CfViokSk+PBq1naACVFo=", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-7.0.2.tgz", + "integrity": "sha512-nJnHJo/tNQjyod234+hPNopWHPvgH0gujf3pcdJWRe3l0GL+jSXXwXJ2SFwIHkVmxPYrx8+gbKU3+Pq26p6fkg==", "requires": { - "disposables": "^1.0.1", - "dnd-core": "^2.6.0", - "hoist-non-react-statics": "^2.1.0", + "dnd-core": "^7.0.2", + "hoist-non-react-statics": "^3.1.0", "invariant": "^2.1.0", - "lodash": "^4.2.0", - "prop-types": "^15.5.10" + "lodash": "^4.17.11", + "recompose": "^0.30.0", + "shallowequal": "^1.1.0" + }, + "dependencies": { + "dnd-core": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-7.0.2.tgz", + "integrity": "sha512-InwRBi6zTndtE3+3nTYpLJkYMEr7utSR7OziAoSFhtQsbUfJE1KeqxM+ZFRIMKn6ehxUTAC+QU6QC7IG9u86Mg==", + "requires": { + "asap": "^2.0.6", + "invariant": "^2.2.4", + "lodash": "^4.17.11", + "redux": "^4.0.1" + } + }, + "hoist-non-react-statics": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.2.1.tgz", + "integrity": "sha512-TFsu3TV3YLY+zFTZDrN8L2DTFanObwmBLpWvJs1qfUuEQ5bTAdFcwfx2T/bsCXfM9QHSLvjfP+nihEl0yvozxw==", + "requires": { + "react-is": "^16.3.2" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "recompose": { + "version": "0.30.0", + "resolved": "https://registry.npmjs.org/recompose/-/recompose-0.30.0.tgz", + "integrity": "sha512-ZTrzzUDa9AqUIhRk4KmVFihH0rapdCSMFXjhHbNrjAWxBuUD/guYlyysMnuHjlZC/KRiOKRtB4jf96yYSkKE8w==", + "requires": { + "@babel/runtime": "^7.0.0", + "change-emitter": "^0.1.2", + "fbjs": "^0.8.1", + "hoist-non-react-statics": "^2.3.1", + "react-lifecycles-compat": "^3.0.2", + "symbol-observable": "^1.0.4" + }, + "dependencies": { + "hoist-non-react-statics": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz", + "integrity": "sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw==" + } + } + }, + "redux": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", + "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + } } }, "react-dnd-html5-backend": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-2.6.0.tgz", - "integrity": "sha1-WQzRzKeEQbsnTt1XH+9MCxbdz44=", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-7.0.2.tgz", + "integrity": "sha512-BPhmHeQjvhPXRykHvFLbM+TJFrrarcuf/mIArNEmXzZuNhLfbOnHtMSzR8lPwodABcDAYj7hEF7vTABXX298vA==", "requires": { - "lodash": "^4.2.0" + "dnd-core": "^7.0.2", + "lodash": "^4.17.11" + }, + "dependencies": { + "dnd-core": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-7.0.2.tgz", + "integrity": "sha512-InwRBi6zTndtE3+3nTYpLJkYMEr7utSR7OziAoSFhtQsbUfJE1KeqxM+ZFRIMKn6ehxUTAC+QU6QC7IG9u86Mg==", + "requires": { + "asap": "^2.0.6", + "invariant": "^2.2.4", + "lodash": "^4.17.11", + "redux": "^4.0.1" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" + }, + "redux": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.1.tgz", + "integrity": "sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==", + "requires": { + "loose-envify": "^1.4.0", + "symbol-observable": "^1.2.0" + } + } } }, "react-dom": { @@ -13575,6 +16406,20 @@ "shallowequal": "^1.0.1" } }, + "react-sortable-tree": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/react-sortable-tree/-/react-sortable-tree-2.6.0.tgz", + "integrity": "sha512-XRAm8mK48xylJtLtk/ENPdV0+cAvx+vCDRxWJq9Nhc4vI+dKx4flEGJc1cFmvt5OvXaaX/KWQFB59/4gYkEPXw==", + "requires": { + "frontend-collective-react-dnd-scrollzone": "^1.0.1", + "lodash.isequal": "^4.5.0", + "prop-types": "^15.6.1", + "react-dnd": "^7.0.1", + "react-dnd-html5-backend": "^7.0.1", + "react-lifecycles-compat": "^3.0.4", + "react-virtualized": "^9.19.1" + } + }, "react-stripe-elements": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/react-stripe-elements/-/react-stripe-elements-2.0.1.tgz", @@ -13668,6 +16513,19 @@ "react-lifecycles-compat": "^3.0.4" } }, + "react-virtualized": { + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/react-virtualized/-/react-virtualized-9.21.0.tgz", + "integrity": "sha512-duKD2HvO33mqld4EtQKm9H9H0p+xce1c++2D5xn59Ma7P8VT7CprfAe5hwjd1OGkyhqzOZiTMlTal7LxjH5yBQ==", + "requires": { + "babel-runtime": "^6.26.0", + "classnames": "^2.2.3", + "dom-helpers": "^2.4.0 || ^3.0.0", + "loose-envify": "^1.3.0", + "prop-types": "^15.6.0", + "react-lifecycles-compat": "^3.0.4" + } + }, "react-with-direction": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-with-direction/-/react-with-direction-1.3.0.tgz", @@ -13803,17 +16661,6 @@ "minimatch": "3.0.4" } }, - "redux": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", - "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", - "requires": { - "lodash": "^4.2.1", - "lodash-es": "^4.2.1", - "loose-envify": "^1.1.0", - "symbol-observable": "^1.0.3" - } - }, "reflect.ownkeys": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz", diff --git a/package.json b/package.json index c99f17d8884..3bf4122ff5e 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,6 @@ "cuid": "^2.1.0", "deep-diff": "^0.3.8", "deep-equal": "^1.0.1", - "dnd-core": "^2.5.4", "escape-string-regexp": "^1.0.5", "express": "4.16.2", "faker": "^4.1.0", @@ -88,6 +87,7 @@ "i18next-sprintf-postprocessor": "^0.2.2", "immutability-helper": "^2.6.5", "immutable": "^3.8.2", + "install": "^0.12.2", "jquery-i18next": "^1.2.1", "later": "^1.2.0", "libphonenumber-js": "1.4.2", @@ -95,6 +95,8 @@ "lodash.pick": "^4.4.0", "match-sorter": "^2.2.0", "meteor-node-stubs": "0.4.1", + "mobx": "^5.8.0", + "mobx-react": "^5.4.3", "moment": "^2.20.1", "moment-timezone": "^0.5.14", "mongodb": "^3.0.5", @@ -103,6 +105,7 @@ "node-geocoder": "^3.22.0", "nodemailer-wellknown": "^0.2.3", "nouislider-algolia-fork": "^10.0.0", + "npm": "^6.5.0", "object-hash": "^1.3.0", "path-to-regexp": "^2.1.0", "prerender-node": "3.1.1", @@ -119,8 +122,8 @@ "react-container-query": "^0.11.0", "react-copy-to-clipboard": "^5.0.1", "react-dates": "17.1.0", - "react-dnd": "^2.5.4", - "react-dnd-html5-backend": "^2.5.4", + "react-dnd": "^7.0.1", + "react-dnd-html5-backend": "^7.0.1", "react-dom": "16.4.2", "react-dropzone": "^4.2.7", "react-helmet": "^5.2.0", @@ -133,6 +136,7 @@ "react-router-dom": "^4.2.2", "react-s-alert": "^1.4.1", "react-select": "^1.2.1", + "react-sortable-tree": "^2.6.0", "react-stripe-elements": "^2.0.1", "react-table": "^6.7.6", "react-taco-table": "^0.5.1", From 4b4c4d72ffd7d6642583b0859166ea9d41bf1954 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 28 Jan 2019 15:23:40 -0800 Subject: [PATCH 03/62] feat: add navigation ui to operator 2.0 experience Signed-off-by: Mike Murray --- imports/plugins/core/navigation/client/index.js | 16 ++++++++++++++++ .../plugins/core/navigation/server/i18n/en.json | 5 +++++ 2 files changed, 21 insertions(+) diff --git a/imports/plugins/core/navigation/client/index.js b/imports/plugins/core/navigation/client/index.js index 0f1e3e531b5..952556a8987 100644 --- a/imports/plugins/core/navigation/client/index.js +++ b/imports/plugins/core/navigation/client/index.js @@ -1,3 +1,19 @@ + +import React from "react"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { faLink } from "@fortawesome/free-solid-svg-icons"; +import { registerOperatorRoute } from "/imports/client/ui"; + import "./containers"; import "./templates"; import "./styles.less"; + +registerOperatorRoute({ + isNavigationLink: true, + isSetting: true, + mainComponent: "navigationDashboard", + path: "/navigation", + // eslint-disable-next-line react/display-name + SidebarIconComponent: (props) => , + sidebarI18nLabel: "admin.navigation.navigation" +}); diff --git a/imports/plugins/core/navigation/server/i18n/en.json b/imports/plugins/core/navigation/server/i18n/en.json index 820d41a861d..3a5a28240dc 100644 --- a/imports/plugins/core/navigation/server/i18n/en.json +++ b/imports/plugins/core/navigation/server/i18n/en.json @@ -4,6 +4,11 @@ "ns": "navigation", "translation": { "navigation": { + "admin": { + "navigation": { + "navigation": "Navigation" + } + }, "navigationItem": { "displayName": "Display Name", "url": "URL", From 6e0823522e724f4d4091b763673a4529e006fd06 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 28 Jan 2019 15:28:58 -0800 Subject: [PATCH 04/62] fix: import template directly Signed-off-by: Mike Murray --- imports/plugins/core/navigation/client/index.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imports/plugins/core/navigation/client/index.js b/imports/plugins/core/navigation/client/index.js index 952556a8987..22ba2f47d49 100644 --- a/imports/plugins/core/navigation/client/index.js +++ b/imports/plugins/core/navigation/client/index.js @@ -3,6 +3,7 @@ import React from "react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faLink } from "@fortawesome/free-solid-svg-icons"; import { registerOperatorRoute } from "/imports/client/ui"; +import NavigationDashboard from "./containers/navigationDashboardContainer"; import "./containers"; import "./templates"; @@ -11,7 +12,7 @@ import "./styles.less"; registerOperatorRoute({ isNavigationLink: true, isSetting: true, - mainComponent: "navigationDashboard", + mainComponent: NavigationDashboard, path: "/navigation", // eslint-disable-next-line react/display-name SidebarIconComponent: (props) => , From 1da26034149fefd3c7865ef6986cf8fd0dd6e2d5 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:35:11 -0800 Subject: [PATCH 05/62] feat: update theme with overrides for components and shape Signed-off-by: Mike Murray --- .../core/router/client/theme/muiTheme.js | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 75d8299da9c..8653ffd8485 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -1,8 +1,11 @@ import { defaultComponentTheme } from "@reactioncommerce/components"; import createMuiTheme from "@material-ui/core/styles/createMuiTheme"; +import createBreakpoints from "@material-ui/core/styles/createBreakpoints"; import colors from "./colors"; const { rui_typography: typography } = defaultComponentTheme; +const breakpoints = createBreakpoints({}); +const toolbarHeight = 80; export const rawMuiTheme = { palette: { @@ -51,6 +54,20 @@ export const rawMuiTheme = { "0px 13px 26px 0 rgba(0,0,0,0.05);", "0px 13px 26px 0 rgba(0,0,0,0.05);" ], + shape: { + borderRadius: 2 + }, + mixins: { + toolbar: { + minHeight: toolbarHeight, + [`${breakpoints.up("xs")} and (orientation: landscape)`]: { + minHeight: toolbarHeight + }, + [breakpoints.up('sm')]: { + minHeight: toolbarHeight + } + } + }, // Override default props props: { MuiAppBar: { @@ -60,10 +77,18 @@ export const rawMuiTheme = { // Override defined theme properties overrides: { MuiAppBar: { + root: { + height: toolbarHeight + }, colorDefault: { backgroundColor: colors.white } }, + MuiButton: { + root: { + textTransform: "initial" + } + }, MuiCard: { root: { border: `1px solid ${colors.black10}` From b5c5ed15b4eafa4af9e474c868880ad91cf28204 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:35:46 -0800 Subject: [PATCH 06/62] feat: allow for rendering main content without a wrapper component Signed-off-by: Mike Murray --- imports/client/ui/layouts/Dashboard.js | 42 ++++++++++++++++++-------- 1 file changed, 29 insertions(+), 13 deletions(-) diff --git a/imports/client/ui/layouts/Dashboard.js b/imports/client/ui/layouts/Dashboard.js index 0a8c44612e6..ba45c6368f4 100644 --- a/imports/client/ui/layouts/Dashboard.js +++ b/imports/client/ui/layouts/Dashboard.js @@ -120,6 +120,9 @@ class Dashboard extends Component { render() { const { components: { IconHamburger } } = this.props; const { isSidebarOpen } = this.state; + const uiState = { + isLeftDrawerOpen: isSidebarOpen + }; return ( @@ -154,19 +157,32 @@ class Dashboard extends Component { />
- - - { - operatorRoutes.map((route) => ( - - )) - } - - + + + { + operatorRoutes.map((route) => ( + { + // If the layout component is explicitly null + if (route.layoutComponent === null) { + return ; + } + + const LayoutComponent = route.layoutComponent || MainContent; + + return ( + + + + ); + }} + /> + )) + } + +
); From 0418cca8c7761515076e7d7bb64bbf9a1e5effe4 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:36:16 -0800 Subject: [PATCH 07/62] feat: disable layout component for navigation ui Signed-off-by: Mike Murray --- imports/plugins/core/navigation/client/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/imports/plugins/core/navigation/client/index.js b/imports/plugins/core/navigation/client/index.js index 22ba2f47d49..1d6cd05238a 100644 --- a/imports/plugins/core/navigation/client/index.js +++ b/imports/plugins/core/navigation/client/index.js @@ -12,6 +12,7 @@ import "./styles.less"; registerOperatorRoute({ isNavigationLink: true, isSetting: true, + layoutComponent: null, mainComponent: NavigationDashboard, path: "/navigation", // eslint-disable-next-line react/display-name From 88237107c8637a55674c58daf863051939c1a36d Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:37:04 -0800 Subject: [PATCH 08/62] fix: update to better match designs Signed-off-by: Mike Murray --- .../v1/NavigationItemTabs.js | 36 +++++++++---------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js index 33d0edf0850..73dd6d2de74 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js @@ -1,28 +1,25 @@ import React from "react"; import PropTypes from "prop-types"; import { withStyles } from "@material-ui/core/styles"; -import Paper from "@material-ui/core/Paper"; +import Divider from "@material-ui/core/Divider"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; import NavigationItemsList from "../../NavigationItemsList/v1"; import TagsList from "../../TagsList/v1"; -const styles = (theme) => ({ +const styles = () => ({ root: { - flexGrow: 1, - backgroundColor: theme.palette.background.paper + flexGrow: 1 }, tabRoot: { - textTransform: "initial", - fontSize: "14px", height: "60px", - marginTop: "22px", - fontFamily: "Source Sans Pro, Helvetica Neue, Helvetica, sans-serif" + marginTop: "22px" } }); class NavigationItemTabs extends React.Component { static propTypes = { + classes: PropTypes.object, navigationItems: PropTypes.array, onClickAddNavigationItem: PropTypes.func, onClickUpdateNavigationItem: PropTypes.func, @@ -69,18 +66,17 @@ class NavigationItemTabs extends React.Component { return (
- - - - - - + + + + + {this.renderNavigationItemsTab()}
); From 8f4ba924a5f8117d5f05e296a9b918e6ee082aab Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:38:10 -0800 Subject: [PATCH 09/62] feat: update base font size Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 1 + 1 file changed, 1 insertion(+) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 8653ffd8485..42214b774e4 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -21,6 +21,7 @@ export const rawMuiTheme = { } }, typography: { + fontSize: 16, fontFamily: typography.bodyText.fontFamily, fontWeightLight: 400, fontWeightRegular: 400, From c84381ada8da271a9690a7be50c287f8b724e5c3 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:38:24 -0800 Subject: [PATCH 10/62] fix: lint issue Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 42214b774e4..70027f3fcd0 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -64,7 +64,7 @@ export const rawMuiTheme = { [`${breakpoints.up("xs")} and (orientation: landscape)`]: { minHeight: toolbarHeight }, - [breakpoints.up('sm')]: { + [breakpoints.up("sm")]: { minHeight: toolbarHeight } } From 1d9bebc1d20d3440aee3e6f03a43f94321c6f2de Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 29 Jan 2019 13:39:07 -0800 Subject: [PATCH 11/62] feat: add toolbar Signed-off-by: Mike Murray --- .../v1/NavigationDashboard.js | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js index 19614890b4c..05f1cb04db3 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js @@ -1,7 +1,12 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; +import classnames from "classnames"; +import AppBar from "@material-ui/core/AppBar"; import Grid from "@material-ui/core/Grid"; import Modal from "@material-ui/core/Modal"; +import Button from "@material-ui/core/Button"; +import Toolbar from "@material-ui/core/Toolbar"; +import Typography from "@material-ui/core/Typography"; import { withStyles } from "@material-ui/core/styles"; import HTML5Backend from "react-dnd-html5-backend"; import { DragDropContext } from "react-dnd"; @@ -10,6 +15,12 @@ import NavigationTreeContainer from "../../NavigationTreeContainer/v1"; import NavigationItemTabs from "../../NavigationItemTabs/v1"; const styles = (theme) => ({ + toolbarButton: { + marginLeft: theme.spacing.unit + }, + leftSidebarOpen: { + paddingLeft: 280 + (theme.spacing.unit * 2) + }, paper: { position: "absolute", width: theme.spacing.unit * 80, @@ -19,6 +30,9 @@ const styles = (theme) => ({ top: "50%", left: "50%", transform: "translate(-50%, -50%)" + }, + title: { + flex: 1 } }); @@ -31,6 +45,9 @@ class NavigationDashboard extends Component { onDragHover: PropTypes.func, onToggleChildrenVisibility: PropTypes.func, tags: PropTypes.array, + uiState: PropTypes.shape({ + isLeftDrawerOpen: PropTypes.bool + }), updateNavigationItem: PropTypes.func } @@ -76,6 +93,7 @@ class NavigationDashboard extends Component { onDragHover, onToggleChildrenVisibility, tags, + uiState, updateNavigationItem } = this.props; @@ -86,8 +104,22 @@ class NavigationDashboard extends Component { overNavigationItemId } = this.state; + const toolbarClassName = classnames({ + [classes.leftSidebarOpen]: uiState.isLeftDrawerOpen + }); + return (
+ + + + Main Navigation + + + + + + Date: Tue, 29 Jan 2019 13:39:20 -0800 Subject: [PATCH 12/62] fix: remove toolbar Signed-off-by: Mike Murray --- .../v1/NavigationTreeContainer.js | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js index 44ac80d53fa..85d5cdb6b25 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js @@ -63,21 +63,6 @@ class NavigationTreeContainer extends Component { render() { return ( - - - - Main Nav - - - - - - - - - From 259c09feae597ff3cdb41f4df56751dcd0e6ce0f Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 10:51:53 -0800 Subject: [PATCH 13/62] chore: update lockfile Signed-off-by: Mike Murray --- package-lock.json | 24 ++++++------------------ 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/package-lock.json b/package-lock.json index 10b7ab054d0..c49b2c4d320 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6851,15 +6851,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6876,22 +6874,19 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -7022,8 +7017,7 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -7037,7 +7031,6 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -7054,7 +7047,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -7070,7 +7062,6 @@ "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -7179,8 +7170,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -7194,7 +7184,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -7332,7 +7321,6 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", From 761aab8b64f74b590728d4460240b5126baa212e Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 11:05:25 -0800 Subject: [PATCH 14/62] feat: use react-sortable-tree Signed-off-by: Mike Murray --- .../v1/NavigationDashboard.js | 6 ++++ .../v1/NavigationTreeContainer.js | 31 +++++++++--------- .../client/hocs/withNavigationUIStore.js | 32 +++++++++++++++++-- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js index 05f1cb04db3..702670d7427 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js @@ -43,7 +43,9 @@ class NavigationDashboard extends Component { navigationItems: PropTypes.array, navigationTreeRows: PropTypes.array, onDragHover: PropTypes.func, + onSetSortableNavigationTree: PropTypes.func, onToggleChildrenVisibility: PropTypes.func, + sortableNavigationTree: PropTypes.arrayOf(PropTypes.object), tags: PropTypes.array, uiState: PropTypes.shape({ isLeftDrawerOpen: PropTypes.bool @@ -91,7 +93,9 @@ class NavigationDashboard extends Component { navigationItems, navigationTreeRows, onDragHover, + onSetSortableNavigationTree, onToggleChildrenVisibility, + sortableNavigationTree, tags, uiState, updateNavigationItem @@ -132,6 +136,8 @@ class NavigationDashboard extends Component { @@ -69,6 +60,14 @@ class NavigationTreeContainer extends Component {

Drag and drop pages and tags from the left column into the navigation structure.

+ +
+ +
+ {this.renderRows()} diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js index 5f0fde2ff2f..41d87841af2 100644 --- a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -680,20 +680,46 @@ export default (Component) => ( } handleSetNavigationTree = (navigationTree) => { - this.setState({ navigationTree }); + const sortableNavigationTree = this.navigationTreeToSortable(navigationTree); + this.setState({ navigationTree, sortableNavigationTree }); + } + + handleSetSortableNavigationTree = (sortableNavigationTree) => { + this.setState({ sortableNavigationTree }); + } + + getNavigationItemTitle(navigationItem, language = "en") { + return navigationItem.draftData.content.find((item) => item.language === language) || { value: "" }; + } + + navigationTreeToSortable(navigationTree) { + return navigationTree.map((node) => { + let newNode = {}; + newNode.id = node.navigationItem._id; + newNode.title = this.getNavigationItemTitle(node.navigationItem).value; + + if (Array.isArray(node.items) && node.items.length) { + newNode.children = this.navigationTreeToSortable(node.items); + } + + return newNode; + }); } render() { - const { navigationItems, navigationTree, dragging, draggingNavigationTree, tags } = this.state; + const { navigationItems, navigationTree, dragging, draggingNavigationTree, sortableNavigationTree, tags } = this.state; let currentNavigationTree = navigationTree; if (dragging) { currentNavigationTree = draggingNavigationTree; } - console.log(this.state); + // console.log(this.state); const navigationTreeRows = this.getFlatDataFromNavigationTree(currentNavigationTree, true); + return ( Date: Thu, 31 Jan 2019 22:28:56 -0800 Subject: [PATCH 15/62] feat: update mui theme font Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 70027f3fcd0..afc1f598a23 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -26,7 +26,10 @@ export const rawMuiTheme = { fontWeightLight: 400, fontWeightRegular: 400, fontWeightMedium: 500, - useNextVariants: true + useNextVariants: true, + h6: { + fontSize: 18 + } }, shadows: [ "none", From ac93c3ecf9be491cdb7939e67ecd86168b4fce07 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:29:28 -0800 Subject: [PATCH 16/62] feat: update mui theme drawer overrides Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index afc1f598a23..34888aebb4a 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -110,6 +110,14 @@ export const rawMuiTheme = { color: colors.coolGrey100 } } + }, + MuiDrawer: { + paperAnchorLeft: { + borderRight: "none" + }, + paperAnchorDockedLeft: { + borderRight: "none" + } } } }; From 907cda8816fbc9067bef69280f282cc89879af1d Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:29:54 -0800 Subject: [PATCH 17/62] feat: update top padding for main container in default theme Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/defaultTheme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/core/router/client/theme/defaultTheme.js b/imports/plugins/core/router/client/theme/defaultTheme.js index 64b878fa570..bb6e8fe9cd9 100644 --- a/imports/plugins/core/router/client/theme/defaultTheme.js +++ b/imports/plugins/core/router/client/theme/defaultTheme.js @@ -31,7 +31,7 @@ const rui_components = { pageContentPaddingBottom: "24px", pageContentPaddingLeft: "24px", pageContentPaddingRight: "24px", - pageContentPaddingTop: "24px", + pageContentPaddingTop: "80px", pageHeaderBackgroundColor: colors.black02 }, ProfileImage: { From b2e7b85a1a43602c23bcaa4220f39b1dfd7e27b4 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:30:35 -0800 Subject: [PATCH 18/62] feat: add update navigation tree hoc Signed-off-by: Mike Murray --- .../client/hocs/withUpdateNavigationTree.js | 89 +++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js diff --git a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js new file mode 100644 index 00000000000..b8890e5e3d1 --- /dev/null +++ b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js @@ -0,0 +1,89 @@ +import React from "react"; +import PropTypes from "prop-types"; +import gql from "graphql-tag"; +import { Mutation } from "react-apollo"; +import { navigationItemFragment } from "./fragments"; + +const updateNavigationTreeMutation = gql` + mutation updateNavigationTreeMutation($input: UpdateNavigationTreeInput!) { + updateNavigationTree(input: $input) { + navigationTree { + + name + draftItems { + expanded + navigationItem { + ...NavigationItemCommon + } + items { + expanded + navigationItem { + ...NavigationItemCommon + } + items { + navigationItem { + ...NavigationItemCommon + } + } + } + } + + } + } + } + ${navigationItemFragment.navigationItem} +`; + +export default (Component) => ( + class WithUpdateNavigationTree extends React.Component { + static propTypes = { + onUpdateNavigationTree: PropTypes.func + } + + handleUpdateNavigationTree = (data) => { + const { updateNavigationTree: { navigationTree } } = data; + this.props.onUpdateNavigationTree(navigationTree); + } + + sortableNavigationTreeToDraftItems(sortableNavigationTree) { + return sortableNavigationTree.map((node) => { + const newNode = {}; + newNode.navigationItemId = node.id; + newNode.expanded = node.expanded; + + if (Array.isArray(node.children) && node.children.length) { + newNode.items = this.sortableNavigationTreeToDraftItems(node.children); + } + + return newNode; + }); + } + + render() { + return ( + + {(updateNavigationTree) => ( + { + const { defaultNavigationTreeId, sortableNavigationTree } = this.props; + const input = { + _id: defaultNavigationTreeId, + navigationTree: { + draftItems: this.sortableNavigationTreeToDraftItems(sortableNavigationTree) + } + }; + + updateNavigationTree({ + variables: { + input + } + }); + }} + /> + )} + + ); + } + } +); From 98e143ad5bfbdc1484ce923686f0c8670c6eae98 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:33:30 -0800 Subject: [PATCH 19/62] feat: add update handler Signed-off-by: Mike Murray --- .../core/navigation/client/hocs/withNavigationUIStore.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js index 41d87841af2..77eaae4e982 100644 --- a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -694,7 +694,7 @@ export default (Component) => ( navigationTreeToSortable(navigationTree) { return navigationTree.map((node) => { - let newNode = {}; + const newNode = {}; newNode.id = node.navigationItem._id; newNode.title = this.getNavigationItemTitle(node.navigationItem).value; @@ -706,6 +706,11 @@ export default (Component) => ( }); } + handleUpdateNavigationTree = (navigationTree) => { + const { draftItems } = navigationTree; + this.handleSetNavigationTree(draftItems); + } + render() { const { navigationItems, navigationTree, dragging, draggingNavigationTree, sortableNavigationTree, tags } = this.state; let currentNavigationTree = navigationTree; @@ -718,6 +723,7 @@ export default (Component) => ( return ( Date: Thu, 31 Jan 2019 22:34:05 -0800 Subject: [PATCH 20/62] feat: compose withUpdateNavigationTree hoc Signed-off-by: Mike Murray --- .../client/containers/navigationDashboardContainer.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js b/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js index 49774c1f7b9..60da79c3ea2 100644 --- a/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js +++ b/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js @@ -3,6 +3,7 @@ import { registerComponent } from "@reactioncommerce/reaction-components"; import NavigationDashboard from "../components/NavigationDashboard/v1"; import withCreateNavigationItem from "../hocs/withCreateNavigationItem"; import withUpdateNavigationItem from "../hocs/withUpdateNavigationItem"; +import withUpdateNavigationTree from "../hocs/withUpdateNavigationTree"; import withDefaultNavigationTree from "../hocs/withDefaultNavigationTree"; import withNavigationItems from "../hocs/withNavigationItems"; import withDefaultNavigationTreeId from "../hocs/withDefaultNavigationTreeId"; @@ -13,19 +14,21 @@ import withTags from "../hocs/withTags"; registerComponent("NavigationDashboard", NavigationDashboard, [ withNavigationUIStore, withCreateNavigationItem, - withUpdateNavigationItem, withDefaultNavigationTreeId, withDefaultNavigationTree, withNavigationItems, + withUpdateNavigationItem, + withUpdateNavigationTree, withTags ]); export default compose( withNavigationUIStore, withCreateNavigationItem, - withUpdateNavigationItem, withDefaultNavigationTreeId, withDefaultNavigationTree, withNavigationItems, + withUpdateNavigationItem, + withUpdateNavigationTree, withTags )(NavigationDashboard); From dd5ddafea85309e25fe4ba4bc9655f825b4d77c9 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:35:03 -0800 Subject: [PATCH 21/62] feat: remove old sortable tree Signed-off-by: Mike Murray --- .../NavigationTreeContainer/v1/NavigationTreeContainer.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js index 89c6261bd90..a4f27e11232 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js @@ -67,10 +67,6 @@ class NavigationTreeContainer extends Component { onChange={onSetSortableNavigationTree} />
- - - {this.renderRows()} - ); From 6210cc42b22f3656019bf8c7f3cc6fa9c167d085 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:35:14 -0800 Subject: [PATCH 22/62] feat: set dnd type Signed-off-by: Mike Murray --- .../NavigationTreeContainer/v1/NavigationTreeContainer.js | 1 + 1 file changed, 1 insertion(+) diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js index a4f27e11232..523b70164fa 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js @@ -65,6 +65,7 @@ class NavigationTreeContainer extends Component {
From a135f1ed2b59cfbae1e60ab036c229a26c38d583 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:37:25 -0800 Subject: [PATCH 23/62] fix: remove lifecycle function causing views to crash on drag Signed-off-by: Mike Murray --- .../v1/NavigationItemCard.js | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js index 4b4a3e72bbe..7e68bcfc705 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js @@ -113,25 +113,6 @@ class NavigationItemCard extends Component { row: PropTypes.object }; - componentDidUpdate(prevProps) { - const { isDragging, isOver, navigationItemId, onSetDraggingNavigationItemId, onSetOverNavigationItemId } = this.props; - if (isDragging !== prevProps.isDragging) { - let draggingNavigationItemId = ""; - if (isDragging) { - draggingNavigationItemId = navigationItemId; - } - onSetDraggingNavigationItemId(draggingNavigationItemId); - } - - if (isOver !== prevProps.isOver) { - let overNavigationItemId = ""; - if (isOver) { - overNavigationItemId = navigationItemId; - } - onSetOverNavigationItemId(overNavigationItemId); - } - } - get type() { const { row } = this.props; let type; From 854c3ddd5971d10466f7179a44909fde153c66aa Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:37:48 -0800 Subject: [PATCH 24/62] feat: setup drag source for react sortable tree Signed-off-by: Mike Murray --- .../v1/NavigationItemCard.js | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js index 7e68bcfc705..d3fdc810fed 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js @@ -90,7 +90,16 @@ const ActionIconSpan = styled.span` const navigationItemSource = { beginDrag(props) { - return props; + const { node: { navigationItem } } = props.row; + const { _id, draftData } = navigationItem; + const { value } = draftData.content.find((content) => content.language === "en"); + + return { + node: { + id: _id, + title: value + } + }; } }; @@ -224,9 +233,13 @@ class NavigationItemCard extends Component { { - connectDragSource(
- {iconEllipsisV} -
) + connectDragSource(( +
+ {iconEllipsisV} +
+ ), { + dropEffect: "copy" + }) } {this.renderChildrenToggleButton()} From 5f9e2e3cbd9ee73193e02c5ba364eae8f64b67b5 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:38:51 -0800 Subject: [PATCH 25/62] feat: connect save button Signed-off-by: Mike Murray --- .../components/NavigationDashboard/v1/NavigationDashboard.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js index 702670d7427..c8d8435b780 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js @@ -98,7 +98,8 @@ class NavigationDashboard extends Component { sortableNavigationTree, tags, uiState, - updateNavigationItem + updateNavigationItem, + updateNavigationTree } = this.props; const { @@ -119,7 +120,7 @@ class NavigationDashboard extends Component { Main Navigation - + From ee8d0d04cef902499eda4997bd49db40407bd516 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 31 Jan 2019 22:40:18 -0800 Subject: [PATCH 26/62] fix: update main content layout Signed-off-by: Mike Murray --- imports/client/ui/layouts/Dashboard.js | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/imports/client/ui/layouts/Dashboard.js b/imports/client/ui/layouts/Dashboard.js index ba45c6368f4..5f73a336cf8 100644 --- a/imports/client/ui/layouts/Dashboard.js +++ b/imports/client/ui/layouts/Dashboard.js @@ -55,6 +55,10 @@ const MainContent = styled.div` margin: 0 auto; `; +const MainContentFullWidth = styled.div` + padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; +`; + // The reason we can't simply do `styledMUI(MUIAppBar)` here is because we're passing in isMobile and isSidebarOpen // props for the styled-components conditionals, but React does not recognize these as valid attributes // for DOM elements and prints warnings in the console. Someday there may be a better solution. @@ -79,14 +83,6 @@ const Grow = styled.div` flex-grow: 1; `; -const HamburgerIconButton = styledMUI(IconButton)` - color: ${applyTheme("Layout.burgerIconColor")}; - position: fixed; - left: ${applyTheme("Layout.burgerIconLeft")}; - top: ${applyTheme("Layout.burgerIconTop")}; - z-index: 2000; -`; - // This is an invisible element that is needed only to push the page content down below the `AppBar` const DrawerHeader = styled.div` min-height: 48px; @@ -106,7 +102,7 @@ class Dashboard extends Component { }; state = { - isSidebarOpen: null + isSidebarOpen: true }; handleDrawerClose = () => { @@ -118,7 +114,6 @@ class Dashboard extends Component { }; render() { - const { components: { IconHamburger } } = this.props; const { isSidebarOpen } = this.state; const uiState = { isLeftDrawerOpen: isSidebarOpen @@ -140,9 +135,6 @@ class Dashboard extends Component { return ( - - - @@ -151,7 +143,7 @@ class Dashboard extends Component { @@ -167,7 +159,11 @@ class Dashboard extends Component { render={(props) => { // If the layout component is explicitly null if (route.layoutComponent === null) { - return ; + return ( + + ; + + ); } const LayoutComponent = route.layoutComponent || MainContent; From 2bb9f4c8aac3a5a42cb882120d2ddc6900aebb2e Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 1 Feb 2019 07:28:39 -0800 Subject: [PATCH 27/62] refactor: flatten folder structure Signed-off-by: Mike Murray --- .../{NavigationDashboard/v1 => }/NavigationDashboard.js | 0 .../navigation/client/components/NavigationDashboard/v1/index.js | 1 - .../components/{NavigationItemCard/v1 => }/NavigationItemCard.js | 0 .../navigation/client/components/NavigationItemCard/v1/index.js | 1 - .../components/{NavigationItemForm => }/NavigationItemForm.js | 0 .../navigation/client/components/NavigationItemForm/index.js | 1 - .../v1/NavigationItemsList.js => NavigationItemList.js} | 0 .../components/{NavigationItemTabs/v1 => }/NavigationItemTabs.js | 0 .../navigation/client/components/NavigationItemTabs/v1/index.js | 1 - .../navigation/client/components/NavigationItemsList/v1/index.js | 1 - .../{NavigationTreeContainer/v1 => }/NavigationTreeContainer.js | 0 .../client/components/NavigationTreeContainer/v1/index.js | 1 - .../client/components/{TagsList/v1/TagsList.js => TagList.js} | 0 .../core/navigation/client/components/TagsList/v1/index.js | 1 - 14 files changed, 7 deletions(-) rename imports/plugins/core/navigation/client/components/{NavigationDashboard/v1 => }/NavigationDashboard.js (100%) delete mode 100644 imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js rename imports/plugins/core/navigation/client/components/{NavigationItemCard/v1 => }/NavigationItemCard.js (100%) delete mode 100644 imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js rename imports/plugins/core/navigation/client/components/{NavigationItemForm => }/NavigationItemForm.js (100%) delete mode 100644 imports/plugins/core/navigation/client/components/NavigationItemForm/index.js rename imports/plugins/core/navigation/client/components/{NavigationItemsList/v1/NavigationItemsList.js => NavigationItemList.js} (100%) rename imports/plugins/core/navigation/client/components/{NavigationItemTabs/v1 => }/NavigationItemTabs.js (100%) delete mode 100644 imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js delete mode 100644 imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js rename imports/plugins/core/navigation/client/components/{NavigationTreeContainer/v1 => }/NavigationTreeContainer.js (100%) delete mode 100644 imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js rename imports/plugins/core/navigation/client/components/{TagsList/v1/TagsList.js => TagList.js} (100%) delete mode 100644 imports/plugins/core/navigation/client/components/TagsList/v1/index.js diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js similarity index 100% rename from imports/plugins/core/navigation/client/components/NavigationDashboard/v1/NavigationDashboard.js rename to imports/plugins/core/navigation/client/components/NavigationDashboard.js diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js deleted file mode 100644 index 3f4160b9351..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard/v1/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./NavigationDashboard"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard.js similarity index 100% rename from imports/plugins/core/navigation/client/components/NavigationItemCard/v1/NavigationItemCard.js rename to imports/plugins/core/navigation/client/components/NavigationItemCard.js diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js deleted file mode 100644 index 0dbdc9153ce..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard/v1/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./NavigationItemCard"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm/NavigationItemForm.js b/imports/plugins/core/navigation/client/components/NavigationItemForm.js similarity index 100% rename from imports/plugins/core/navigation/client/components/NavigationItemForm/NavigationItemForm.js rename to imports/plugins/core/navigation/client/components/NavigationItemForm.js diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm/index.js b/imports/plugins/core/navigation/client/components/NavigationItemForm/index.js deleted file mode 100644 index ba0fff65876..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationItemForm/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./NavigationItemForm"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/NavigationItemsList.js b/imports/plugins/core/navigation/client/components/NavigationItemList.js similarity index 100% rename from imports/plugins/core/navigation/client/components/NavigationItemsList/v1/NavigationItemsList.js rename to imports/plugins/core/navigation/client/components/NavigationItemList.js diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs.js similarity index 100% rename from imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/NavigationItemTabs.js rename to imports/plugins/core/navigation/client/components/NavigationItemTabs.js diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js deleted file mode 100644 index 3f2352499db..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationItemTabs/v1/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./NavigationItemTabs"; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js deleted file mode 100644 index c08551923bd..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationItemsList/v1/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./NavigationItemsList"; diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js similarity index 100% rename from imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/NavigationTreeContainer.js rename to imports/plugins/core/navigation/client/components/NavigationTreeContainer.js diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js deleted file mode 100644 index 54d85800997..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer/v1/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./NavigationTreeContainer"; diff --git a/imports/plugins/core/navigation/client/components/TagsList/v1/TagsList.js b/imports/plugins/core/navigation/client/components/TagList.js similarity index 100% rename from imports/plugins/core/navigation/client/components/TagsList/v1/TagsList.js rename to imports/plugins/core/navigation/client/components/TagList.js diff --git a/imports/plugins/core/navigation/client/components/TagsList/v1/index.js b/imports/plugins/core/navigation/client/components/TagsList/v1/index.js deleted file mode 100644 index c010bfe2cfd..00000000000 --- a/imports/plugins/core/navigation/client/components/TagsList/v1/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from "./TagsList"; From cddfc94224a33ad33288152397936b03f3d2e2b3 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Fri, 1 Feb 2019 07:45:53 -0800 Subject: [PATCH 28/62] refactor: update imports Signed-off-by: Mike Murray --- .../client/components/NavigationDashboard.js | 6 +++--- .../client/components/NavigationItemCard.js | 14 +++++++------- .../client/components/NavigationItemForm.js | 2 +- .../client/components/NavigationItemList.js | 2 +- .../client/components/NavigationItemTabs.js | 8 ++++---- .../client/components/NavigationTreeContainer.js | 2 +- .../client/components/NavigationTreeNode.js | 2 +- .../core/navigation/client/components/TagList.js | 2 +- .../containers/navigationDashboardContainer.js | 2 +- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js index c8d8435b780..90e4705d937 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard.js @@ -10,9 +10,9 @@ import Typography from "@material-ui/core/Typography"; import { withStyles } from "@material-ui/core/styles"; import HTML5Backend from "react-dnd-html5-backend"; import { DragDropContext } from "react-dnd"; -import NavigationItemForm from "../../NavigationItemForm"; -import NavigationTreeContainer from "../../NavigationTreeContainer/v1"; -import NavigationItemTabs from "../../NavigationItemTabs/v1"; +import NavigationItemForm from "./NavigationItemForm"; +import NavigationTreeContainer from "./NavigationTreeContainer"; +import NavigationItemTabs from "./NavigationItemTabs"; const styles = (theme) => ({ toolbarButton: { diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard.js index d3fdc810fed..cae2e1571ef 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard.js @@ -5,13 +5,13 @@ import { DragSource, DropTarget } from "react-dnd"; import styled from "styled-components"; import Paper from "@material-ui/core/Paper"; import Link from "@reactioncommerce/components/Link/v1"; -import iconEllipsisV from "../../../svg/iconEllipsisV"; -import iconChevronDown from "../../../svg/iconChevronDown"; -import iconChevronRight from "../../../svg/iconChevronRight"; -import iconPencil from "../../../svg/iconPencil"; -import iconTimes from "../../../svg/iconTimes"; -import iconFile from "../../../svg/iconFile"; -import iconTag from "../../../svg/iconTag"; +import iconEllipsisV from "../svg/iconEllipsisV"; +import iconChevronDown from "../svg/iconChevronDown"; +import iconChevronRight from "../svg/iconChevronRight"; +import iconPencil from "../svg/iconPencil"; +import iconTimes from "../svg/iconTimes"; +import iconFile from "../svg/iconFile"; +import iconTag from "../svg/iconTag"; const CardContainer = styled.div` margin-bottom: 5px; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm.js b/imports/plugins/core/navigation/client/components/NavigationItemForm.js index a241381f7e6..2a409f8efd4 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemForm.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemForm.js @@ -12,7 +12,7 @@ import ErrorsBlock from "@reactioncommerce/components/ErrorsBlock/v1"; import Field from "@reactioncommerce/components/Field/v1"; import TextInput from "@reactioncommerce/components/TextInput/v1"; import { i18next } from "/client/api"; -import iconTimes from "../../svg/iconTimes"; +import iconTimes from "../svg/iconTimes"; const Wrapper = styled.div` diff --git a/imports/plugins/core/navigation/client/components/NavigationItemList.js b/imports/plugins/core/navigation/client/components/NavigationItemList.js index 87f2777d53f..befffd2a600 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemList.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemList.js @@ -2,7 +2,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; import Link from "@reactioncommerce/components/Link/v1"; -import NavigationItemCard from "../../NavigationItemCard/v1"; +import NavigationItemCard from "./NavigationItemCard"; const Wrapper = styled.div` padding: 20px; diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs.js index 73dd6d2de74..f8f1eebffd4 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemTabs.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemTabs.js @@ -4,8 +4,8 @@ import { withStyles } from "@material-ui/core/styles"; import Divider from "@material-ui/core/Divider"; import Tabs from "@material-ui/core/Tabs"; import Tab from "@material-ui/core/Tab"; -import NavigationItemsList from "../../NavigationItemsList/v1"; -import TagsList from "../../TagsList/v1"; +import NavigationItemList from "./NavigationItemList"; +import TagList from "./TagList"; const styles = () => ({ root: { @@ -47,10 +47,10 @@ class NavigationItemTabs extends React.Component { updateNavigationItem } = this.props; if (value === 0) { - return ; + return ; } return ( - Date: Mon, 4 Feb 2019 09:14:51 -0800 Subject: [PATCH 29/62] feat: update dashboard layout Signed-off-by: Mike Murray --- imports/client/ui/layouts/Dashboard.js | 129 ++++++++++++------------- 1 file changed, 60 insertions(+), 69 deletions(-) diff --git a/imports/client/ui/layouts/Dashboard.js b/imports/client/ui/layouts/Dashboard.js index 5f73a336cf8..d48957ca186 100644 --- a/imports/client/ui/layouts/Dashboard.js +++ b/imports/client/ui/layouts/Dashboard.js @@ -1,11 +1,12 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; -import styled, { css, injectGlobal } from "styled-components"; -import styledMUI from "styled-components-mui"; +import { compose } from "recompose"; +import classNames from "classnames"; +import styled, { injectGlobal } from "styled-components"; import { ContainerQuery } from "react-container-query"; -import MUIAppBar from "@material-ui/core/AppBar"; -import MUIToolbar from "@material-ui/core/Toolbar"; -import IconButton from "@material-ui/core/IconButton"; +import withStyles from "@material-ui/core/styles/withStyles"; +import AppBar from "@material-ui/core/AppBar"; +import Toolbar from "@material-ui/core/Toolbar"; import { applyTheme, CustomPropTypes } from "@reactioncommerce/components/utils"; import { withComponents } from "@reactioncommerce/components-context"; import { Route, Switch } from "react-router"; @@ -44,6 +45,10 @@ const Main = styled(({ children, isMobile, isSidebarOpen, ...divProps }) => ( (props.isSidebarOpen && props.isMobile === false ? "280px" : 0)}; + padding-bottom: ${applyTheme("Layout.pageContentPaddingBottom")}; + padding-right: ${applyTheme("Layout.pageContentPaddingRight")}; + padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; + margin: 0 auto; `; const MainContent = styled.div` @@ -55,44 +60,27 @@ const MainContent = styled.div` margin: 0 auto; `; -const MainContentFullWidth = styled.div` - padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; -`; - -// The reason we can't simply do `styledMUI(MUIAppBar)` here is because we're passing in isMobile and isSidebarOpen -// props for the styled-components conditionals, but React does not recognize these as valid attributes -// for DOM elements and prints warnings in the console. Someday there may be a better solution. -// See https://github.com/styled-components/styled-components/issues/305 -const AppBar = styledMUI(({ children, isMobile, isSidebarOpen, ...restProps }) => ({children}))` - background-color: ${applyTheme("Layout.pageHeaderBackgroundColor")}; +const MainFullWidth = styled.div` + width: 100vw; + background-color: ${applyTheme("Layout.pageBackgroundColor")}; + flex-grow: 1; transition: ${(props) => (props.isSidebarOpen && props.isMobile !== true - ? "margin 225ms cubic-bezier(0.0, 0, 0.2, 1) 0ms,width 225ms cubic-bezier(0.0, 0, 0.2, 1) 0ms" - : "margin 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms,width 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms")}; - ${(props) => { - if (props.isSidebarOpen && props.isMobile !== true) { - return css` - margin-left: ${applyTheme("Sidebar.drawerWidth")}; - width: calc(100% - ${applyTheme("Sidebar.drawerWidth")});`; - } - return null; - }}; + ? "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms" + : "padding 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms")}; + padding-left: ${(props) => (props.isSidebarOpen && props.isMobile === false ? "280px" : 0)}; + padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; `; const Grow = styled.div` flex-grow: 1; `; -// This is an invisible element that is needed only to push the page content down below the `AppBar` -const DrawerHeader = styled.div` - min-height: 48px; - @media (min-width: 600px) { - min-height: 64px; +const styles = (theme) => ({ + leftSidebarOpen: { + paddingLeft: 280 + (theme.spacing.unit * 2) } - @media (min-width: 0px) and (orientation: landscape) { - min-height: 48px; - } -`; +}); class Dashboard extends Component { static propTypes = { @@ -114,11 +102,16 @@ class Dashboard extends Component { }; render() { + const { classes } = this.props; const { isSidebarOpen } = this.state; const uiState = { isLeftDrawerOpen: isSidebarOpen }; + const toolbarClassName = classNames({ + [classes.leftSidebarOpen]: uiState.isLeftDrawerOpen + }); + return ( {({ isMobile }) => { @@ -135,11 +128,11 @@ class Dashboard extends Component { return ( - - + + - + -
- - - - { - operatorRoutes.map((route) => ( - { - // If the layout component is explicitly null - if (route.layoutComponent === null) { - return ( - - ; - - ); - } - - const LayoutComponent = route.layoutComponent || MainContent; - + + { + operatorRoutes.map((route) => ( + { + // If the layout component is explicitly null + if (route.layoutComponent === null) { return ( - - - + + ; + ); - }} - /> - )) - } - - -
+ } + + const LayoutComponent = route.layoutComponent || Main; + + return ( + + + + ); + }} + /> + )) + } +
); }} @@ -188,4 +176,7 @@ class Dashboard extends Component { } } -export default withComponents(Dashboard); +export default compose( + withComponents, + withStyles(styles, { name: "RuiDashboard" }) +)(Dashboard); From 14fb957b26750a60b15061e05ae4538faedf7b78 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 09:15:18 -0800 Subject: [PATCH 30/62] feat: update theme colors for components Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 34888aebb4a..0746b9e344e 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -18,7 +18,8 @@ export const rawMuiTheme = { light: colors.coolGrey300, main: colors.coolGrey, dark: colors.coolGrey400 - } + }, + divider: colors.black10 }, typography: { fontSize: 16, @@ -84,6 +85,9 @@ export const rawMuiTheme = { root: { height: toolbarHeight }, + colorPrimary: { + backgroundColor: colors.white + }, colorDefault: { backgroundColor: colors.white } From dceda6cda16b17ce1f2b6ba19b2c1e41d85477e4 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 10:40:00 -0800 Subject: [PATCH 31/62] feat: add sortable tree theme components Signed-off-by: Mike Murray --- .../components/SortableNodeContentRenderer.js | 295 ++++++++++++++++++ .../client/components/SortableTheme.js | 10 + .../components/SortableTreeNodeRenderer.js | 76 +++++ 3 files changed, 381 insertions(+) create mode 100644 imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js create mode 100644 imports/plugins/core/navigation/client/components/SortableTheme.js create mode 100644 imports/plugins/core/navigation/client/components/SortableTreeNodeRenderer.js diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js new file mode 100644 index 00000000000..b03b2ee2a23 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -0,0 +1,295 @@ +// Based on theme: https://github.com/frontend-collective/react-sortable-tree-theme-file-explorer +import React, { Component } from "react"; +import PropTypes from "prop-types"; +import classNames from "classnames"; +import { withStyles } from "@material-ui/core/styles"; +import Card from "@material-ui/core/Card"; +import IconButton from "@material-ui/core/IconButton"; +import ChevronRightIcon from "mdi-material-ui/ChevronRight"; +import DragIcon from "mdi-material-ui/Drag"; + +const styles = (theme) => ({ + cardContent: { + display: "flex", + alignItems: "center", + paddingLeft: theme.spacing.unit, + paddingRight: theme.spacing.unit, + height: "100%" + }, + dragIcon: { + padding: 6 + }, + // Expand styles. This empty object has to be here for "&$expanded" to work below. + expanded: {}, + expandIcon: { + "padding": 6, + "transform": "rotate(0deg)", + "transition": theme.transitions.create("transform", { duration: theme.transitions.duration.shortest }), + "&:hover": { + backgroundColor: "transparent" + }, + "&$expanded": { + transform: "rotate(90deg)" + } + } +}); + +/** + * Check if node is a descendant + * @param {Object} older Parent + * @param {Object} younger Child + * @returns {Boolean} Boolean value + */ +function isDescendant(older, younger) { + return ( + !!older.children && + typeof older.children !== "function" && + older.children.some((child) => child === younger || isDescendant(child, younger)) + ); +} + +class SortableNodeContentRenderer extends Component { + static propTypes = { + buttons: PropTypes.arrayOf(PropTypes.node), + canDrag: PropTypes.bool, + canDrop: PropTypes.bool, + className: PropTypes.string, + classes: PropTypes.object, + connectDragPreview: PropTypes.func.isRequired, + connectDragSource: PropTypes.func.isRequired, + didDrop: PropTypes.bool.isRequired, // eslint-disable-line react/boolean-prop-naming + draggedNode: PropTypes.shape({}), + icons: PropTypes.arrayOf(PropTypes.node), + isDragging: PropTypes.bool.isRequired, + isOver: PropTypes.bool.isRequired, + isSearchFocus: PropTypes.bool, + isSearchMatch: PropTypes.bool, + listIndex: PropTypes.number.isRequired, + lowerSiblingCounts: PropTypes.arrayOf(PropTypes.number).isRequired, + node: PropTypes.shape({}).isRequired, + parentNode: PropTypes.shape({}), // Needed for dndManager + path: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).isRequired, + rowDirection: PropTypes.string.isRequired, + scaffoldBlockPxWidth: PropTypes.number.isRequired, + style: PropTypes.shape({}), + swapDepth: PropTypes.number, + swapFrom: PropTypes.number, + swapLength: PropTypes.number, + title: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), + toggleChildrenVisibility: PropTypes.func, + treeId: PropTypes.string.isRequired, + treeIndex: PropTypes.number.isRequired + } + + static defaultProps = { + buttons: [], + canDrag: false, + canDrop: false, + className: "", + draggedNode: null, + icons: [], + isSearchFocus: false, + isSearchMatch: false, + parentNode: null, + style: {}, + swapDepth: null, + swapFrom: null, + swapLength: null, + title: null, + toggleChildrenVisibility: null + } + + render() { + const { + classes, + + // ReactSortableTreeProps + scaffoldBlockPxWidth, + toggleChildrenVisibility, + connectDragPreview, + connectDragSource, + isDragging, + canDrop, + canDrag, + node, + title, + draggedNode, + path, + treeIndex, + isSearchMatch, + isSearchFocus, + icons, + buttons, + className, + style, + didDrop, + lowerSiblingCounts, + listIndex, + swapFrom, + swapLength, + swapDepth, + treeId, // Not needed, but preserved for other renderers + isOver, // Not needed, but preserved for other renderers + parentNode, // Needed for dndManager + rowDirection, + ...otherProps + } = this.props; + const nodeTitle = title || node.title; + + const isDraggedDescendant = draggedNode && isDescendant(draggedNode, node); + const isLandingPadActive = !didDrop && isDragging; + + // Construct the scaffold representing the structure of the tree + const scaffold = []; + lowerSiblingCounts.forEach((lowerSiblingCount, i) => { + scaffold.push(( +
+ )); + + if (treeIndex !== listIndex && i === swapDepth) { + // This row has been shifted, and is at the depth of + // the line pointing to the new destination + let highlightLineClass = ""; + + if (listIndex === swapFrom + swapLength - 1) { + // This block is on the bottom (target) line + // This block points at the target block (where the row will go when released) + highlightLineClass = styles.highlightBottomLeftCorner; + } else if (treeIndex === swapFrom) { + // This block is on the top (source) line + highlightLineClass = styles.highlightTopLeftCorner; + } else { + // This block is between the bottom and top + highlightLineClass = styles.highlightLineVertical; + } + + scaffold.push(( +
+ )); + } + }); + + const nodeContent = ( +
+ + + + + + + {toggleChildrenVisibility && node.children && node.children.length > 0 && ( + toggleChildrenVisibility({ node, path, treeIndex })} + > + + + )} + +
+ + {/* Set the row preview to be used during drag and drop */} + {connectDragPreview(( +
+ {scaffold} +
+
+
+ {icons.map((icon, index) => ( +
+ {icon} +
+ ))} +
+
+ + {typeof nodeTitle === "function" + ? nodeTitle({ + node, + path, + treeIndex + }) + : nodeTitle} + +
+ +
+ {buttons.map((btn, index) => ( +
+ {btn} +
+ ))} +
+
+
+
+ ))} +
+
+ +
+ ); + + return canDrag + ? connectDragSource(nodeContent, { dropEffect: "copy" }) + : nodeContent; + } +} + +export default withStyles(styles, { name: "RuiSortableNodeContentRenderer" })(SortableNodeContentRenderer); diff --git a/imports/plugins/core/navigation/client/components/SortableTheme.js b/imports/plugins/core/navigation/client/components/SortableTheme.js new file mode 100644 index 00000000000..47ba3743081 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/SortableTheme.js @@ -0,0 +1,10 @@ +import nodeContentRenderer from "./SortableNodeContentRenderer"; +import treeNodeRenderer from "./SortableTreeNodeRenderer"; + +module.exports = { + nodeContentRenderer, + treeNodeRenderer, + scaffoldBlockPxWidth: 65, + rowHeight: 60, + slideRegionSize: 50 +}; diff --git a/imports/plugins/core/navigation/client/components/SortableTreeNodeRenderer.js b/imports/plugins/core/navigation/client/components/SortableTreeNodeRenderer.js new file mode 100644 index 00000000000..327d81a68c3 --- /dev/null +++ b/imports/plugins/core/navigation/client/components/SortableTreeNodeRenderer.js @@ -0,0 +1,76 @@ +import React, { Component, Children, cloneElement } from "react"; +import PropTypes from "prop-types"; + +const styles = {}; + +class FileThemeTreeNodeRenderer extends Component { + static propTypes = { + canDrop: PropTypes.bool, + children: PropTypes.node.isRequired, + connectDropTarget: PropTypes.func.isRequired, + draggedNode: PropTypes.shape({}), + getPrevRow: PropTypes.func.isRequired, + isOver: PropTypes.bool.isRequired, + listIndex: PropTypes.number.isRequired, + lowerSiblingCounts: PropTypes.arrayOf(PropTypes.number).isRequired, + node: PropTypes.shape({}).isRequired, + path: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).isRequired, + rowDirection: PropTypes.string.isRequired, + scaffoldBlockPxWidth: PropTypes.number.isRequired, + swapDepth: PropTypes.number, + swapFrom: PropTypes.number, + swapLength: PropTypes.number, + treeId: PropTypes.string.isRequired, + treeIndex: PropTypes.number.isRequired + } + + static defaultProps = { + swapFrom: null, + swapDepth: null, + swapLength: null, + canDrop: false, + draggedNode: null + } + + render() { + const { + children, + listIndex, + swapFrom, + swapLength, + swapDepth, + scaffoldBlockPxWidth, + lowerSiblingCounts, + connectDropTarget, + isOver, + draggedNode, + canDrop, + treeIndex, + treeId, // Delete from otherProps + getPrevRow, // Delete from otherProps + node, // Delete from otherProps + path, // Delete from otherProps + rowDirection, + ...otherProps + } = this.props; + + return connectDropTarget(( +
+ {Children.map(children, (child) => ( + cloneElement(child, { + isOver, + canDrop, + draggedNode, + lowerSiblingCounts, + listIndex, + swapFrom, + swapLength, + swapDepth + }) + ))} +
+ )); + } +} + +export default FileThemeTreeNodeRenderer; From 3a611fd2febcc600fb8c8b6abe665bf913265eab Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 10:41:07 -0800 Subject: [PATCH 32/62] feat: use button component Signed-off-by: Mike Murray --- .../core/navigation/client/components/NavigationItemList.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemList.js b/imports/plugins/core/navigation/client/components/NavigationItemList.js index befffd2a600..e5792c756be 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemList.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemList.js @@ -1,7 +1,7 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; -import Link from "@reactioncommerce/components/Link/v1"; +import Button from "@material-ui/core/Button"; import NavigationItemCard from "./NavigationItemCard"; const Wrapper = styled.div` @@ -48,7 +48,7 @@ class PagesList extends Component { return ( - Add navigation item + {this.renderNavigationItems()} From 1fdd4aaa9ae36ec70e158c4d8d9b88fb092a09c0 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 10:42:17 -0800 Subject: [PATCH 33/62] refactor: remove tabs and use only the navigation item list Signed-off-by: Mike Murray --- .../client/components/NavigationItemTabs.js | 49 ++++--------------- 1 file changed, 9 insertions(+), 40 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs.js index f8f1eebffd4..e37e48839e5 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemTabs.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemTabs.js @@ -24,60 +24,29 @@ class NavigationItemTabs extends React.Component { onClickAddNavigationItem: PropTypes.func, onClickUpdateNavigationItem: PropTypes.func, onSetDraggingNavigationItemId: PropTypes.func, - tags: PropTypes.array, updateNavigationItem: PropTypes.func } - state = { - value: 0 - }; - - handleChange = (event, value) => { - this.setState({ value }); - }; + render() { + const { classes } = this.props; - renderNavigationItemsTab() { - const { value } = this.state; const { navigationItems, onClickAddNavigationItem, onClickUpdateNavigationItem, onSetDraggingNavigationItemId, - tags, updateNavigationItem } = this.props; - if (value === 0) { - return ; - } - return ( - - ); - } - - render() { - const { classes } = this.props; - const { value } = this.state; return (
- - - - - - {this.renderNavigationItemsTab()} +
); } From 6f402824a48fa910d7c2e7bec3675629a4a374ae Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 10:43:27 -0800 Subject: [PATCH 34/62] feat: update component to better match designs Signed-off-by: Mike Murray --- .../client/components/NavigationItemCard.js | 66 +++++++++++-------- 1 file changed, 38 insertions(+), 28 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard.js index cae2e1571ef..93f6a951d5b 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard.js @@ -3,8 +3,10 @@ import PropTypes from "prop-types"; import { compose } from "recompose"; import { DragSource, DropTarget } from "react-dnd"; import styled from "styled-components"; -import Paper from "@material-ui/core/Paper"; +import Card from "@material-ui/core/Card"; +import { withStyles } from "@material-ui/core/styles"; import Link from "@reactioncommerce/components/Link/v1"; +import DragIcon from "mdi-material-ui/Drag"; import iconEllipsisV from "../svg/iconEllipsisV"; import iconChevronDown from "../svg/iconChevronDown"; import iconChevronRight from "../svg/iconChevronRight"; @@ -88,6 +90,12 @@ const ActionIconSpan = styled.span` color: #666666; `; +const styles = (theme) => ({ + card: { + marignTop: -1 + } +}); + const navigationItemSource = { beginDrag(props) { const { node: { navigationItem } } = props.row; @@ -201,6 +209,7 @@ class NavigationItemCard extends Component { render() { const { + classes, connectDragPreview, connectDragSource, isDragging, @@ -229,32 +238,30 @@ class NavigationItemCard extends Component { const toRender = (
- - - - { - connectDragSource(( -
- {iconEllipsisV} -
- ), { - dropEffect: "copy" - }) - } - {this.renderChildrenToggleButton()} - - {title} - - - {type === "tag" ? iconTag : iconFile} - - {description} - - - {this.renderActionButtons()} -
-
-
+ + + { + connectDragSource(( +
+ +
+ ), { + dropEffect: "copy" + }) + } + {this.renderChildrenToggleButton()} + + {title} + + + {type === "tag" ? iconTag : iconFile} + + {description} + + + {this.renderActionButtons()} +
+
); @@ -262,4 +269,7 @@ class NavigationItemCard extends Component { } } -export default compose(DragSource("CARD", navigationItemSource, sourceCollect))(NavigationItemCard); +export default compose( + DragSource("CARD", navigationItemSource, sourceCollect), + withStyles(styles, { name: "RuiNavigationItemCard" }) +)(NavigationItemCard); From 953b5953ec76d5609a61fdb27ed94709331c07de Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 10:45:25 -0800 Subject: [PATCH 35/62] feat: update layout Signed-off-by: Mike Murray --- .../components/NavigationTreeContainer.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js index 08c75c3bb47..78604bb890b 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js @@ -2,9 +2,11 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; import Grid from "@material-ui/core/Grid"; +import withStyles from "@material-ui/core/styles/withStyles"; import { SortableTreeWithoutDndContext as SortableTree } from "react-sortable-tree"; import "react-sortable-tree/style.css"; import NavigationTreeNode from "./NavigationTreeNode"; +import SortableTheme from "./SortableTheme"; const Wrapper = styled.div` @@ -20,6 +22,12 @@ const NavigationItemsListContainer = styled.div` margin-top: 50px; `; +const styles = (theme) => ({ + wrapper: { + borderLeft: `1px solid ${theme.palette.divider}` + } +}); + class NavigationTreeContainer extends Component { static propTypes = { navigationTreeRows: PropTypes.array, @@ -51,9 +59,9 @@ class NavigationTreeContainer extends Component { } render() { - const { onSetSortableNavigationTree, sortableNavigationTree } = this.props; + const { classes, onSetSortableNavigationTree, sortableNavigationTree } = this.props; return ( - +
@@ -61,17 +69,18 @@ class NavigationTreeContainer extends Component { -
+
- +
); } } -export default NavigationTreeContainer; +export default withStyles(styles, { name: "RuiNavigationTreeContainer" })(NavigationTreeContainer); From ab52eafe12a95d554cc621ad19d767229995e465 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 10:45:41 -0800 Subject: [PATCH 36/62] feat: update layout Signed-off-by: Mike Murray --- .../navigation/client/components/NavigationDashboard.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js index 90e4705d937..b7aee4a2ca6 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard.js @@ -15,6 +15,10 @@ import NavigationTreeContainer from "./NavigationTreeContainer"; import NavigationItemTabs from "./NavigationItemTabs"; const styles = (theme) => ({ + root: { + height: "100vh", + overflow: "hidden" + }, toolbarButton: { marginLeft: theme.spacing.unit }, @@ -114,8 +118,7 @@ class NavigationDashboard extends Component { }); return ( -
- +
Main Navigation From d7a0e53d8a568058eebbe5443657532f7dbe0ba4 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Mon, 4 Feb 2019 15:19:18 -0800 Subject: [PATCH 37/62] refactor: simplifying NavigationItemCard internal components, icons and layout Signed-off-by: Mike Murray --- .../client/components/NavigationItemCard.js | 247 ++++-------------- 1 file changed, 47 insertions(+), 200 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard.js index 93f6a951d5b..db428394281 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard.js @@ -1,98 +1,32 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import { compose } from "recompose"; -import { DragSource, DropTarget } from "react-dnd"; -import styled from "styled-components"; +import { DragSource } from "react-dnd"; import Card from "@material-ui/core/Card"; +import IconButton from "@material-ui/core/IconButton"; import { withStyles } from "@material-ui/core/styles"; -import Link from "@reactioncommerce/components/Link/v1"; import DragIcon from "mdi-material-ui/Drag"; -import iconEllipsisV from "../svg/iconEllipsisV"; -import iconChevronDown from "../svg/iconChevronDown"; -import iconChevronRight from "../svg/iconChevronRight"; -import iconPencil from "../svg/iconPencil"; -import iconTimes from "../svg/iconTimes"; -import iconFile from "../svg/iconFile"; -import iconTag from "../svg/iconTag"; - -const CardContainer = styled.div` - margin-bottom: 5px; -`; -const CardContentContainer = styled.div` - display: flex; - padding: 10px 20px; -`; - -const HandleIconSpan = styled.span` - display: inline-block; - height: 16px; - width: 16px; - color: #e6e6e6; - cursor: pointer; -`; - -const ChevronIconContainer = styled.div` - width: 20px; - display: flex; - flex-direction: column; - justify-content: center; -`; - -const ChevronIconSpan = styled.span` - display: inline-block; - height: 16px; - width: 16px; - color: #666666; -`; - -const NavDetailContainer = styled.div` - display: flex; - flex-direction: column; - justify-content: center; - padding-left: 20px; -`; - -const NavName = styled.p` - font-weight: 700; - margin: 0; -`; - -const NavDesc = styled.p` - font-weight: 400; - margin: 0; - color: #b3b3b3; -`; - -const NavDescSpan = styled.span` - display: inline-block; - height: 16px; - width: 16px; - margin-right: 10px; -`; - -const ActionsContainer = styled.div` - margin-left: auto; - display: flex; -`; - -const ActionIconContainer = styled.div` - width: 50px; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; -`; - -const ActionIconSpan = styled.span` - display: inline-block; - height: 20px; - width: 20px; - color: #666666; -`; +import FileOutlineIcon from "mdi-material-ui/FileOutline"; +import PencilIcon from "mdi-material-ui/Pencil"; +import { Typography } from "@material-ui/core"; const styles = (theme) => ({ card: { - marignTop: -1 + height: 60, + display: "flex", + alignItems: "center", + paddingLeft: theme.spacing.unit, + paddingRight: theme.spacing.unit + }, + rowContent: { + flex: 1 + }, + subtitle: { + display: "flex", + alignItems: "center" + }, + subtitleIcon: { + marginRight: 4 } }); @@ -113,6 +47,9 @@ const navigationItemSource = { /** * Specifies which props to inject into your component. + * @param {Object} connect DnD connect + * @param {Object} monitor DnD monitor + * @returns {Object} DnD connection source */ function sourceCollect(connect, monitor) { return { @@ -124,89 +61,19 @@ function sourceCollect(connect, monitor) { class NavigationItemCard extends Component { static propTypes = { + classes: PropTypes.func, + connectDragPreview: PropTypes.func, + connectDragSource: PropTypes.func, + isDragging: PropTypes.bool, onClickUpdateNavigationItem: PropTypes.func, - onSetDraggingNavigationItemId: PropTypes.func, - onToggleChildrenVisibility: PropTypes.func, row: PropTypes.object }; - get type() { - const { row } = this.props; - let type; - if (row) { - const { node: { navigationItem, tag } } = row; - if (tag) { - type = "tag"; - } else if (navigationItem) { - type = "item"; - } - } - return type; - } - - get isInTree() { - const { row } = this.props; - if (row) { - const { treeIndex } = row; - if (treeIndex !== undefined) { - return true; - } - } - return false; - } - handleClickEdit = () => { const { onClickUpdateNavigationItem, row: { node: { navigationItem } } } = this.props; onClickUpdateNavigationItem(navigationItem); } - handleToggleVisibility = () => { - const { onToggleChildrenVisibility, row: { path } } = this.props; - onToggleChildrenVisibility(path); - } - - renderActionButtons() { - const removeActionButton = ( - - - {iconTimes} - - - ); - - const editActionButton = ( - - - {iconPencil} - - - ); - - if (this.isInTree || this.type === "item") { - return ( - - { this.type === "item" ? editActionButton : null } - { this.isInTree ? removeActionButton : null } - - ); - } - return null; - } - - renderChildrenToggleButton() { - const { row: { node: { expanded, items } } } = this.props; - if (this.isInTree && items && items.length > 0) { - return ( - - - {expanded ? iconChevronDown : iconChevronRight } - - - ); - } - return null; - } - render() { const { classes, @@ -216,51 +83,31 @@ class NavigationItemCard extends Component { row } = this.props; - let title; - let description; - let type; + const { node: { navigationItem } } = row; + const { draftData } = navigationItem; + const { value } = draftData.content.find((content) => content.language === "en"); + const title = value; + const { url: subtitle } = draftData; - if (row) { - const { node: { tag, navigationItem } } = row; - if (tag) { - const { name } = tag; - title = name; - description = name; - type = "tag"; - } else if (navigationItem) { - const { draftData } = navigationItem; - const { value } = draftData.content.find((content) => content.language === "en"); - title = value; - ({ url: description } = draftData); - type = "item"; - } - } + const dragHandle = ( +
+ +
+ ); const toRender = (
- - { - connectDragSource(( -
- -
- ), { - dropEffect: "copy" - }) - } - {this.renderChildrenToggleButton()} - - {title} - - - {type === "tag" ? iconTag : iconFile} - - {description} - - - {this.renderActionButtons()} -
+ {connectDragSource(dragHandle, { dropEffect: "copy" })} +
+ {title} + + {subtitle} + +
+ + +
); From 1601601be328a32ea12fd793340eaad125d8d99e Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 09:17:14 -0800 Subject: [PATCH 38/62] feat: add edit and remove buttons to tree nodes Signed-off-by: Mike Murray --- .../components/NavigationTreeContainer.js | 50 +++++++++++++++---- 1 file changed, 40 insertions(+), 10 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js index 78604bb890b..41ae7ea64ff 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js @@ -2,26 +2,20 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import styled from "styled-components"; import Grid from "@material-ui/core/Grid"; +import IconButton from "@material-ui/core/IconButton"; import withStyles from "@material-ui/core/styles/withStyles"; -import { SortableTreeWithoutDndContext as SortableTree } from "react-sortable-tree"; +import PencilIcon from "mdi-material-ui/Pencil"; +import CloseIcon from "mdi-material-ui/Close"; +import { SortableTreeWithoutDndContext as SortableTree, removeNodeAtPath } from "react-sortable-tree"; import "react-sortable-tree/style.css"; import NavigationTreeNode from "./NavigationTreeNode"; import SortableTheme from "./SortableTheme"; - -const Wrapper = styled.div` - border-left: 1px solid #ccc; -`; - const ContentWrapper = styled.div` padding: 40px 80px; min-height: calc(100vh - 140px); `; -const NavigationItemsListContainer = styled.div` - margin-top: 50px; -`; - const styles = (theme) => ({ wrapper: { borderLeft: `1px solid ${theme.palette.divider}` @@ -30,6 +24,7 @@ const styles = (theme) => ({ class NavigationTreeContainer extends Component { static propTypes = { + classes: PropTypes.object, navigationTreeRows: PropTypes.array, onClickUpdateNavigationItem: PropTypes.func, onDragHover: PropTypes.func, @@ -40,6 +35,40 @@ class NavigationTreeContainer extends Component { sortableNavigationTree: PropTypes.arrayOf(PropTypes.object) } + static defaultProps = { + onSetSortableNavigationTree() {} + } + + getNodeKey = ({ treeIndex }) => treeIndex; + + generateNodeProps = ({ node, path }) => { + const { onClickUpdateNavigationItem, onSetSortableNavigationTree, sortableNavigationTree } = this.props; + + return { + buttons: [ + { + onClickUpdateNavigationItem(node.navigationItem); + }} + > + + , + { + const newSortableNavigationTree = removeNodeAtPath({ + treeData: sortableNavigationTree, + path, + getNodeKey: this.getNodeKey + }); + onSetSortableNavigationTree(newSortableNavigationTree); + }} + > + + + ] + }; + } + renderRows() { const { navigationTreeRows, onClickUpdateNavigationItem, onDragHover, onSetOverNavigationItemId, onToggleChildrenVisibility } = this.props; let rows = null; @@ -71,6 +100,7 @@ class NavigationTreeContainer extends Component {
Date: Tue, 5 Feb 2019 09:18:53 -0800 Subject: [PATCH 39/62] refactor: simplify JSX and apply some additional layout styles Signed-off-by: Mike Murray --- .../components/SortableNodeContentRenderer.js | 119 ++++++++---------- 1 file changed, 52 insertions(+), 67 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js index b03b2ee2a23..890e2c959a5 100644 --- a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -5,6 +5,7 @@ import classNames from "classnames"; import { withStyles } from "@material-ui/core/styles"; import Card from "@material-ui/core/Card"; import IconButton from "@material-ui/core/IconButton"; +import Typography from "@material-ui/core/Typography"; import ChevronRightIcon from "mdi-material-ui/ChevronRight"; import DragIcon from "mdi-material-ui/Drag"; @@ -31,6 +32,20 @@ const styles = (theme) => ({ "&$expanded": { transform: "rotate(90deg)" } + }, + row: { + display: "flex", + flex: 1, + alignItems: "center" + }, + rowDragDisabled: { + opacity: 0.8 + }, + rowContent: { + flex: 1 + }, + rowToolbar: { + display: "flex" } }); @@ -210,77 +225,47 @@ class SortableNodeContentRenderer extends Component { )} + {/* Set the row preview to be used during drag and drop */} + {connectDragPreview(( +
+ {scaffold} +
+
+ + {typeof nodeTitle === "function" ? nodeTitle({ node, path, treeIndex }) : nodeTitle} + + + {node.secondaryText} + +
-
- - {/* Set the row preview to be used during drag and drop */} - {connectDragPreview(( -
- {scaffold} -
-
-
- {icons.map((icon, index) => ( -
- {icon} -
- ))} -
-
- - {typeof nodeTitle === "function" - ? nodeTitle({ - node, - path, - treeIndex - }) - : nodeTitle} - -
- -
- {buttons.map((btn, index) => ( -
- {btn} -
- ))} +
+ {buttons.map((button, index) => ( +
+ {button}
-
+ ))}
- ))} -
+
+ ))}
From 8bf12daa1e4e5956c9aee4ef73709b23a4eff31f Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 09:21:13 -0800 Subject: [PATCH 40/62] refactor: use subtitle Signed-off-by: Mike Murray --- .../client/components/SortableNodeContentRenderer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js index 890e2c959a5..5938fa4428f 100644 --- a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -128,6 +128,7 @@ class SortableNodeContentRenderer extends Component { canDrag, node, title, + subtitle, draggedNode, path, treeIndex, @@ -150,6 +151,7 @@ class SortableNodeContentRenderer extends Component { ...otherProps } = this.props; const nodeTitle = title || node.title; + const nodeSubtitle = subtitle || node.subtitle; const isDraggedDescendant = draggedNode && isDescendant(draggedNode, node); const isLandingPadActive = !didDrop && isDragging; @@ -249,7 +251,7 @@ class SortableNodeContentRenderer extends Component { {typeof nodeTitle === "function" ? nodeTitle({ node, path, treeIndex }) : nodeTitle} - {node.secondaryText} + {typeof nodeSubtitle === "function" ? nodeSubtitle({ node, path, treeIndex }) : nodeSubtitle}
From d1976b48ebb4f0c172afc312a0cab3142b005427 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 09:21:45 -0800 Subject: [PATCH 41/62] feat: add subtitle and original navigation item to tree node object Signed-off-by: Mike Murray --- .../core/navigation/client/hocs/withNavigationUIStore.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js index 77eaae4e982..652bb3e7f81 100644 --- a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -628,8 +628,7 @@ export default (Component) => ( const rows = this.getRows(addedResult.navigationTree); const expandedParentPath = rows[addedResult.treeIndex].path; - console.log("ROWS", rows); - console.log(expandedParentPath); + return { draggedNode, targetDepth, @@ -697,6 +696,8 @@ export default (Component) => ( const newNode = {}; newNode.id = node.navigationItem._id; newNode.title = this.getNavigationItemTitle(node.navigationItem).value; + newNode.subtitle = node.navigationItem.draftData.url; + newNode.navigationItem = { ...node.navigationItem }; if (Array.isArray(node.items) && node.items.length) { newNode.children = this.navigationTreeToSortable(node.items); From 578d91e39dd8e06db6868716eefc3407095c2c65 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 09:22:18 -0800 Subject: [PATCH 42/62] feat: additional improvements to the theme to better reflect the designs Signed-off-by: Mike Murray --- .../core/router/client/theme/muiTheme.js | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 0746b9e344e..042c01a529c 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -7,16 +7,22 @@ const { rui_typography: typography } = defaultComponentTheme; const breakpoints = createBreakpoints({}); const toolbarHeight = 80; +export const defaultSpacingUnit = 10; + +// Colors +export const colorPrimaryMain = colors.coolGrey; +export const colorSecondaryMain = colors.coolGrey; + export const rawMuiTheme = { palette: { primary: { light: colors.coolGrey300, - main: colors.coolGrey, + main: colorPrimaryMain, dark: colors.coolGrey400 }, secondary: { light: colors.coolGrey300, - main: colors.coolGrey, + main: colorSecondaryMain, dark: colors.coolGrey400 }, divider: colors.black10 @@ -30,6 +36,16 @@ export const rawMuiTheme = { useNextVariants: true, h6: { fontSize: 18 + }, + subtitle1: { + fontSize: 16 + }, + button: { + fontSize: 14, + letterSpacing: 0.8 + }, + caption: { + color: colors.black30 } }, shadows: [ @@ -62,6 +78,9 @@ export const rawMuiTheme = { shape: { borderRadius: 2 }, + spacing: { + unit: defaultSpacingUnit + }, mixins: { toolbar: { minHeight: toolbarHeight, @@ -95,11 +114,18 @@ export const rawMuiTheme = { MuiButton: { root: { textTransform: "initial" + }, + outlinedPrimary: { + border: `1px solid ${colorPrimaryMain}` + }, + outlinedSecondary: { + border: `1px solid ${colorSecondaryMain}` } }, MuiCard: { root: { - border: `1px solid ${colors.black10}` + border: `1px solid ${colors.black10}`, + padding: `${defaultSpacingUnit}px ${defaultSpacingUnit * 2}px` } }, MuiCheckbox: { From 34eae18f3edc92889ea6288badd4ba446b668def Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:38:12 -0800 Subject: [PATCH 43/62] feat: add ConfirmDialog component Signed-off-by: Mike Murray --- .../components/ConfirmDialog/ConfirmDialog.js | 84 +++++++++++++++++++ .../ui/components/ConfirmDialog/index.js | 1 + 2 files changed, 85 insertions(+) create mode 100644 imports/client/ui/components/ConfirmDialog/ConfirmDialog.js create mode 100644 imports/client/ui/components/ConfirmDialog/index.js diff --git a/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js b/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js new file mode 100644 index 00000000000..5d16a763b63 --- /dev/null +++ b/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js @@ -0,0 +1,84 @@ +import React, { Component, Fragment } from "react"; +import PropTypes from "prop-types"; +import Button from "@material-ui/core/Button"; +import Dialog from "@material-ui/core/Dialog"; +import DialogActions from "@material-ui/core/DialogActions"; +import DialogContent from "@material-ui/core/DialogContent"; +import DialogContentText from "@material-ui/core/DialogContentText"; +import DialogTitle from "@material-ui/core/DialogTitle"; + +class ConfirmDialog extends Component { + static propTypes = { + cancelActionText: PropTypes.string, + children: PropTypes.func.isRequired, + confirmActionText: PropTypes.string, + message: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), + onConfirm: PropTypes.func, + openButtonContent: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired, + title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]) + } + + static defaultProps = { + cancelActionText: "Cancel", + confirmActionText: "OK", + onConfirm() {} + } + + state = { + isOpen: false + } + + handleClose = () => { + this.setState({ + isOpen: false + }); + } + + handleConfirm = () => { + this.props.onConfirm(); + this.handleClose(); + } + + handleOpen = () => { + this.setState({ + isOpen: true + }); + } + + render() { + const { children, title, message, confirmActionText, cancelActionText } = this.props; + const { isOpen } = this.state; + + return ( + + {children({ + openDialog: this.handleOpen + })} + + {title} + {message && ( + + {message} + + )} + + + + + + + + ); + } +} + +export default ConfirmDialog; diff --git a/imports/client/ui/components/ConfirmDialog/index.js b/imports/client/ui/components/ConfirmDialog/index.js new file mode 100644 index 00000000000..bb8a3267924 --- /dev/null +++ b/imports/client/ui/components/ConfirmDialog/index.js @@ -0,0 +1 @@ +export { default } from "./ConfirmDialog"; From 24d3f3707bbe0f243e333c226e3876a3ccf97ebe Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:38:34 -0800 Subject: [PATCH 44/62] fix: button padding Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 042c01a529c..96b9b8bb0e8 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -113,6 +113,7 @@ export const rawMuiTheme = { }, MuiButton: { root: { + padding: `${defaultSpacingUnit}px ${defaultSpacingUnit * 2}px`, textTransform: "initial" }, outlinedPrimary: { @@ -125,7 +126,6 @@ export const rawMuiTheme = { MuiCard: { root: { border: `1px solid ${colors.black10}`, - padding: `${defaultSpacingUnit}px ${defaultSpacingUnit * 2}px` } }, MuiCheckbox: { From e69a05baeb61d5a272c4b8bcd3e6420ef8afdc60 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:38:44 -0800 Subject: [PATCH 45/62] feat: add translations Signed-off-by: Mike Murray --- imports/plugins/core/navigation/server/i18n/en.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/imports/plugins/core/navigation/server/i18n/en.json b/imports/plugins/core/navigation/server/i18n/en.json index 3a5a28240dc..26b3f84236a 100644 --- a/imports/plugins/core/navigation/server/i18n/en.json +++ b/imports/plugins/core/navigation/server/i18n/en.json @@ -6,7 +6,12 @@ "navigation": { "admin": { "navigation": { - "navigation": "Navigation" + "cancel": "Cancel", + "ok": "OK", + "delete": "Delete", + "navigation": "Navigation", + "deleteTitle": "Delete navigation item?", + "deleteMessage": "Deleting navigation items also removes them from the navigation tree." } }, "navigationItem": { From f545d1add1591ae41491e35228226ba8c74455ae Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:39:17 -0800 Subject: [PATCH 46/62] fix: prop type Signed-off-by: Mike Murray --- .../core/navigation/client/components/NavigationItemCard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard.js index db428394281..1625bc40cf7 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard.js @@ -61,7 +61,7 @@ function sourceCollect(connect, monitor) { class NavigationItemCard extends Component { static propTypes = { - classes: PropTypes.func, + classes: PropTypes.object, connectDragPreview: PropTypes.func, connectDragSource: PropTypes.func, isDragging: PropTypes.bool, From 8963a0646bd6b51fec7194af61fbe01e973c37ac Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:39:59 -0800 Subject: [PATCH 47/62] feat: add confirmation dialog for remove button Signed-off-by: Mike Murray --- .../components/NavigationTreeContainer.js | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js index 41ae7ea64ff..84776aff1d1 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js @@ -8,6 +8,7 @@ import PencilIcon from "mdi-material-ui/Pencil"; import CloseIcon from "mdi-material-ui/Close"; import { SortableTreeWithoutDndContext as SortableTree, removeNodeAtPath } from "react-sortable-tree"; import "react-sortable-tree/style.css"; +import ConfirmDialog from "/imports/client/ui/components/ConfirmDialog"; import NavigationTreeNode from "./NavigationTreeNode"; import SortableTheme from "./SortableTheme"; @@ -46,15 +47,14 @@ class NavigationTreeContainer extends Component { return { buttons: [ - { - onClickUpdateNavigationItem(node.navigationItem); - }} - > + { onClickUpdateNavigationItem(node.navigationItem); }}> , - { + } + title={"Remove this navigation item?"} + onConfirm={() => { const newSortableNavigationTree = removeNodeAtPath({ treeData: sortableNavigationTree, path, @@ -63,8 +63,12 @@ class NavigationTreeContainer extends Component { onSetSortableNavigationTree(newSortableNavigationTree); }} > - - + {({ openDialog }) => ( + + + + )} + ] }; } @@ -97,11 +101,10 @@ class NavigationTreeContainer extends Component {

Drag and drop pages and tags from the left column into the navigation structure.

-
Date: Tue, 5 Feb 2019 14:43:16 -0800 Subject: [PATCH 48/62] feat: update title and subtitle display Signed-off-by: Mike Murray --- .../components/SortableNodeContentRenderer.js | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js index 5938fa4428f..bd519c76553 100644 --- a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -8,6 +8,7 @@ import IconButton from "@material-ui/core/IconButton"; import Typography from "@material-ui/core/Typography"; import ChevronRightIcon from "mdi-material-ui/ChevronRight"; import DragIcon from "mdi-material-ui/Drag"; +import FileOutlineIcon from "mdi-material-ui/FileOutline"; const styles = (theme) => ({ cardContent: { @@ -46,6 +47,13 @@ const styles = (theme) => ({ }, rowToolbar: { display: "flex" + }, + subtitle: { + display: "flex", + alignItems: "center" + }, + subtitleIcon: { + marginRight: 4 } }); @@ -87,6 +95,7 @@ class SortableNodeContentRenderer extends Component { rowDirection: PropTypes.string.isRequired, scaffoldBlockPxWidth: PropTypes.number.isRequired, style: PropTypes.shape({}), + subtitle: PropTypes.oneOfType([PropTypes.func, PropTypes.node]), swapDepth: PropTypes.number, swapFrom: PropTypes.number, swapLength: PropTypes.number, @@ -247,11 +256,11 @@ class SortableNodeContentRenderer extends Component { }} >
- + {typeof nodeTitle === "function" ? nodeTitle({ node, path, treeIndex }) : nodeTitle} - - {typeof nodeSubtitle === "function" ? nodeSubtitle({ node, path, treeIndex }) : nodeSubtitle} + + {typeof nodeSubtitle === "function" ? nodeSubtitle({ node, path, treeIndex }) : nodeSubtitle}
From a7a9ccae2b78c08d7eb554b0782d9c7e54996407 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:47:29 -0800 Subject: [PATCH 49/62] feat: add delete and publish Signed-off-by: Mike Murray --- .../client/components/NavigationDashboard.js | 21 +++-- .../client/components/NavigationItemForm.js | 43 +++++++-- .../client/hocs/withDefaultNavigationTree.js | 4 +- .../client/hocs/withUpdateNavigationItem.js | 92 +++++++++++++++++-- 4 files changed, 132 insertions(+), 28 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js index b7aee4a2ca6..a41d5cd514e 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard.js @@ -44,6 +44,7 @@ class NavigationDashboard extends Component { static propTypes = { classes: PropTypes.object, createNavigationItem: PropTypes.func, + deleteNavigationItem: PropTypes.func, navigationItems: PropTypes.array, navigationTreeRows: PropTypes.array, onDragHover: PropTypes.func, @@ -54,7 +55,8 @@ class NavigationDashboard extends Component { uiState: PropTypes.shape({ isLeftDrawerOpen: PropTypes.bool }), - updateNavigationItem: PropTypes.func + updateNavigationItem: PropTypes.func, + updateNavigationTree: PropTypes.func } state = { @@ -67,13 +69,13 @@ class NavigationDashboard extends Component { tags: [] } - addNavigationItem = () => this.setState({ isModalOpen: true, modalMode: "create", navigationItem: null }); - - handleCloseModal = () => this.setState({ isModalOpen: false }); - - handleSetDraggingNavigationItemId = (draggingNavigationItemId) => this.setState({ draggingNavigationItemId }); + addNavigationItem = () => { + this.setState({ isModalOpen: true, modalMode: "create", navigationItem: null }); + } - handleSetOverNavigationItemId = (overNavigationItemId) => this.setState({ overNavigationItemId }); + handleCloseModal = () => { + this.setState({ isModalOpen: false }); + } updateNavigationItem = (navigationItemDoc) => { const { _id, draftData } = navigationItemDoc; @@ -94,6 +96,7 @@ class NavigationDashboard extends Component { const { classes, createNavigationItem, + deleteNavigationItem, navigationItems, navigationTreeRows, onDragHover, @@ -123,8 +126,7 @@ class NavigationDashboard extends Component { Main Navigation - - + @@ -161,6 +163,7 @@ class NavigationDashboard extends Component {
{ + const { deleteNavigationItem, navigationItem, onCloseForm } = this.props; + deleteNavigationItem({ + variables: { + input: { + _id: navigationItem._id + } + } + }); + return onCloseForm(); + } + handleClickSave = () => { if (this.form) { this.form.submit(); @@ -113,7 +127,7 @@ class NavigationItemForm extends Component { } render() { - const { onCloseForm, navigationItem } = this.props; + const { onCloseForm, mode, navigationItem } = this.props; const nameInputId = `name_${this.uniqueInstanceIdentifier}`; const urlInputId = `url_${this.uniqueInstanceIdentifier}`; @@ -134,9 +148,9 @@ class NavigationItemForm extends Component { {this.renderActionTitle()} - - {iconTimes} - + + + @@ -167,8 +181,19 @@ class NavigationItemForm extends Component { - - + { mode !== "create" && ( + + {({ openDialog }) => ( + + )} + + )} + + diff --git a/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js b/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js index 5a2702990e0..6ccb213dc39 100644 --- a/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js +++ b/imports/plugins/core/navigation/client/hocs/withDefaultNavigationTree.js @@ -39,12 +39,12 @@ export default (Component) => ( onCompleted={this.handleSetNavigationTree} notifyOnNetworkStatusChange={true} > - {({ data, loading }) => { + {({ data, loading, refetch }) => { if (!loading) { const { navigationTreeById: { name } } = data; props.navigationTreeName = name; } - return ; + return ; }} ); diff --git a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js index e4e5651b194..2fe120cda3a 100644 --- a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js +++ b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js @@ -1,7 +1,7 @@ import React from "react"; import PropTypes from "prop-types"; import gql from "graphql-tag"; -import { Mutation } from "react-apollo"; +import { Mutation, withApollo } from "react-apollo"; import { navigationItemFragment } from "./fragments"; const updateNavigationItemMutation = gql` @@ -15,28 +15,104 @@ const updateNavigationItemMutation = gql` ${navigationItemFragment.navigationItem} `; -export default (Component) => ( +const deleteNavigationItemMutation = gql` + mutation deleteNavigationItemMutation($input: DeleteNavigationItemInput!) { + deleteNavigationItem(input: $input) { + navigationItem { + ...NavigationItemCommon + } + } + } + ${navigationItemFragment.navigationItem} +`; + +const publishNavigationChangesMutation = gql` + mutation publishNavigationChangesMutation($input: PublishNavigationChangesInput!) { + publishNavigationChanges(input: $input) { + navigationTree { + name + draftItems { + expanded + navigationItem { + ...NavigationItemCommon + } + items { + expanded + navigationItem { + ...NavigationItemCommon + } + items { + navigationItem { + ...NavigationItemCommon + } + } + } + } + } + } + } + ${navigationItemFragment.navigationItem} +`; + +export default (Component) => { class WithUpdateNavigationItem extends React.Component { static propTypes = { - onUpdateNavigationItem: PropTypes.func + client: PropTypes.object, + defaultNavigationTreeId: PropTypes.string, + onDeleteNavigationItem: PropTypes.func, + onUpdateNavigationItem: PropTypes.func, + refetchNavigationTree: PropTypes.func + } + + static defaultProps = { + onDeleteNavigationItem() {}, + onUpdateNavigationItem() {} } handleUpdateNavigationItem = (data) => { const { updateNavigationItem: { navigationItem } } = data; this.props.onUpdateNavigationItem(navigationItem); + this.handlePublishNavigationChanges(); + } + + handleDeleteNavigationItem = (data) => { + const { deleteNavigationItem: { navigationItem } } = data; + this.props.onDeleteNavigationItem(navigationItem); + this.props.refetchNavigationTree(); + this.handlePublishNavigationChanges(); + } + + handlePublishNavigationChanges = () => { + const { client, defaultNavigationTreeId } = this.props; + + client.mutate({ + mutation: publishNavigationChangesMutation, + variables: { + input: { + _id: defaultNavigationTreeId + } + } + }); } render() { return ( {(updateNavigationItem) => ( - + + {(deleteNavigationItem) => ( + + )} + )} ); } } -); + + return withApollo(WithUpdateNavigationItem); +}; From 422e20267a2f53d2dedc32e342718e5ddb2e5fb4 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 14:49:11 -0800 Subject: [PATCH 50/62] refactor: remove unused icons Signed-off-by: Mike Murray --- .../navigation/client/svg/iconChevronDown.js | 23 -------------- .../navigation/client/svg/iconChevronRight.js | 23 -------------- .../navigation/client/svg/iconEllipsisV.js | 31 ------------------- .../core/navigation/client/svg/iconFile.js | 23 -------------- .../core/navigation/client/svg/iconPencil.js | 27 ---------------- .../core/navigation/client/svg/iconTag.js | 23 -------------- .../core/navigation/client/svg/iconTimes.js | 23 -------------- 7 files changed, 173 deletions(-) delete mode 100644 imports/plugins/core/navigation/client/svg/iconChevronDown.js delete mode 100644 imports/plugins/core/navigation/client/svg/iconChevronRight.js delete mode 100644 imports/plugins/core/navigation/client/svg/iconEllipsisV.js delete mode 100644 imports/plugins/core/navigation/client/svg/iconFile.js delete mode 100644 imports/plugins/core/navigation/client/svg/iconPencil.js delete mode 100644 imports/plugins/core/navigation/client/svg/iconTag.js delete mode 100644 imports/plugins/core/navigation/client/svg/iconTimes.js diff --git a/imports/plugins/core/navigation/client/svg/iconChevronDown.js b/imports/plugins/core/navigation/client/svg/iconChevronDown.js deleted file mode 100644 index 34afc3de81b..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconChevronDown.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconChevronDownSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconChevronDown = ( - // credit: https://fontawesome.com/icons/chevron-down?style=solid - - - -); - -export default IconChevronDown; diff --git a/imports/plugins/core/navigation/client/svg/iconChevronRight.js b/imports/plugins/core/navigation/client/svg/iconChevronRight.js deleted file mode 100644 index 40f66515f0e..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconChevronRight.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconChevronRightSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconChevronRight = ( - // credit: https://fontawesome.com/icons/chevron-right?style=solid - - - -); - -export default IconChevronRight; diff --git a/imports/plugins/core/navigation/client/svg/iconEllipsisV.js b/imports/plugins/core/navigation/client/svg/iconEllipsisV.js deleted file mode 100644 index d678cb7981f..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconEllipsisV.js +++ /dev/null @@ -1,31 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconEllipsisVSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconEllipsisV = ( - // credit: https://fontawesome.com/icons/ellipsis-v?style=solid - - - - - -); - -export default IconEllipsisV; diff --git a/imports/plugins/core/navigation/client/svg/iconFile.js b/imports/plugins/core/navigation/client/svg/iconFile.js deleted file mode 100644 index aec0dfca09d..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconFile.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconFileSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconFile = ( - // credit: https://fontawesome.com/icons/file?style=solid - - - -); - -export default IconFile; diff --git a/imports/plugins/core/navigation/client/svg/iconPencil.js b/imports/plugins/core/navigation/client/svg/iconPencil.js deleted file mode 100644 index b760e470fed..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconPencil.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconPencilSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconPencil = ( - // credit: https://fontawesome.com/icons/pencil?style=solid - - - - -); - -export default IconPencil; diff --git a/imports/plugins/core/navigation/client/svg/iconTag.js b/imports/plugins/core/navigation/client/svg/iconTag.js deleted file mode 100644 index eddaff6efdf..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconTag.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconTagSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconTag = ( - // credit: https://fontawesome.com/icons/tag?style=solid - - - -); - -export default IconTag; diff --git a/imports/plugins/core/navigation/client/svg/iconTimes.js b/imports/plugins/core/navigation/client/svg/iconTimes.js deleted file mode 100644 index 8fe643b6eb4..00000000000 --- a/imports/plugins/core/navigation/client/svg/iconTimes.js +++ /dev/null @@ -1,23 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -const IconTimesSvg = styled.svg` - height: 100%; - max-height: 100%; - vertical-align: middle; -`; - -const IconTimes = ( - // credit: https://fontawesome.com/icons/times?style=solid - - - -); - -export default IconTimes; From fe1ebfc7b283546730bf977b458b037912cd7892 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 15:27:08 -0800 Subject: [PATCH 51/62] refactor: remove unused code and unneeded wrapper components Signed-off-by: Mike Murray --- .../client/components/NavigationDashboard.js | 23 +- .../client/components/NavigationItemList.js | 36 +- .../client/components/NavigationItemTabs.js | 55 - .../components/NavigationTreeContainer.js | 19 - .../client/components/NavigationTreeNode.js | 96 -- .../navigation/client/components/TagList.js | 35 - .../navigationDashboardContainer.js | 8 +- .../core/navigation/client/hocs/queries.txt | 114 -- .../client/hocs/withNavigationUIStore.js | 674 +--------- .../core/navigation/client/hocs/withTags.js | 65 - .../plugins/core/navigation/client/index.js | 1 - .../core/navigation/client/styles.less | 10 - .../navigation/client/utils/treeDataUtils.js | 1198 ----------------- 13 files changed, 25 insertions(+), 2309 deletions(-) delete mode 100644 imports/plugins/core/navigation/client/components/NavigationItemTabs.js delete mode 100644 imports/plugins/core/navigation/client/components/NavigationTreeNode.js delete mode 100644 imports/plugins/core/navigation/client/components/TagList.js delete mode 100644 imports/plugins/core/navigation/client/hocs/queries.txt delete mode 100644 imports/plugins/core/navigation/client/hocs/withTags.js delete mode 100644 imports/plugins/core/navigation/client/styles.less delete mode 100644 imports/plugins/core/navigation/client/utils/treeDataUtils.js diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js index a41d5cd514e..52a7741d2b1 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard.js @@ -12,7 +12,7 @@ import HTML5Backend from "react-dnd-html5-backend"; import { DragDropContext } from "react-dnd"; import NavigationItemForm from "./NavigationItemForm"; import NavigationTreeContainer from "./NavigationTreeContainer"; -import NavigationItemTabs from "./NavigationItemTabs"; +import NavigationItemList from "./NavigationItemList"; const styles = (theme) => ({ root: { @@ -46,12 +46,8 @@ class NavigationDashboard extends Component { createNavigationItem: PropTypes.func, deleteNavigationItem: PropTypes.func, navigationItems: PropTypes.array, - navigationTreeRows: PropTypes.array, - onDragHover: PropTypes.func, onSetSortableNavigationTree: PropTypes.func, - onToggleChildrenVisibility: PropTypes.func, sortableNavigationTree: PropTypes.arrayOf(PropTypes.object), - tags: PropTypes.array, uiState: PropTypes.shape({ isLeftDrawerOpen: PropTypes.bool }), @@ -98,12 +94,8 @@ class NavigationDashboard extends Component { createNavigationItem, deleteNavigationItem, navigationItems, - navigationTreeRows, - onDragHover, onSetSortableNavigationTree, - onToggleChildrenVisibility, sortableNavigationTree, - tags, uiState, updateNavigationItem, updateNavigationTree @@ -112,8 +104,7 @@ class NavigationDashboard extends Component { const { isModalOpen, modalMode, - navigationItem, - overNavigationItemId + navigationItem } = this.state; const toolbarClassName = classnames({ @@ -132,25 +123,17 @@ class NavigationDashboard extends Component { - diff --git a/imports/plugins/core/navigation/client/components/NavigationItemList.js b/imports/plugins/core/navigation/client/components/NavigationItemList.js index e5792c756be..c75a3610d09 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemList.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemList.js @@ -1,24 +1,24 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; -import styled from "styled-components"; +import withStyles from "@material-ui/core/styles/withStyles"; import Button from "@material-ui/core/Button"; import NavigationItemCard from "./NavigationItemCard"; -const Wrapper = styled.div` - padding: 20px; -`; -const LinkContainer = styled.div` - margin: 20px 0px; - text-align: right; - - .add-nav-item-link { - font-weight: 700; +const styles = (theme) => ({ + root: { + flexGrow: 1, + padding: theme.spacing.unit * 2 + }, + header: { + marginBottom: theme.spacing.unit * 2, + textAlign: "right" } -`; +}); -class PagesList extends Component { +class NavigationItemList extends Component { static propTypes = { + classes: PropTypes.object, navigationItems: PropTypes.array, onClickAddNavigationItem: PropTypes.func, onClickUpdateNavigationItem: PropTypes.func, @@ -44,16 +44,16 @@ class PagesList extends Component { } render() { - const { onClickAddNavigationItem } = this.props; + const { classes, onClickAddNavigationItem } = this.props; return ( - - +
+
- +
{this.renderNavigationItems()} - +
); } } -export default PagesList; +export default withStyles(styles, { name: "RuiNavigationItemList" })(NavigationItemList); diff --git a/imports/plugins/core/navigation/client/components/NavigationItemTabs.js b/imports/plugins/core/navigation/client/components/NavigationItemTabs.js deleted file mode 100644 index e37e48839e5..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationItemTabs.js +++ /dev/null @@ -1,55 +0,0 @@ -import React from "react"; -import PropTypes from "prop-types"; -import { withStyles } from "@material-ui/core/styles"; -import Divider from "@material-ui/core/Divider"; -import Tabs from "@material-ui/core/Tabs"; -import Tab from "@material-ui/core/Tab"; -import NavigationItemList from "./NavigationItemList"; -import TagList from "./TagList"; - -const styles = () => ({ - root: { - flexGrow: 1 - }, - tabRoot: { - height: "60px", - marginTop: "22px" - } -}); - -class NavigationItemTabs extends React.Component { - static propTypes = { - classes: PropTypes.object, - navigationItems: PropTypes.array, - onClickAddNavigationItem: PropTypes.func, - onClickUpdateNavigationItem: PropTypes.func, - onSetDraggingNavigationItemId: PropTypes.func, - updateNavigationItem: PropTypes.func - } - - render() { - const { classes } = this.props; - - const { - navigationItems, - onClickAddNavigationItem, - onClickUpdateNavigationItem, - onSetDraggingNavigationItemId, - updateNavigationItem - } = this.props; - - return ( -
- -
- ); - } -} - -export default withStyles(styles)(NavigationItemTabs); diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js index 84776aff1d1..9091eb3ffeb 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js @@ -9,7 +9,6 @@ import CloseIcon from "mdi-material-ui/Close"; import { SortableTreeWithoutDndContext as SortableTree, removeNodeAtPath } from "react-sortable-tree"; import "react-sortable-tree/style.css"; import ConfirmDialog from "/imports/client/ui/components/ConfirmDialog"; -import NavigationTreeNode from "./NavigationTreeNode"; import SortableTheme from "./SortableTheme"; const ContentWrapper = styled.div` @@ -73,24 +72,6 @@ class NavigationTreeContainer extends Component { }; } - renderRows() { - const { navigationTreeRows, onClickUpdateNavigationItem, onDragHover, onSetOverNavigationItemId, onToggleChildrenVisibility } = this.props; - let rows = null; - if (navigationTreeRows) { - rows = navigationTreeRows.map((row, index) => ( - - )); - } - return rows; - } - render() { const { classes, onSetSortableNavigationTree, sortableNavigationTree } = this.props; return ( diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeNode.js b/imports/plugins/core/navigation/client/components/NavigationTreeNode.js deleted file mode 100644 index d62dd0fc1b5..00000000000 --- a/imports/plugins/core/navigation/client/components/NavigationTreeNode.js +++ /dev/null @@ -1,96 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import { findDOMNode } from "react-dom"; -import { compose } from "recompose"; -import Grid from "@material-ui/core/Grid"; -import { DropTarget } from "react-dnd"; -import NavigationItemCard from "./NavigationItemCard"; - -const navigationTreeRowTarget = { - drop(props) { - console.log(props); - }, - hover(props, monitor, component) { - // find the middle of things - const { onDragHover, row: { treeIndex: targetTreeIndex } } = props; - const hoverBoundingRect = findDOMNode(component).getBoundingClientRect(); - const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2; - const rowLength = hoverBoundingRect.right - hoverBoundingRect.left; - const clientOffset = monitor.getClientOffset(); - const hoverClientX = clientOffset.x - hoverBoundingRect.left; - const hoverClientY = clientOffset.y - hoverBoundingRect.top; - - const treeIndex = hoverClientY < hoverMiddleY ? targetTreeIndex : targetTreeIndex + 1; - let depth = 2; - if (hoverClientX < 0.08 * rowLength) { - depth = 0; - } else if (hoverClientX < 0.17 * rowLength) { - depth = 1; - } - const draggedNode = monitor.getItem().row.node; - onDragHover({ depth, draggedNode, treeIndex }); - } -}; - -/** - * @name targetCollect - * @summary a collecting function to connect nodes to the DnD backend - * @param {Object} connect - an instance of DropTargetConnector - * @param {Object} monitor - an instance of DropTargetMonitor - * @returns {Object} props to be injected into the component - */ -function targetCollect(connect, monitor) { - return { - connectDropTarget: connect.dropTarget(), - isOver: monitor.isOver() - }; -} - -class NavigationTreeNode extends Component { - static propTypes = { - connectDropTarget: PropTypes.func, - onClickUpdateNavigationItem: PropTypes.func, - onDragHover: PropTypes.func, - onSetOverNavigationItemId: PropTypes.func, - onToggleChildrenVisibility: PropTypes.func, - row: PropTypes.object - } - - renderNavigationItemCard(row) { - const { onClickUpdateNavigationItem, onSetOverNavigationItemId, onToggleChildrenVisibility } = this.props; - return ( - - ); - } - - render() { - const { connectDropTarget, row } = this.props; - const { path } = row; - const offset = path.length - 1; - const offsets = path.map((item, index) => { - if (index === 0) { - return null; - } - return (); - }); - - const toRender = ( - - {offsets} - - {this.renderNavigationItemCard(row)} - - - ); - - return connectDropTarget(
{toRender}
); - } -} - -export default compose(DropTarget("CARD", navigationTreeRowTarget, targetCollect))(NavigationTreeNode); diff --git a/imports/plugins/core/navigation/client/components/TagList.js b/imports/plugins/core/navigation/client/components/TagList.js deleted file mode 100644 index 8a830fb42f1..00000000000 --- a/imports/plugins/core/navigation/client/components/TagList.js +++ /dev/null @@ -1,35 +0,0 @@ -import React, { Component } from "react"; -import PropTypes from "prop-types"; -import styled from "styled-components"; -import NavigationItemCard from "./NavigationItemCard"; - -const Wrapper = styled.div` - padding: 80px 20px; -`; - -class TagsList extends Component { - static propTypes = { - tags: PropTypes.arrayOf(PropTypes.object) - } - - renderTags() { - const { tags } = this.props; - if (tags) { - return tags.map((tag) => { - const row = { node: { tag: tag.node } }; - return ; - }); - } - return null; - } - - render() { - return ( - - {this.renderTags()} - - ); - } -} - -export default TagsList; diff --git a/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js b/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js index 4f77edbf846..3c12b508ee7 100644 --- a/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js +++ b/imports/plugins/core/navigation/client/containers/navigationDashboardContainer.js @@ -8,8 +8,6 @@ import withDefaultNavigationTree from "../hocs/withDefaultNavigationTree"; import withNavigationItems from "../hocs/withNavigationItems"; import withDefaultNavigationTreeId from "../hocs/withDefaultNavigationTreeId"; import withNavigationUIStore from "../hocs/withNavigationUIStore"; -import withTags from "../hocs/withTags"; - registerComponent("NavigationDashboard", NavigationDashboard, [ withNavigationUIStore, @@ -18,8 +16,7 @@ registerComponent("NavigationDashboard", NavigationDashboard, [ withDefaultNavigationTree, withNavigationItems, withUpdateNavigationItem, - withUpdateNavigationTree, - withTags + withUpdateNavigationTree ]); export default compose( @@ -29,6 +26,5 @@ export default compose( withDefaultNavigationTree, withNavigationItems, withUpdateNavigationItem, - withUpdateNavigationTree, - withTags + withUpdateNavigationTree )(NavigationDashboard); diff --git a/imports/plugins/core/navigation/client/hocs/queries.txt b/imports/plugins/core/navigation/client/hocs/queries.txt deleted file mode 100644 index 0534fed739a..00000000000 --- a/imports/plugins/core/navigation/client/hocs/queries.txt +++ /dev/null @@ -1,114 +0,0 @@ -mutation createNavigationItem($input: CreateNavigationItemInput!) { - createNavigationItem(input: $input) { - navigationItem { - _id - } - } -} - -{ - "input": { - "navigationItem": { - "draftData": { - "content": [ - { - "language": "en", - "value": "Blog" - } - ], - "url": "/blog", - "isUrlRelative": true - } - } - } -} - -query NavigationItemsByShopId($shopId: ID!){ - navigationItemsByShopId(shopId: $shopId) { - edges { - cursor - node { - _id - data { - content { - language - value - } - } - } - } - } -} - -{ - "shopId": "cmVhY3Rpb24vc2hvcDpKOEJocTN1VHRkZ3daeDNyeg==" -} - -mutation UpdateNavigationTree($input: UpdateNavigationTreeInput!) { - updateNavigationTree(input: $input) { - navigationTree { - _id - } - } -} - -========== PRIMARY SHOP - -{ - "data": { - "primaryShop": { - "_id": "cmVhY3Rpb24vc2hvcDpKOEJocTN1VHRkZ3daeDNyeg==", - "defaultNavigationTreeId": "cmVhY3Rpb24vbmF2aWdhdGlvblRyZWU6bktpNTZSY2hwdEx6ZDdSc1g=" - } - } -} - -========== NAVIGATION ITEMS - -{ - "data": { - "navigationItemsByShopId": { - "edges": [ - { - "cursor": "UVBTQkVTc2R2MkpHR1Roemc=", - "node": { - "_id": "cmVhY3Rpb24vbmF2aWdhdGlvbkl0ZW06UVBTQkVTc2R2MkpHR1Roemc=", - "data": { - "content": null - } - } - }, - { - "cursor": "ZHprNjlTaG1BbWg2V1pOQUw=", - "node": { - "_id": "cmVhY3Rpb24vbmF2aWdhdGlvbkl0ZW06ZHprNjlTaG1BbWg2V1pOQUw=", - "data": { - "content": [ - { - "language": "en", - "value": "Home" - } - ] - } - } - } - ] - } - } -} - -{ - "input": { - "_id": "cmVhY3Rpb24vbmF2aWdhdGlvblRyZWU6bktpNTZSY2hwdEx6ZDdSc1g=", - "navigationTree": { - "name":"MainNav", - "draftItems": [ - { - "navigationItemId": "cmVhY3Rpb24vbmF2aWdhdGlvbkl0ZW06ZHprNjlTaG1BbWg2V1pOQUw=", - "items": - } - - ] - } - } -} \ No newline at end of file diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js index 652bb3e7f81..38ee3f95d9c 100644 --- a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -4,596 +4,12 @@ export default (Component) => ( class WithNavigationUIStore extends React.Component { state = { navigationItems: [], + sortableNavigationTree: [], draggingNavigationTree: null, navigationTree: [], - tags: [], - targetDepth: 0, - targetTreeIndex: -1, dragging: false } - getNodeDataAtTreeIndexOrNextIndex({ - targetIndex, - node, - currentIndex, - path = [], - lowerSiblingCounts = [], - ignoreCollapsed = true, - isPseudoRoot = false - }) { - // The pseudo-root is not considered in the path - const selfPath = !isPseudoRoot - ? [...path, currentIndex] - : []; - - // Return target node when found - if (currentIndex === targetIndex) { - return { - node, - lowerSiblingCounts, - path: selfPath - }; - } - - // Add one and continue for nodes with no children or hidden children - if (!node.items || (ignoreCollapsed && node.expanded !== true)) { - return { nextIndex: currentIndex + 1 }; - } - - // Iterate over each child and their descendants and return the - // target node if childIndex reaches the targetIndex - let childIndex = currentIndex + 1; - const childCount = node.items.length; - for (let i = 0; i < childCount; i += 1) { - const result = this.getNodeDataAtTreeIndexOrNextIndex({ - ignoreCollapsed, - targetIndex, - node: node.items[i], - currentIndex: childIndex, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath - }); - - if (result.node) { - return result; - } - - childIndex = result.nextIndex; - } - - // If the target node is not found, return the farthest traversed index - return { nextIndex: childIndex }; - } - - getDescendantCount({ node, ignoreCollapsed = true }) { - return ( - this.getNodeDataAtTreeIndexOrNextIndex({ - ignoreCollapsed, - node, - currentIndex: 0, - targetIndex: -1 - }).nextIndex - 1 - ); - } - - changeNodeAtPath({ navigationTree, path, newNode, ignoreCollapsed = true }) { - const RESULT_MISS = "RESULT_MISS"; - const traverse = ({ - isPseudoRoot = false, - node, - currentTreeIndex, - pathIndex - }) => { - if ( - !isPseudoRoot && - currentTreeIndex !== path[pathIndex] - ) { - return RESULT_MISS; - } - - if (pathIndex >= path.length - 1) { - // If this is the final location in the path, return its changed form - return typeof newNode === "function" - ? newNode({ node, treeIndex: currentTreeIndex }) - : newNode; - } - - if (!node.items) { - // If this node is part of the path, but has no children, return the unchanged node - throw new Error("Path referenced children of node with no children."); - } - - let nextTreeIndex = currentTreeIndex + 1; - for (let i = 0; i < node.items.length; i += 1) { - const result = traverse({ - node: node.items[i], - currentTreeIndex: nextTreeIndex, - pathIndex: pathIndex + 1 - }); - - // If the result went down the correct path - if (result !== RESULT_MISS) { - if (result) { - // If the result was truthy (in this case, an object), - // pass it to the next level of recursion up - return { - ...node, - items: [ - ...node.items.slice(0, i), - result, - ...node.items.slice(i + 1) - ] - }; - } - // If the result was falsy (returned from the newNode function), then - // delete the node from the array. - return { - ...node, - items: [ - ...node.items.slice(0, i), - ...node.items.slice(i + 1) - ] - }; - } - - nextTreeIndex += 1 + this.getDescendantCount({ node: node.items[i], ignoreCollapsed }); - } - - return RESULT_MISS; - }; - - // Use a pseudo-root node in the beginning traversal - const result = traverse({ - node: { items: navigationTree }, - currentTreeIndex: -1, - pathIndex: -1, - isPseudoRoot: true - }); - - return result.items; - } - - walkDescendants = ({ - callback, - ignoreCollapsed, - isPseudoRoot = false, - node, - parentNode = null, - currentIndex, - path = [], - lowerSiblingCounts = [] - }) => { - // The pseudo-root is not considered in the path - - const selfPath = isPseudoRoot - ? [] - : [...path, currentIndex]; - const selfInfo = isPseudoRoot - ? null - : { - node, - parentNode, - path: selfPath, - lowerSiblingCounts, - treeIndex: currentIndex - }; - - if (!isPseudoRoot) { - const callbackResult = callback(selfInfo); - - // Cut walk short if the callback returned false - if (callbackResult === false) { - return false; - } - } - - // Return self on nodes with no children or hidden children - if ( - !node.items || - (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return currentIndex; - } - - // Get all descendants - let childIndex = currentIndex; - const childCount = node.items.length; - if (typeof node.items !== "function") { - for (let i = 0; i < childCount; i += 1) { - childIndex = this.walkDescendants({ - callback, - ignoreCollapsed, - node: node.items[i], - parentNode: isPseudoRoot ? null : node, - currentIndex: childIndex + 1, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath - }); - // Cut walk short if the callback returned false - if (childIndex === false) { - return false; - } - } - } - return childIndex; - } - - walk = ({ navigationTree, ignoreCollapsed = true, callback }) => { - if (!navigationTree || navigationTree.length < 1) { - return; - } - - this.walkDescendants({ - callback, - ignoreCollapsed, - isPseudoRoot: true, - node: { items: navigationTree }, - currentIndex: -1, - path: [], - lowerSiblingCounts: [] - }); - } - - addNodeAtDepthAndIndex = ({ - targetDepth, - targetTreeIndex, - newNode, - ignoreCollapsed, - expandParent, - isPseudoRoot = false, - isLastChild, - node, - currentIndex, - currentDepth, - path = [] - }) => { - const selfPath = () => (isPseudoRoot ? [] : [...path, currentIndex]); - - // If the current position is the only possible place to add, add it - if ( - currentIndex >= targetTreeIndex - 1 || - (isLastChild && !(node.items && node.items.length)) - ) { - if (typeof node.items === "function") { - throw new Error("Cannot add to children defined by a function"); - } else { - const extraNodeProps = expandParent ? { expanded: true } : {}; - const nextNode = { - ...node, - - ...extraNodeProps, - items: node.items ? [newNode, ...node.items] : [newNode] - }; - - return { - node: nextNode, - nextIndex: currentIndex + 2, - insertedTreeIndex: currentIndex + 1, - parentPath: selfPath(nextNode), - parentNode: isPseudoRoot ? null : nextNode - }; - } - } - - // If this is the target depth for the insertion, - // i.e., where the newNode can be added to the current node's children - if (currentDepth >= targetDepth - 1) { - // Skip over nodes with no children or hidden children - if ( - !node.items || - typeof node.items === "function" || - (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return { node, nextIndex: currentIndex + 1 }; - } - - // Scan over the children to see if there's a place among them that fulfills - // the minimumTreeIndex requirement - let childIndex = currentIndex + 1; - let insertedTreeIndex = null; - let insertIndex = null; - for (let i = 0; i < node.items.length; i += 1) { - // If a valid location is found, mark it as the insertion location and - // break out of the loop - if (childIndex >= targetTreeIndex) { - insertedTreeIndex = childIndex; - insertIndex = i; - break; - } - - // Increment the index by the child itself plus the number of descendants it has - childIndex += - 1 + this.getDescendantCount({ node: node.items[i], ignoreCollapsed }); - } - - // If no valid indices to add the node were found - if (insertIndex === null) { - // If the last position in this node's children is less than the minimum index - // and there are more children on the level of this node, return without insertion - if (childIndex < targetTreeIndex && !isLastChild) { - return { node, nextIndex: childIndex }; - } - - // Use the last position in the children array to insert the newNode - insertedTreeIndex = childIndex; - insertIndex = node.items.length; - } - - // Insert the newNode at the insertIndex - const nextNode = { - ...node, - items: [ - ...node.items.slice(0, insertIndex), - newNode, - ...node.items.slice(insertIndex) - ] - }; - - // Return node with successful insert result - return { - node: nextNode, - nextIndex: childIndex, - insertedTreeIndex, - parentPath: selfPath(nextNode), - parentNode: isPseudoRoot ? null : nextNode - }; - } - - // Skip over nodes with no children or hidden children - if ( - !node.items || - typeof node.items === "function" || - (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return { node, nextIndex: currentIndex + 1 }; - } - - // Get all descendants - let insertedTreeIndex = null; - let pathFragment = null; - let parentNode = null; - let childIndex = currentIndex + 1; - let newChildren = node.items; - if (typeof newChildren !== "function") { - newChildren = newChildren.map((child, i) => { - if (insertedTreeIndex !== null) { - return child; - } - - const mapResult = this.addNodeAtDepthAndIndex({ - targetDepth, - targetTreeIndex, - newNode, - ignoreCollapsed, - expandParent, - isLastChild: isLastChild && i === newChildren.length - 1, - node: child, - currentIndex: childIndex, - currentDepth: currentDepth + 1, - path: [] // Cannot determine the parent path until the children have been processed - }); - - if ("insertedTreeIndex" in mapResult) { - ({ - insertedTreeIndex, - parentNode, - parentPath: pathFragment - } = mapResult); - } - - childIndex = mapResult.nextIndex; - - return mapResult.node; - }); - } - - const nextNode = { ...node, children: newChildren }; - const result = { - node: nextNode, - nextIndex: childIndex - }; - - if (insertedTreeIndex !== null) { - result.insertedTreeIndex = insertedTreeIndex; - result.parentPath = [...selfPath(nextNode), ...pathFragment]; - result.parentNode = parentNode; - } - - return result; - } - - insertNode = ({ - navigationTree, - targetDepth, - targetTreeIndex, - newNode, - ignoreCollapsed = true, - expandParent = false - }) => { - if (!navigationTree && targetDepth === 0) { - return { - navigationTree: [newNode], - treeIndex: 0, - path: [0], - parentNode: null - }; - } - const insertResult = this.addNodeAtDepthAndIndex({ - targetDepth, - targetTreeIndex, - newNode, - ignoreCollapsed, - expandParent, - isPseudoRoot: true, - isLastChild: true, - node: { items: navigationTree }, - currentIndex: -1, - currentDepth: -1 - }); - - if (!("insertedTreeIndex" in insertResult)) { - throw new Error("No suitable position found to insert."); - } - - const treeIndex = insertResult.insertedTreeIndex; - return { - navigationTree: insertResult.node.items, - treeIndex, - path: [ - ...insertResult.parentPath, - treeIndex - ], - parentNode: insertResult.parentNode - }; - } - - find = ({ - searchQuery, - searchMethod, - searchFocusOffset, - expandAllMatchPaths = false, - expandFocusMatchPaths = true - }) => { - let matchCount = 0; - const trav = ({ isPseudoRoot = false, node, currentIndex, path = [] }) => { - let matches = []; - let isSelfMatch = false; - let hasFocusMatch = false; - // The pseudo-root is not considered in the path - const selfPath = isPseudoRoot - ? [] - : [...path, currentIndex]; - const extraInfo = isPseudoRoot - ? null - : { - path: selfPath, - treeIndex: currentIndex - }; - - // Nodes with with children that aren't lazy - const hasChildren = - node.items && - typeof node.items !== "function" && - node.items.length > 0; - - // Examine the current node to see if it is a match - if (!isPseudoRoot && searchMethod({ ...extraInfo, node, searchQuery })) { - if (matchCount === searchFocusOffset) { - hasFocusMatch = true; - } - - // Keep track of the number of matching nodes, so we know when the searchFocusOffset - // is reached - matchCount += 1; - - // We cannot add this node to the matches right away, as it may be changed - // during the search of the descendants. The entire node is used in - // comparisons between nodes inside the `matches` and `treeData` results - // of this method (`find`) - isSelfMatch = true; - } - - let childIndex = currentIndex; - const newNode = { ...node }; - if (hasChildren) { - // Get all descendants - newNode.items = newNode.items.map((child) => { - const mapResult = trav({ - node: child, - currentIndex: childIndex + 1, - path: selfPath - }); - - // Ignore hidden nodes by only advancing the index counter to the returned treeIndex - // if the child is expanded. - // - // The child could have been expanded from the start, - // or expanded due to a matching node being found in its descendants - if (mapResult.node.expanded) { - childIndex = mapResult.treeIndex; - } else { - childIndex += 1; - } - - if (mapResult.matches.length > 0 || mapResult.hasFocusMatch) { - matches = [...matches, ...mapResult.matches]; - if (mapResult.hasFocusMatch) { - hasFocusMatch = true; - } - - // Expand the current node if it has descendants matching the search - // and the settings are set to do so. - if ( - (expandAllMatchPaths && mapResult.matches.length > 0) || - ((expandAllMatchPaths || expandFocusMatchPaths) && - mapResult.hasFocusMatch) - ) { - newNode.expanded = true; - } - } - - return mapResult.node; - }); - } - - // Cannot assign a treeIndex to hidden nodes - if (!isPseudoRoot && !newNode.expanded) { - matches = matches.map((match) => ({ - ...match, - treeIndex: null - })); - } - - // Add this node to the matches if it fits the search criteria. - // This is performed at the last minute so newNode can be sent in its final form. - if (isSelfMatch) { - matches = [{ ...extraInfo, node: newNode }, ...matches]; - } - - return { - node: matches.length > 0 ? newNode : node, - matches, - hasFocusMatch, - treeIndex: childIndex - }; - }; - - const { navigationTree } = this.state; - const result = trav({ - node: { items: navigationTree }, - isPseudoRoot: true, - currentIndex: -1 - }); - - return { - matches: result.matches, - navigationTree: result.node.items - }; - } - - getFlatDataFromNavigationTree = (navigationTree, ignoreCollapsed) => { - if (!navigationTree || navigationTree.length < 1) { - return []; - } - - const flattened = []; - this.walk({ - navigationTree, - ignoreCollapsed, - callback: (nodeInfo) => { - flattened.push(nodeInfo); - } - }); - - return flattened; - } - - getRows = (navigationTree) => this.getFlatDataFromNavigationTree(navigationTree, true); - - handleSetTags = (tags) => { - this.setState({ tags }); - } - handleSetNavigationItems = (navigationItems) => { this.setState({ navigationItems }); } @@ -604,80 +20,6 @@ export default (Component) => ( })); } - handleDragHover = ({ - depth: targetDepth, - draggedNode, - treeIndex: targetTreeIndex - }) => { - const { targetDepth: currentTargetDepth, targetTreeIndex: currentTargetTreeIndex } = this.state; - if ( - targetDepth === currentTargetDepth && - targetTreeIndex === currentTargetTreeIndex - ) { - return; - } - - this.setState(({ navigationTree, draggingNavigationTree }) => { - const addedResult = this.insertNode({ - navigationTree, - newNode: draggedNode, - targetDepth, - targetTreeIndex, - expandParent: true - }); - - const rows = this.getRows(addedResult.navigationTree); - const expandedParentPath = rows[addedResult.treeIndex].path; - - return { - draggedNode, - targetDepth, - targetTreeIndex, - draggingNavigationTree: this.changeNodeAtPath({ - navigationTree: draggingNavigationTree, - path: expandedParentPath.slice(0, -1), - newNode: ({ node }) => ({ ...node, expanded: true }) - }), - // reset the scroll focus so it doesn't jump back - // to a search result while dragging - searchFocusTreeIndex: null, - dragging: true - }; - }); - } - - handleToggleChildrenVisibility = (path) => { - const { navigationTree: currentNavigationTree } = this.state; - const navigationTree = this.changeNodeAtPath({ - navigationTree: currentNavigationTree, - path, - newNode: ({ node }) => ({ ...node, expanded: !node.expanded }) - }); - - this.setState({ navigationTree }); - } - - handleUpdateNavigationItem = (navigationItem) => { - this.setState((prevState) => { - const { navigationTree: prevNavigationTree } = prevState; - const result = this.find({ - searchMethod: ({ node, searchQuery }) => node.navigationItem._id === searchQuery, - searchQuery: navigationItem._id, - searchFocusOffset: 0 - }); - const navigationTree = result.matches.reduce((newNavigationTree, row) => { - const { path } = row; - return this.changeNodeAtPath({ - navigationTree: newNavigationTree, - path, - newNode: ({ node }) => ({ ...node, navigationItem }), - ignoreCollapsed: false - }); - }, prevNavigationTree); - return { navigationTree }; - }); - } - handleSetNavigationTree = (navigationTree) => { const sortableNavigationTree = this.navigationTreeToSortable(navigationTree); this.setState({ navigationTree, sortableNavigationTree }); @@ -713,13 +55,7 @@ export default (Component) => ( } render() { - const { navigationItems, navigationTree, dragging, draggingNavigationTree, sortableNavigationTree, tags } = this.state; - let currentNavigationTree = navigationTree; - if (dragging) { - currentNavigationTree = draggingNavigationTree; - } - // console.log(this.state); - const navigationTreeRows = this.getFlatDataFromNavigationTree(currentNavigationTree, true); + const { navigationItems, sortableNavigationTree } = this.state; return ( ( sortableNavigationTree={sortableNavigationTree} onSetSortableNavigationTree={this.handleSetSortableNavigationTree} navigationItems={navigationItems} - navigationTreeRows={navigationTreeRows} - onDragHover={this.handleDragHover} - tags={tags} onAddNavigationItem={this.handleAddNavigationItem} onSetNavigationTree={this.handleSetNavigationTree} - onSetTags={this.handleSetTags} onSetNavigationItems={this.handleSetNavigationItems} - onToggleChildrenVisibility={this.handleToggleChildrenVisibility} - onUpdateNavigationItem={this.handleUpdateNavigationItem} /> ); } diff --git a/imports/plugins/core/navigation/client/hocs/withTags.js b/imports/plugins/core/navigation/client/hocs/withTags.js deleted file mode 100644 index 1a0b5e5711f..00000000000 --- a/imports/plugins/core/navigation/client/hocs/withTags.js +++ /dev/null @@ -1,65 +0,0 @@ - -import React from "react"; -import PropTypes from "prop-types"; -import gql from "graphql-tag"; -import { Query } from "react-apollo"; - -const tagsQuery = gql` - query tagsQuery($shopId: ID!, $cursor: ConnectionCursor) { - tags(shopId: $shopId, first: 200, after: $cursor) { - pageInfo { - endCursor - startCursor - hasNextPage - } - edges { - cursor - node { - _id - name - } - } - } - } -`; - -export default (Component) => ( - class WithTags extends React.Component { - static propTypes = { - onSetTags: PropTypes.func, - shopId: PropTypes.string.isRequired - } - - static defaultProps = { - shopId: "" - } - - handleSetTags = (data) => { - const { tags: { edges } } = data; - this.props.onSetTags(edges); - } - - render() { - const props = { ...this.props }; - const { shopId } = this.props; - if (!shopId) { - return ; - } - - const variables = { - shopId - }; - - return ( - - {() => ()} - - ); - } - } -); diff --git a/imports/plugins/core/navigation/client/index.js b/imports/plugins/core/navigation/client/index.js index 1d6cd05238a..7a26256202c 100644 --- a/imports/plugins/core/navigation/client/index.js +++ b/imports/plugins/core/navigation/client/index.js @@ -7,7 +7,6 @@ import NavigationDashboard from "./containers/navigationDashboardContainer"; import "./containers"; import "./templates"; -import "./styles.less"; registerOperatorRoute({ isNavigationLink: true, diff --git a/imports/plugins/core/navigation/client/styles.less b/imports/plugins/core/navigation/client/styles.less deleted file mode 100644 index f33324d8fc0..00000000000 --- a/imports/plugins/core/navigation/client/styles.less +++ /dev/null @@ -1,10 +0,0 @@ -.nav-items-header-container { - border-bottom: 2px solid rgb(204, 204, 204); - min-height: 60px; - padding-top: 10px; - padding-left: 20px; -} - -.nav-items-header-container-right { - border-left: 2px solid rgb(204, 204, 204); -} diff --git a/imports/plugins/core/navigation/client/utils/treeDataUtils.js b/imports/plugins/core/navigation/client/utils/treeDataUtils.js deleted file mode 100644 index 55f7b9d61d9..00000000000 --- a/imports/plugins/core/navigation/client/utils/treeDataUtils.js +++ /dev/null @@ -1,1198 +0,0 @@ -/** - * Performs a depth-first traversal over all of the node descendants, - * incrementing currentIndex by 1 for each - */ -function getNodeDataAtTreeIndexOrNextIndex({ - targetIndex, - node, - currentIndex, - getNodeKey, - path = [], - lowerSiblingCounts = [], - ignoreCollapsed = true, - isPseudoRoot = false -}) { - // The pseudo-root is not considered in the path - const selfPath = !isPseudoRoot - ? [...path, getNodeKey({ node, treeIndex: currentIndex })] - : []; - - // Return target node when found - if (currentIndex === targetIndex) { - return { - node, - lowerSiblingCounts, - path: selfPath - }; - } - - // Add one and continue for nodes with no children or hidden children - if (!node.children || (ignoreCollapsed && node.expanded !== true)) { - return { nextIndex: currentIndex + 1 }; - } - - // Iterate over each child and their descendants and return the - // target node if childIndex reaches the targetIndex - let childIndex = currentIndex + 1; - const childCount = node.children.length; - for (let i = 0; i < childCount; i += 1) { - const result = getNodeDataAtTreeIndexOrNextIndex({ - ignoreCollapsed, - getNodeKey, - targetIndex, - node: node.children[i], - currentIndex: childIndex, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath - }); - - if (result.node) { - return result; - } - - childIndex = result.nextIndex; - } - - // If the target node is not found, return the farthest traversed index - return { nextIndex: childIndex }; -} - -export function getDescendantCount({ node, ignoreCollapsed = true }) { - return ( - getNodeDataAtTreeIndexOrNextIndex({ - getNodeKey: () => {}, - ignoreCollapsed, - node, - currentIndex: 0, - targetIndex: -1 - }).nextIndex - 1 - ); -} - -/** - * Walk all descendants of the given node, depth-first - * - * @param {Object} args - Function parameters - * @param {function} args.callback - Function to call on each node - * @param {function} args.getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean} args.ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * @param {boolean=} args.isPseudoRoot - If true, this node has no real data, and only serves - * as the parent of all the nodes in the tree - * @param {Object} args.node - A tree node - * @param {Object=} args.parentNode - The parent node of `node` - * @param {number} args.currentIndex - The treeIndex of `node` - * @param {number[]|string[]} args.path - Array of keys leading up to node to be changed - * @param {number[]} args.lowerSiblingCounts - An array containing the count of siblings beneath the - * previous nodes in this path - * - * @return {number|false} nextIndex - Index of the next sibling of `node`, - * or false if the walk should be terminated - */ -function walkDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - isPseudoRoot = false, - node, - parentNode = null, - currentIndex, - path = [], - lowerSiblingCounts = [] -}) { - // The pseudo-root is not considered in the path - const selfPath = isPseudoRoot - ? [] - : [...path, getNodeKey({ node, treeIndex: currentIndex })]; - const selfInfo = isPseudoRoot - ? null - : { - node, - parentNode, - path: selfPath, - lowerSiblingCounts, - treeIndex: currentIndex - }; - - if (!isPseudoRoot) { - const callbackResult = callback(selfInfo); - - // Cut walk short if the callback returned false - if (callbackResult === false) { - return false; - } - } - - // Return self on nodes with no children or hidden children - if ( - !node.children || - (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return currentIndex; - } - - // Get all descendants - let childIndex = currentIndex; - const childCount = node.children.length; - if (typeof node.children !== "function") { - for (let i = 0; i < childCount; i += 1) { - childIndex = walkDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - node: node.children[i], - parentNode: isPseudoRoot ? null : node, - currentIndex: childIndex + 1, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath - }); - - // Cut walk short if the callback returned false - if (childIndex === false) { - return false; - } - } - } - - return childIndex; -} - -/** - * Perform a change on the given node and all its descendants, traversing the tree depth-first - * - * @param {Object} args - Function parameters - * @param {function} args.callback - Function to call on each node - * @param {function} args.getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean} args.ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * @param {boolean=} args.isPseudoRoot - If true, this node has no real data, and only serves - * as the parent of all the nodes in the tree - * @param {Object} args.node - A tree node - * @param {Object=} args.parentNode - The parent node of `node` - * @param {number} args.currentIndex - The treeIndex of `node` - * @param {number[]|string[]} args.path - Array of keys leading up to node to be changed - * @param {number[]} args.lowerSiblingCounts - An array containing the count of siblings beneath the - * previous nodes in this path - * - * @return {number|false} nextIndex - Index of the next sibling of `node`, - * or false if the walk should be terminated - */ -function mapDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - isPseudoRoot = false, - node, - parentNode = null, - currentIndex, - path = [], - lowerSiblingCounts = [] -}) { - const nextNode = { ...node }; - - // The pseudo-root is not considered in the path - const selfPath = isPseudoRoot - ? [] - : [...path, getNodeKey({ node: nextNode, treeIndex: currentIndex })]; - const selfInfo = { - node: nextNode, - parentNode, - path: selfPath, - lowerSiblingCounts, - treeIndex: currentIndex - }; - - // Return self on nodes with no children or hidden children - if ( - !nextNode.children || - (nextNode.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return { - treeIndex: currentIndex, - node: callback(selfInfo) - }; - } - - // Get all descendants - let childIndex = currentIndex; - const childCount = nextNode.children.length; - if (typeof nextNode.children !== "function") { - nextNode.children = nextNode.children.map((child, i) => { - const mapResult = mapDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - node: child, - parentNode: isPseudoRoot ? null : nextNode, - currentIndex: childIndex + 1, - lowerSiblingCounts: [...lowerSiblingCounts, childCount - i - 1], - path: selfPath - }); - childIndex = mapResult.treeIndex; - - return mapResult.node; - }); - } - - return { - node: callback(selfInfo), - treeIndex: childIndex - }; -} - -/** - * Count all the visible (expanded) descendants in the tree data. - * - * @param {!Object[]} treeData - Tree data - * - * @return {number} count - */ -export function getVisibleNodeCount({ treeData }) { - const traverse = (node) => { - if ( - !node.children || - node.expanded !== true || - typeof node.children === "function" - ) { - return 1; - } - - return ( - 1 + - node.children.reduce( - (total, currentNode) => total + traverse(currentNode), - 0 - ) - ); - }; - - return treeData.reduce( - (total, currentNode) => total + traverse(currentNode), - 0 - ); -} - -/** - * Get the th visible node in the tree data. - * - * @param {!Object[]} treeData - Tree data - * @param {!number} targetIndex - The index of the node to search for - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * - * @return {{ - * node: Object, - * path: []string|[]number, - * lowerSiblingCounts: []number - * }|null} node - The node at targetIndex, or null if not found - */ -export function getVisibleNodeInfoAtIndex({ - treeData, - index: targetIndex, - getNodeKey -}) { - if (!treeData || treeData.length < 1) { - return null; - } - - // Call the tree traversal with a pseudo-root node - const result = getNodeDataAtTreeIndexOrNextIndex({ - targetIndex, - getNodeKey, - node: { - children: treeData, - expanded: true - }, - currentIndex: -1, - path: [], - lowerSiblingCounts: [], - isPseudoRoot: true - }); - - if (result.node) { - return result; - } - - return null; -} - -/** - * Walk descendants depth-first and call a callback on each - * - * @param {!Object[]} treeData - Tree data - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {function} callback - Function to call on each node - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return void - */ -export function walk({ - treeData, - getNodeKey, - callback, - ignoreCollapsed = true -}) { - if (!treeData || treeData.length < 1) { - return; - } - - walkDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - isPseudoRoot: true, - node: { children: treeData }, - currentIndex: -1, - path: [], - lowerSiblingCounts: [] - }); -} - -/** - * Perform a depth-first transversal of the descendants and - * make a change to every node in the tree - * - * @param {!Object[]} treeData - Tree data - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {function} callback - Function to call on each node - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return {Object[]} changedTreeData - The changed tree data - */ -export function map({ - treeData, - getNodeKey, - callback, - ignoreCollapsed = true -}) { - if (!treeData || treeData.length < 1) { - return []; - } - - return mapDescendants({ - callback, - getNodeKey, - ignoreCollapsed, - isPseudoRoot: true, - node: { children: treeData }, - currentIndex: -1, - path: [], - lowerSiblingCounts: [] - }).node.children; -} - -/** - * Expand or close every node in the tree - * - * @param {!Object[]} treeData - Tree data - * @param {?boolean} expanded - Whether the node is expanded or not - * - * @return {Object[]} changedTreeData - The changed tree data - */ -export function toggleExpandedForAll({ treeData, expanded = true }) { - return map({ - treeData, - callback: ({ node }) => ({ ...node, expanded }), - getNodeKey: ({ treeIndex }) => treeIndex, - ignoreCollapsed: false - }); -} - -/** - * Replaces node at path with object, or callback-defined object - * - * @param {!Object[]} treeData - * @param {number[]|string[]} path - Array of keys leading up to node to be changed - * @param {function|any} newNode - Node to replace the node at the path with, or a function producing the new node - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return {Object[]} changedTreeData - The changed tree data - */ -export function changeNodeAtPath({ - treeData, - path, - newNode, - getNodeKey, - ignoreCollapsed = true -}) { - const RESULT_MISS = "RESULT_MISS"; - const traverse = ({ - isPseudoRoot = false, - node, - currentTreeIndex, - pathIndex - }) => { - if ( - !isPseudoRoot && - getNodeKey({ node, treeIndex: currentTreeIndex }) !== path[pathIndex] - ) { - return RESULT_MISS; - } - - if (pathIndex >= path.length - 1) { - // If this is the final location in the path, return its changed form - return typeof newNode === "function" - ? newNode({ node, treeIndex: currentTreeIndex }) - : newNode; - } - if (!node.children) { - // If this node is part of the path, but has no children, return the unchanged node - throw new Error("Path referenced children of node with no children."); - } - - let nextTreeIndex = currentTreeIndex + 1; - for (let i = 0; i < node.children.length; i += 1) { - const result = traverse({ - node: node.children[i], - currentTreeIndex: nextTreeIndex, - pathIndex: pathIndex + 1 - }); - - // If the result went down the correct path - if (result !== RESULT_MISS) { - if (result) { - // If the result was truthy (in this case, an object), - // pass it to the next level of recursion up - return { - ...node, - children: [ - ...node.children.slice(0, i), - result, - ...node.children.slice(i + 1) - ] - }; - } - // If the result was falsy (returned from the newNode function), then - // delete the node from the array. - return { - ...node, - children: [ - ...node.children.slice(0, i), - ...node.children.slice(i + 1) - ] - }; - } - - nextTreeIndex += - 1 + getDescendantCount({ node: node.children[i], ignoreCollapsed }); - } - - return RESULT_MISS; - }; - - // Use a pseudo-root node in the beginning traversal - const result = traverse({ - node: { children: treeData }, - currentTreeIndex: -1, - pathIndex: -1, - isPseudoRoot: true - }); - - if (result === RESULT_MISS) { - throw new Error("No node found at the given path."); - } - - return result.children; -} - -/** - * Removes the node at the specified path and returns the resulting treeData. - * - * @param {!Object[]} treeData - * @param {number[]|string[]} path - Array of keys leading up to node to be deleted - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return {Object[]} changedTreeData - The tree data with the node removed - */ -export function removeNodeAtPath({ - treeData, - path, - getNodeKey, - ignoreCollapsed = true -}) { - return changeNodeAtPath({ - treeData, - path, - getNodeKey, - ignoreCollapsed, - newNode: null // Delete the node - }); -} - -/** - * Removes the node at the specified path and returns the resulting treeData. - * - * @param {!Object[]} treeData - * @param {number[]|string[]} path - Array of keys leading up to node to be deleted - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return {Object} result - * @return {Object[]} result.treeData - The tree data with the node removed - * @return {Object} result.node - The node that was removed - * @return {number} result.treeIndex - The previous treeIndex of the removed node - */ -export function removeNode({ - treeData, - path, - getNodeKey, - ignoreCollapsed = true -}) { - let removedNode = null; - let removedTreeIndex = null; - const nextTreeData = changeNodeAtPath({ - treeData, - path, - getNodeKey, - ignoreCollapsed, - newNode: ({ node, treeIndex }) => { - // Store the target node and delete it from the tree - removedNode = node; - removedTreeIndex = treeIndex; - - return null; - } - }); - - return { - treeData: nextTreeData, - node: removedNode, - treeIndex: removedTreeIndex - }; -} - -/** - * Gets the node at the specified path - * - * @param {!Object[]} treeData - * @param {number[]|string[]} path - Array of keys leading up to node to be deleted - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return {Object|null} nodeInfo - The node info at the given path, or null if not found - */ -export function getNodeAtPath({ - treeData, - path, - getNodeKey, - ignoreCollapsed = true -}) { - let foundNodeInfo = null; - - try { - changeNodeAtPath({ - treeData, - path, - getNodeKey, - ignoreCollapsed, - newNode: ({ node, treeIndex }) => { - foundNodeInfo = { node, treeIndex }; - return node; - } - }); - } catch (err) { - // Ignore the error -- the null return will be explanation enough - } - - return foundNodeInfo; -} - -/** - * Adds the node to the specified parent and returns the resulting treeData. - * - * @param {!Object[]} treeData - * @param {!Object} newNode - The node to insert - * @param {number|string} parentKey - The key of the to-be parentNode of the node - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * @param {boolean=} expandParent - If true, expands the parentNode specified by parentPath - * @param {boolean=} addAsFirstChild - If true, adds new node as first child of tree - * - * @return {Object} result - * @return {Object[]} result.treeData - The updated tree data - * @return {number} result.treeIndex - The tree index at which the node was inserted - */ -export function addNodeUnderParent({ - treeData, - newNode, - parentKey = null, - getNodeKey, - ignoreCollapsed = true, - expandParent = false, - addAsFirstChild = false -}) { - if (parentKey === null) { - return { - treeData: [...(treeData || []), newNode], - treeIndex: (treeData || []).length - }; - } - - let insertedTreeIndex = null; - let hasBeenAdded = false; - const changedTreeData = map({ - treeData, - getNodeKey, - ignoreCollapsed, - callback: ({ node, treeIndex, path }) => { - const key = path ? path[path.length - 1] : null; - // Return nodes that are not the parent as-is - if (hasBeenAdded || key !== parentKey) { - return node; - } - hasBeenAdded = true; - - const parentNode = { - ...node - }; - - if (expandParent) { - parentNode.expanded = true; - } - - // If no children exist yet, just add the single newNode - if (!parentNode.children) { - insertedTreeIndex = treeIndex + 1; - return { - ...parentNode, - children: [newNode] - }; - } - - if (typeof parentNode.children === "function") { - throw new Error("Cannot add to children defined by a function"); - } - - let nextTreeIndex = treeIndex + 1; - for (let i = 0; i < parentNode.children.length; i += 1) { - nextTreeIndex += - 1 + - getDescendantCount({ node: parentNode.children[i], ignoreCollapsed }); - } - - insertedTreeIndex = nextTreeIndex; - - const children = addAsFirstChild - ? [newNode, ...parentNode.children] - : [...parentNode.children, newNode]; - - return { - ...parentNode, - children - }; - } - }); - - if (!hasBeenAdded) { - throw new Error("No node found with the given key."); - } - - return { - treeData: changedTreeData, - treeIndex: insertedTreeIndex - }; -} - -function addNodeAtDepthAndIndex({ - targetDepth, - minimumTreeIndex, - newNode, - ignoreCollapsed, - expandParent, - isPseudoRoot = false, - isLastChild, - node, - currentIndex, - currentDepth, - getNodeKey, - path = [] -}) { - const selfPath = (n) => - (isPseudoRoot - ? [] - : [...path, getNodeKey({ node: n, treeIndex: currentIndex })]); - - // If the current position is the only possible place to add, add it - if ( - currentIndex >= minimumTreeIndex - 1 || - (isLastChild && !(node.children && node.children.length)) - ) { - if (typeof node.children === "function") { - throw new Error("Cannot add to children defined by a function"); - } else { - const extraNodeProps = expandParent ? { expanded: true } : {}; - const nextNode = { - ...node, - - ...extraNodeProps, - children: node.children ? [newNode, ...node.children] : [newNode] - }; - - return { - node: nextNode, - nextIndex: currentIndex + 2, - insertedTreeIndex: currentIndex + 1, - parentPath: selfPath(nextNode), - parentNode: isPseudoRoot ? null : nextNode - }; - } - } - - // If this is the target depth for the insertion, - // i.e., where the newNode can be added to the current node's children - if (currentDepth >= targetDepth - 1) { - // Skip over nodes with no children or hidden children - if ( - !node.children || - typeof node.children === "function" || - (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return { node, nextIndex: currentIndex + 1 }; - } - - // Scan over the children to see if there's a place among them that fulfills - // the minimumTreeIndex requirement - let childIndex = currentIndex + 1; - let insertedTreeIndex = null; - let insertIndex = null; - for (let i = 0; i < node.children.length; i += 1) { - // If a valid location is found, mark it as the insertion location and - // break out of the loop - if (childIndex >= minimumTreeIndex) { - insertedTreeIndex = childIndex; - insertIndex = i; - break; - } - - // Increment the index by the child itself plus the number of descendants it has - childIndex += - 1 + getDescendantCount({ node: node.children[i], ignoreCollapsed }); - } - - // If no valid indices to add the node were found - if (insertIndex === null) { - // If the last position in this node's children is less than the minimum index - // and there are more children on the level of this node, return without insertion - if (childIndex < minimumTreeIndex && !isLastChild) { - return { node, nextIndex: childIndex }; - } - - // Use the last position in the children array to insert the newNode - insertedTreeIndex = childIndex; - insertIndex = node.children.length; - } - - // Insert the newNode at the insertIndex - const nextNode = { - ...node, - children: [ - ...node.children.slice(0, insertIndex), - newNode, - ...node.children.slice(insertIndex) - ] - }; - - // Return node with successful insert result - return { - node: nextNode, - nextIndex: childIndex, - insertedTreeIndex, - parentPath: selfPath(nextNode), - parentNode: isPseudoRoot ? null : nextNode - }; - } - - // Skip over nodes with no children or hidden children - if ( - !node.children || - typeof node.children === "function" || - (node.expanded !== true && ignoreCollapsed && !isPseudoRoot) - ) { - return { node, nextIndex: currentIndex + 1 }; - } - - // Get all descendants - let insertedTreeIndex = null; - let pathFragment = null; - let parentNode = null; - let childIndex = currentIndex + 1; - let newChildren = node.children; - if (typeof newChildren !== "function") { - newChildren = newChildren.map((child, i) => { - if (insertedTreeIndex !== null) { - return child; - } - - const mapResult = addNodeAtDepthAndIndex({ - targetDepth, - minimumTreeIndex, - newNode, - ignoreCollapsed, - expandParent, - isLastChild: isLastChild && i === newChildren.length - 1, - node: child, - currentIndex: childIndex, - currentDepth: currentDepth + 1, - getNodeKey, - path: [] // Cannot determine the parent path until the children have been processed - }); - - if ("insertedTreeIndex" in mapResult) { - ({ - insertedTreeIndex, - parentNode, - parentPath: pathFragment - } = mapResult); - } - - childIndex = mapResult.nextIndex; - - return mapResult.node; - }); - } - - const nextNode = { ...node, children: newChildren }; - const result = { - node: nextNode, - nextIndex: childIndex - }; - - if (insertedTreeIndex !== null) { - result.insertedTreeIndex = insertedTreeIndex; - result.parentPath = [...selfPath(nextNode), ...pathFragment]; - result.parentNode = parentNode; - } - - return result; -} - -/** - * Insert a node into the tree at the given depth, after the minimum index - * - * @param {!Object[]} treeData - Tree data - * @param {!number} depth - The depth to insert the node at (the first level of the array being depth 0) - * @param {!number} minimumTreeIndex - The lowest possible treeIndex to insert the node at - * @param {!Object} newNode - The node to insert into the tree - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * @param {boolean=} expandParent - If true, expands the parent of the inserted node - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * - * @return {Object} result - * @return {Object[]} result.treeData - The tree data with the node added - * @return {number} result.treeIndex - The tree index at which the node was inserted - * @return {number[]|string[]} result.path - Array of keys leading to the node location after insertion - * @return {Object} result.parentNode - The parent node of the inserted node - */ -export function insertNode({ - treeData, - depth: targetDepth, - minimumTreeIndex, - newNode, - getNodeKey = () => {}, - ignoreCollapsed = true, - expandParent = false -}) { - if (!treeData && targetDepth === 0) { - return { - treeData: [newNode], - treeIndex: 0, - path: [getNodeKey({ node: newNode, treeIndex: 0 })], - parentNode: null - }; - } - - const insertResult = addNodeAtDepthAndIndex({ - targetDepth, - minimumTreeIndex, - newNode, - ignoreCollapsed, - expandParent, - getNodeKey, - isPseudoRoot: true, - isLastChild: true, - node: { children: treeData }, - currentIndex: -1, - currentDepth: -1 - }); - - if (!("insertedTreeIndex" in insertResult)) { - throw new Error("No suitable position found to insert."); - } - - const treeIndex = insertResult.insertedTreeIndex; - return { - treeData: insertResult.node.children, - treeIndex, - path: [ - ...insertResult.parentPath, - getNodeKey({ node: newNode, treeIndex }) - ], - parentNode: insertResult.parentNode - }; -} - -/** - * Get tree data flattened. - * - * @param {!Object[]} treeData - Tree data - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {boolean=} ignoreCollapsed - Ignore children of nodes without `expanded` set to `true` - * - * @return {{ - * node: Object, - * path: []string|[]number, - * lowerSiblingCounts: []number - * }}[] nodes - The node array - */ -export function getFlatDataFromTree({ - treeData, - getNodeKey, - ignoreCollapsed = true -}) { - if (!treeData || treeData.length < 1) { - return []; - } - - const flattened = []; - walk({ - treeData, - getNodeKey, - ignoreCollapsed, - callback: (nodeInfo) => { - flattened.push(nodeInfo); - } - }); - - return flattened; -} - -/** - * Generate a tree structure from flat data. - * - * @param {!Object[]} flatData - * @param {!function=} getKey - Function to get the key from the nodeData - * @param {!function=} getParentKey - Function to get the parent key from the nodeData - * @param {string|number=} rootKey - The value returned by `getParentKey` that corresponds to the root node. - * For example, if your nodes have id 1-99, you might use rootKey = 0 - * - * @return {Object[]} treeData - The flat data represented as a tree - */ -export function getTreeFromFlatData({ - flatData, - getKey = (node) => node.id, - getParentKey = (node) => node.parentId, - rootKey = "0" -}) { - if (!flatData) { - return []; - } - - const childrenToParents = {}; - flatData.forEach((child) => { - const parentKey = getParentKey(child); - - if (parentKey in childrenToParents) { - childrenToParents[parentKey].push(child); - } else { - childrenToParents[parentKey] = [child]; - } - }); - - if (!(rootKey in childrenToParents)) { - return []; - } - - const trav = (parent) => { - const parentKey = getKey(parent); - if (parentKey in childrenToParents) { - return { - ...parent, - children: childrenToParents[parentKey].map((child) => trav(child)) - }; - } - - return { ...parent }; - }; - - return childrenToParents[rootKey].map((child) => trav(child)); -} - -/** - * Check if a node is a descendant of another node. - * - * @param {!Object} older - Potential ancestor of younger node - * @param {!Object} younger - Potential descendant of older node - * - * @return {boolean} - */ -export function isDescendant(older, younger) { - return ( - !!older.children && - typeof older.children !== "function" && - older.children.some((child) => child === younger || isDescendant(child, younger)) - ); -} - -/** - * Get the maximum depth of the children (the depth of the root node is 0). - * - * @param {!Object} node - Node in the tree - * @param {?number} depth - The current depth - * - * @return {number} maxDepth - The deepest depth in the tree - */ -export function getDepth(node, depth = 0) { - if (!node.children) { - return depth; - } - - if (typeof node.children === "function") { - return depth + 1; - } - - return node.children.reduce( - (deepest, child) => Math.max(deepest, getDepth(child, depth + 1)), - depth - ); -} - -/** - * Find nodes matching a search query in the tree, - * - * @param {!function} getNodeKey - Function to get the key from the nodeData and tree index - * @param {!Object[]} treeData - Tree data - * @param {?string|number} searchQuery - Function returning a boolean to indicate whether the node is a match or not - * @param {!function} searchMethod - Function returning a boolean to indicate whether the node is a match or not - * @param {?number} searchFocusOffset - The offset of the match to focus on - * (e.g., 0 focuses on the first match, 1 on the second) - * @param {boolean=} expandAllMatchPaths - If true, expands the paths to any matched node - * @param {boolean=} expandFocusMatchPaths - If true, expands the path to the focused node - * - * @return {Object[]} matches - An array of objects containing the matching `node`s, their `path`s and `treeIndex`s - * @return {Object[]} treeData - The original tree data with all relevant nodes expanded. - * If expandAllMatchPaths and expandFocusMatchPaths are both false, - * it will be the same as the original tree data. - */ -export function find({ - getNodeKey, - treeData, - searchQuery, - searchMethod, - searchFocusOffset, - expandAllMatchPaths = false, - expandFocusMatchPaths = true -}) { - let matchCount = 0; - const trav = ({ isPseudoRoot = false, node, currentIndex, path = [] }) => { - let matches = []; - let isSelfMatch = false; - let hasFocusMatch = false; - // The pseudo-root is not considered in the path - const selfPath = isPseudoRoot - ? [] - : [...path, getNodeKey({ node, treeIndex: currentIndex })]; - const extraInfo = isPseudoRoot - ? null - : { - path: selfPath, - treeIndex: currentIndex - }; - - // Nodes with with children that aren't lazy - const hasChildren = - node.children && - typeof node.children !== "function" && - node.children.length > 0; - - // Examine the current node to see if it is a match - if (!isPseudoRoot && searchMethod({ ...extraInfo, node, searchQuery })) { - if (matchCount === searchFocusOffset) { - hasFocusMatch = true; - } - - // Keep track of the number of matching nodes, so we know when the searchFocusOffset - // is reached - matchCount += 1; - - // We cannot add this node to the matches right away, as it may be changed - // during the search of the descendants. The entire node is used in - // comparisons between nodes inside the `matches` and `treeData` results - // of this method (`find`) - isSelfMatch = true; - } - - let childIndex = currentIndex; - const newNode = { ...node }; - if (hasChildren) { - // Get all descendants - newNode.children = newNode.children.map((child) => { - const mapResult = trav({ - node: child, - currentIndex: childIndex + 1, - path: selfPath - }); - - // Ignore hidden nodes by only advancing the index counter to the returned treeIndex - // if the child is expanded. - // - // The child could have been expanded from the start, - // or expanded due to a matching node being found in its descendants - if (mapResult.node.expanded) { - childIndex = mapResult.treeIndex; - } else { - childIndex += 1; - } - - if (mapResult.matches.length > 0 || mapResult.hasFocusMatch) { - matches = [...matches, ...mapResult.matches]; - if (mapResult.hasFocusMatch) { - hasFocusMatch = true; - } - - // Expand the current node if it has descendants matching the search - // and the settings are set to do so. - if ( - (expandAllMatchPaths && mapResult.matches.length > 0) || - ((expandAllMatchPaths || expandFocusMatchPaths) && - mapResult.hasFocusMatch) - ) { - newNode.expanded = true; - } - } - - return mapResult.node; - }); - } - - // Cannot assign a treeIndex to hidden nodes - if (!isPseudoRoot && !newNode.expanded) { - matches = matches.map((match) => ({ - ...match, - treeIndex: null - })); - } - - // Add this node to the matches if it fits the search criteria. - // This is performed at the last minute so newNode can be sent in its final form. - if (isSelfMatch) { - matches = [{ ...extraInfo, node: newNode }, ...matches]; - } - - return { - node: matches.length > 0 ? newNode : node, - matches, - hasFocusMatch, - treeIndex: childIndex - }; - }; - - const result = trav({ - node: { children: treeData }, - isPseudoRoot: true, - currentIndex: -1 - }); - - return { - matches: result.matches, - treeData: result.node.children - }; -} From 0128ad14a58e12d4984168dba7baa8fcbe093a41 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 15:34:28 -0800 Subject: [PATCH 52/62] refactor: styles Signed-off-by: Mike Murray --- .../client/components/NavigationItemForm.js | 52 ++++++++----------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm.js b/imports/plugins/core/navigation/client/components/NavigationItemForm.js index 56858d1ee60..35095612867 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemForm.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemForm.js @@ -1,10 +1,10 @@ -import React, { Component } from "react"; +import React, { Component, Fragment } from "react"; import PropTypes from "prop-types"; import { uniqueId } from "lodash"; import SimpleSchema from "simpl-schema"; -import styled from "styled-components"; import Grid from "@material-ui/core/Grid"; import { Form } from "reacto-form"; +import withStyles from "@material-ui/core/styles/withStyles"; import Button from "@material-ui/core/Button"; import IconButton from "@material-ui/core/IconButton"; import CloseIcon from "mdi-material-ui/Close"; @@ -15,25 +15,18 @@ import TextInput from "@reactioncommerce/components/TextInput/v1"; import { i18next } from "/client/api"; import ConfirmDialog from "/imports/client/ui/components/ConfirmDialog"; - -const Wrapper = styled.div` - .close-icon-container { - text-align: right; - } - .close-icon { - display: inline-block; - height: 12px; - width: 12px; - } - .buttons-container { - text-align: right; - margin-top: 20px; - - .button-save { - margin-left: 10px; - } +const styles = (theme) => ({ + closeButtonContainer: { + textAlign: "right", + }, + formActions: { + textAlign: "right", + marginTop: theme.spacing.unit * 2 + }, + formActionButton: { + marginLeft: theme.spacing.unit } -`; +}); const navigationItemFormSchema = new SimpleSchema({ name: String, @@ -50,6 +43,7 @@ const navigationItemValidator = navigationItemFormSchema.getFormValidator(); class NavigationItemForm extends Component { static propTypes = { + classes: PropTypes.object, createNavigationItem: PropTypes.func, deleteNavigationItem: PropTypes.func, mode: PropTypes.oneOf(["create", "edit"]), @@ -127,7 +121,7 @@ class NavigationItemForm extends Component { } render() { - const { onCloseForm, mode, navigationItem } = this.props; + const { classes, onCloseForm, mode, navigationItem } = this.props; const nameInputId = `name_${this.uniqueInstanceIdentifier}`; const urlInputId = `url_${this.uniqueInstanceIdentifier}`; @@ -142,12 +136,12 @@ class NavigationItemForm extends Component { }); return ( - + {this.renderActionTitle()} - + @@ -180,7 +174,7 @@ class NavigationItemForm extends Component { - + { mode !== "create" && ( {({ openDialog }) => ( - + )} )} - - + + - + ); } } -export default NavigationItemForm; +export default withStyles(styles, { name: "RuiNavigationItemForm" })(NavigationItemForm); From 0092e7b27b40524c025a6da4b4bd5f60a885c60b Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 20:54:16 -0800 Subject: [PATCH 53/62] fix: remove comma Signed-off-by: Mike Murray --- imports/plugins/core/router/client/theme/muiTheme.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 96b9b8bb0e8..53b5e568206 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -125,7 +125,7 @@ export const rawMuiTheme = { }, MuiCard: { root: { - border: `1px solid ${colors.black10}`, + border: `1px solid ${colors.black10}` } }, MuiCheckbox: { From b256b45f0f250bd641ce78a505a4b6312f3fb45a Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 20:57:58 -0800 Subject: [PATCH 54/62] feat: add separate dashboard layout components Signed-off-by: Mike Murray --- .../ui/layouts/ContentViewFullLayout.js | 44 +++++++++++++++++++ .../ui/layouts/ContentViewStandardLayout.js | 44 +++++++++++++++++++ 2 files changed, 88 insertions(+) create mode 100644 imports/client/ui/layouts/ContentViewFullLayout.js create mode 100644 imports/client/ui/layouts/ContentViewStandardLayout.js diff --git a/imports/client/ui/layouts/ContentViewFullLayout.js b/imports/client/ui/layouts/ContentViewFullLayout.js new file mode 100644 index 00000000000..6eb66bd8d96 --- /dev/null +++ b/imports/client/ui/layouts/ContentViewFullLayout.js @@ -0,0 +1,44 @@ +/** + * Component provies a fill width and height, non-scrollable container + * for dashboard layouts that want to defin their on scroll zones. + */ +import React from "react"; +import PropTypes from "prop-types"; +import classNames from "classnames"; +import withStyles from "@material-ui/core/styles/withStyles"; + +const styles = () => ({ + root: { + width: "100vw", + height: "100vh", + paddingTop: 80, + background: "", + flexGrow: 1, + transition: "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms", + overflow: "hidden" + }, + leftSidebarOpen: { + paddingLeft: 280 + } +}); + +const ContentViewFullLayout = ({ children, classes, isMobile, isSidebarOpen }) => ( +
+ {children} +
+); + +ContentViewFullLayout.propTypes = { + children: PropTypes.node, + classes: PropTypes.object, + isMobile: PropTypes.bool, + isSidebarOpen: PropTypes.bool +}; + +export default withStyles(styles, { name: "RuiContentViewFullLayout" })(ContentViewFullLayout); diff --git a/imports/client/ui/layouts/ContentViewStandardLayout.js b/imports/client/ui/layouts/ContentViewStandardLayout.js new file mode 100644 index 00000000000..cfc7d3525b0 --- /dev/null +++ b/imports/client/ui/layouts/ContentViewStandardLayout.js @@ -0,0 +1,44 @@ +import React from "react"; +import PropTypes from "prop-types"; +import classNames from "classnames"; +import withStyles from "@material-ui/core/styles/withStyles"; + +const styles = (theme) => ({ + root: { + width: "100vw", + background: "", + flexGrow: 1, + transition: "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms" + }, + content: { + maxWidth: 1140, + padding: theme.spacing.unit * 2, + margin: "0 auto" + }, + leftSidebarOpen: { + paddingLeft: 280 + } +}); + +const ContentViewStandardLayout = ({ children, classes, isMobile, isSidebarOpen }) => ( +
+
+ {children} +
+
+); + +ContentViewStandardLayout.propTypes = { + children: PropTypes.node, + classes: PropTypes.object, + isMobile: PropTypes.bool, + isSidebarOpen: PropTypes.bool +}; + +export default withStyles(styles, { name: "RuiContentViewStandardLayout" })(ContentViewStandardLayout); From 6ec9bf55df2edf853f17e320fa35239ff64bdc57 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 21:01:08 -0800 Subject: [PATCH 55/62] feat: update layouts Signed-off-by: Mike Murray --- imports/client/ui/layouts/Dashboard.js | 51 ++++---------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/imports/client/ui/layouts/Dashboard.js b/imports/client/ui/layouts/Dashboard.js index d48957ca186..acc21673189 100644 --- a/imports/client/ui/layouts/Dashboard.js +++ b/imports/client/ui/layouts/Dashboard.js @@ -7,12 +7,14 @@ import { ContainerQuery } from "react-container-query"; import withStyles from "@material-ui/core/styles/withStyles"; import AppBar from "@material-ui/core/AppBar"; import Toolbar from "@material-ui/core/Toolbar"; -import { applyTheme, CustomPropTypes } from "@reactioncommerce/components/utils"; +import { CustomPropTypes } from "@reactioncommerce/components/utils"; import { withComponents } from "@reactioncommerce/components-context"; import { Route, Switch } from "react-router"; import ProfileImageWithData from "../components/ProfileImageWithData"; import Sidebar from "../components/Sidebar"; import { operatorRoutes } from "../index"; +import ContentViewFullLayout from "./ContentViewFullLayout"; +import ContentViewStandardayout from "./ContentViewStandardLayout"; const query = { isMobile: { @@ -32,46 +34,6 @@ const Container = styled.div` display: flex; `; -// The reason we can't simply do `styled.div` here is because we're passing in isMobile and isSidebarOpen -// props for the styled-components conditionals, but React does not recognize these as valid attributes -// for DOM elements and prints warnings in the console. Someday there may be a better solution. -// See https://github.com/styled-components/styled-components/issues/305 -const Main = styled(({ children, isMobile, isSidebarOpen, ...divProps }) => (
{children}
))` - width: 100vw; - background-color: ${applyTheme("Layout.pageBackgroundColor")}; - flex-grow: 1; - transition: ${(props) => - (props.isSidebarOpen && props.isMobile !== true - ? "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms" - : "padding 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms")}; - padding-left: ${(props) => (props.isSidebarOpen && props.isMobile === false ? "280px" : 0)}; - padding-bottom: ${applyTheme("Layout.pageContentPaddingBottom")}; - padding-right: ${applyTheme("Layout.pageContentPaddingRight")}; - padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; - margin: 0 auto; -`; - -const MainContent = styled.div` - max-width: ${applyTheme("Layout.pageContentMaxWidth")}; - padding-bottom: ${applyTheme("Layout.pageContentPaddingBottom")}; - padding-left: ${applyTheme("Layout.pageContentPaddingLeft")}; - padding-right: ${applyTheme("Layout.pageContentPaddingRight")}; - padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; - margin: 0 auto; -`; - -const MainFullWidth = styled.div` - width: 100vw; - background-color: ${applyTheme("Layout.pageBackgroundColor")}; - flex-grow: 1; - transition: ${(props) => - (props.isSidebarOpen && props.isMobile !== true - ? "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms" - : "padding 195ms cubic-bezier(0.4, 0, 0.6, 1) 0ms")}; - padding-left: ${(props) => (props.isSidebarOpen && props.isMobile === false ? "280px" : 0)}; - padding-top: ${applyTheme("Layout.pageContentPaddingTop")}; -`; - const Grow = styled.div` flex-grow: 1; `; @@ -84,6 +46,7 @@ const styles = (theme) => ({ class Dashboard extends Component { static propTypes = { + classes: PropTypes.object, components: PropTypes.shape({ IconHamburger: CustomPropTypes.component.isRequired }) @@ -150,13 +113,13 @@ class Dashboard extends Component { // If the layout component is explicitly null if (route.layoutComponent === null) { return ( - + ; - + ); } - const LayoutComponent = route.layoutComponent || Main; + const LayoutComponent = route.layoutComponent || ContentViewStandardayout; return ( From ce38fadd00ea20528ca3c0778975f09251d56dfb Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 21:03:28 -0800 Subject: [PATCH 56/62] fix: update layouts to fix issues with scrolling Signed-off-by: Mike Murray --- .../client/components/NavigationDashboard.js | 30 +++++-------- .../client/components/NavigationItemList.js | 26 +++++++++-- .../components/NavigationTreeContainer.js | 44 +++++++++---------- .../components/SortableNodeContentRenderer.js | 3 +- 4 files changed, 56 insertions(+), 47 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js index 52a7741d2b1..5510ecf2135 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard.js @@ -16,7 +16,8 @@ import NavigationItemList from "./NavigationItemList"; const styles = (theme) => ({ root: { - height: "100vh", + display: "flex", + height: `calc(100vh - ${theme.mixins.toolbar.minHeight}px)`, overflow: "hidden" }, toolbarButton: { @@ -120,23 +121,16 @@ class NavigationDashboard extends Component { - - - - - - - - - + + ({ root: { + display: "flex", + flexDirection: "column", flexGrow: 1, - padding: theme.spacing.unit * 2 + width: "100%", + maxWidth: 380 }, header: { - marginBottom: theme.spacing.unit * 2, - textAlign: "right" + padding: theme.spacing.unit * 2, + textAlign: "right", + flex: 0 + }, + list: { + flex: 1, + overflowY: "auto", + height: "100%" + }, + listContent: { + paddingLeft: theme.spacing.unit * 2, + paddingRight: theme.spacing.unit * 2, + paddingBottom: theme.spacing.unit * 2 } }); @@ -50,7 +64,11 @@ class NavigationItemList extends Component {
- {this.renderNavigationItems()} +
+
+ {this.renderNavigationItems()} +
+
); } diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js index 9091eb3ffeb..0d62d5445f9 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js @@ -1,7 +1,5 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; -import styled from "styled-components"; -import Grid from "@material-ui/core/Grid"; import IconButton from "@material-ui/core/IconButton"; import withStyles from "@material-ui/core/styles/withStyles"; import PencilIcon from "mdi-material-ui/Pencil"; @@ -11,14 +9,11 @@ import "react-sortable-tree/style.css"; import ConfirmDialog from "/imports/client/ui/components/ConfirmDialog"; import SortableTheme from "./SortableTheme"; -const ContentWrapper = styled.div` - padding: 40px 80px; - min-height: calc(100vh - 140px); -`; - const styles = (theme) => ({ wrapper: { - borderLeft: `1px solid ${theme.palette.divider}` + width: "100%", + borderLeft: `1px solid ${theme.palette.divider}`, + height: `calc(100vh - ${theme.mixins.toolbar.minHeight}px)` } }); @@ -76,22 +71,23 @@ class NavigationTreeContainer extends Component { const { classes, onSetSortableNavigationTree, sortableNavigationTree } = this.props; return (
- - - -

Drag and drop pages and tags from the left column into the navigation structure.

-
-
-
- -
-
+
); } diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js index bd519c76553..9750a2a640a 100644 --- a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -210,7 +210,8 @@ class SortableNodeContentRenderer extends Component { style={{ marginTop: -2, height: "100%", - marginLeft: (lowerSiblingCounts.length - 1) * scaffoldBlockPxWidth + marginLeft: (lowerSiblingCounts.length) * scaffoldBlockPxWidth, + marginRight: scaffoldBlockPxWidth }} {...otherProps} > From b1f55126c46637d1ade461c2ead5ac29bd7d5307 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 21:11:31 -0800 Subject: [PATCH 57/62] feat: discard navigation tree changes Signed-off-by: Mike Murray --- .../navigation/client/components/NavigationDashboard.js | 5 +++-- .../core/navigation/client/hocs/withNavigationUIStore.js | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationDashboard.js b/imports/plugins/core/navigation/client/components/NavigationDashboard.js index 5510ecf2135..a88cec7aeda 100644 --- a/imports/plugins/core/navigation/client/components/NavigationDashboard.js +++ b/imports/plugins/core/navigation/client/components/NavigationDashboard.js @@ -2,7 +2,6 @@ import React, { Component } from "react"; import PropTypes from "prop-types"; import classnames from "classnames"; import AppBar from "@material-ui/core/AppBar"; -import Grid from "@material-ui/core/Grid"; import Modal from "@material-ui/core/Modal"; import Button from "@material-ui/core/Button"; import Toolbar from "@material-ui/core/Toolbar"; @@ -47,6 +46,7 @@ class NavigationDashboard extends Component { createNavigationItem: PropTypes.func, deleteNavigationItem: PropTypes.func, navigationItems: PropTypes.array, + onDiscardNavigationTreeChanges: PropTypes.func, onSetSortableNavigationTree: PropTypes.func, sortableNavigationTree: PropTypes.arrayOf(PropTypes.object), uiState: PropTypes.shape({ @@ -98,6 +98,7 @@ class NavigationDashboard extends Component { onSetSortableNavigationTree, sortableNavigationTree, uiState, + onDiscardNavigationTreeChanges, updateNavigationItem, updateNavigationTree } = this.props; @@ -117,7 +118,7 @@ class NavigationDashboard extends Component { Main Navigation - + diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js index 38ee3f95d9c..14a60616e53 100644 --- a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -10,6 +10,12 @@ export default (Component) => ( dragging: false } + handleDiscardChanges = () => { + this.setState(({ navigationTree }) => ({ + sortableNavigationTree: this.navigationTreeToSortable(navigationTree) + })); + } + handleSetNavigationItems = (navigationItems) => { this.setState({ navigationItems }); } @@ -65,6 +71,7 @@ export default (Component) => ( onSetSortableNavigationTree={this.handleSetSortableNavigationTree} navigationItems={navigationItems} onAddNavigationItem={this.handleAddNavigationItem} + onDiscardNavigationTreeChanges={this.handleDiscardChanges} onSetNavigationTree={this.handleSetNavigationTree} onSetNavigationItems={this.handleSetNavigationItems} /> From ab948346247936dbb386a01621842ec5c3f1813c Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 21:16:36 -0800 Subject: [PATCH 58/62] fix: lint issues Signed-off-by: Mike Murray --- imports/client/ui/components/ConfirmDialog/ConfirmDialog.js | 1 - .../core/navigation/client/hocs/withUpdateNavigationTree.js | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js b/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js index 5d16a763b63..47607d77731 100644 --- a/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js +++ b/imports/client/ui/components/ConfirmDialog/ConfirmDialog.js @@ -14,7 +14,6 @@ class ConfirmDialog extends Component { confirmActionText: PropTypes.string, message: PropTypes.oneOfType([PropTypes.string, PropTypes.node]), onConfirm: PropTypes.func, - openButtonContent: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired, title: PropTypes.oneOfType([PropTypes.string, PropTypes.node]) } diff --git a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js index b8890e5e3d1..54e07ee0be2 100644 --- a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js +++ b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js @@ -37,7 +37,9 @@ const updateNavigationTreeMutation = gql` export default (Component) => ( class WithUpdateNavigationTree extends React.Component { static propTypes = { - onUpdateNavigationTree: PropTypes.func + defaultNavigationTreeId: PropTypes.string, + onUpdateNavigationTree: PropTypes.func, + sortableNavigationTree: PropTypes.arrayOf(PropTypes.object) } handleUpdateNavigationTree = (data) => { From 2dff930ccda4bd2de00cba8358ac11848e46a3f9 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 21:24:14 -0800 Subject: [PATCH 59/62] fix: lint issue Signed-off-by: Mike Murray --- .../components/SortableNodeContentRenderer.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js index 9750a2a640a..61018431f7c 100644 --- a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -167,16 +167,16 @@ class SortableNodeContentRenderer extends Component { // Construct the scaffold representing the structure of the tree const scaffold = []; - lowerSiblingCounts.forEach((lowerSiblingCount, i) => { + lowerSiblingCounts.forEach((lowerSiblingCount, index) => { scaffold.push((
)); - if (treeIndex !== listIndex && i === swapDepth) { + if (treeIndex !== listIndex && index === swapDepth) { // This row has been shifted, and is at the depth of // the line pointing to the new destination let highlightLineClass = ""; @@ -195,9 +195,9 @@ class SortableNodeContentRenderer extends Component { scaffold.push((
@@ -261,7 +261,8 @@ class SortableNodeContentRenderer extends Component { {typeof nodeTitle === "function" ? nodeTitle({ node, path, treeIndex }) : nodeTitle} - {typeof nodeSubtitle === "function" ? nodeSubtitle({ node, path, treeIndex }) : nodeSubtitle} + + {typeof nodeSubtitle === "function" ? nodeSubtitle({ node, path, treeIndex }) : nodeSubtitle}
From 1a5384caddfeec6f82385ab4ffc0540d3b3b74b5 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Tue, 5 Feb 2019 21:24:46 -0800 Subject: [PATCH 60/62] fix: update padding for layout containers Signed-off-by: Mike Murray --- imports/client/ui/layouts/ContentViewFullLayout.js | 4 ++-- imports/client/ui/layouts/ContentViewStandardLayout.js | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/imports/client/ui/layouts/ContentViewFullLayout.js b/imports/client/ui/layouts/ContentViewFullLayout.js index 6eb66bd8d96..f0ff018b44b 100644 --- a/imports/client/ui/layouts/ContentViewFullLayout.js +++ b/imports/client/ui/layouts/ContentViewFullLayout.js @@ -7,11 +7,11 @@ import PropTypes from "prop-types"; import classNames from "classnames"; import withStyles from "@material-ui/core/styles/withStyles"; -const styles = () => ({ +const styles = (theme) => ({ root: { width: "100vw", height: "100vh", - paddingTop: 80, + paddingTop: theme.mixins.toolbar.minHeight, background: "", flexGrow: 1, transition: "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms", diff --git a/imports/client/ui/layouts/ContentViewStandardLayout.js b/imports/client/ui/layouts/ContentViewStandardLayout.js index cfc7d3525b0..85e8e512fb7 100644 --- a/imports/client/ui/layouts/ContentViewStandardLayout.js +++ b/imports/client/ui/layouts/ContentViewStandardLayout.js @@ -6,13 +6,15 @@ import withStyles from "@material-ui/core/styles/withStyles"; const styles = (theme) => ({ root: { width: "100vw", - background: "", flexGrow: 1, transition: "padding 225ms cubic-bezier(0, 0, 0.2, 1) 0ms" }, content: { maxWidth: 1140, - padding: theme.spacing.unit * 2, + paddingTop: theme.mixins.toolbar.minHeight + (theme.spacing.unit * 2), + paddingLeft: theme.spacing.unit * 2, + paddingRight: theme.spacing.unit * 2, + paddingBottom: theme.spacing.unit * 2, margin: "0 auto" }, leftSidebarOpen: { From 6457f97f71c2d032076909a63956db10cea53959 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Wed, 6 Feb 2019 11:04:28 -0800 Subject: [PATCH 61/62] feat: fixing minor ui issues Signed-off-by: Mike Murray --- .../client/components/NavigationItemCard.js | 11 ++++++++++- .../client/components/NavigationTreeContainer.js | 1 + .../client/components/SortableNodeContentRenderer.js | 6 +++++- .../navigation/client/hocs/withNavigationUIStore.js | 1 + .../client/hocs/withUpdateNavigationItem.js | 1 + .../client/hocs/withUpdateNavigationTree.js | 2 ++ imports/plugins/core/router/client/theme/muiTheme.js | 1 + 7 files changed, 21 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemCard.js b/imports/plugins/core/navigation/client/components/NavigationItemCard.js index 1625bc40cf7..6c76f23dd3a 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemCard.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemCard.js @@ -18,6 +18,13 @@ const styles = (theme) => ({ paddingLeft: theme.spacing.unit, paddingRight: theme.spacing.unit }, + iconButton: { + "padding": 6, + "color": theme.palette.colors.black30, + "&:hover": { + backgroundColor: "transparent" + } + }, rowContent: { flex: 1 }, @@ -91,7 +98,9 @@ class NavigationItemCard extends Component { const dragHandle = (
- + + +
); diff --git a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js index 0d62d5445f9..e6fbf118998 100644 --- a/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js +++ b/imports/plugins/core/navigation/client/components/NavigationTreeContainer.js @@ -84,6 +84,7 @@ class NavigationTreeContainer extends Component { }} generateNodeProps={this.generateNodeProps} treeData={sortableNavigationTree || []} + maxDepth={3} onChange={onSetSortableNavigationTree} theme={SortableTheme} dndType={"CARD"} diff --git a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js index 61018431f7c..0a681652129 100644 --- a/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js +++ b/imports/plugins/core/navigation/client/components/SortableNodeContentRenderer.js @@ -19,7 +19,11 @@ const styles = (theme) => ({ height: "100%" }, dragIcon: { - padding: 6 + "padding": 6, + "color": theme.palette.colors.black30, + "&:hover": { + backgroundColor: "transparent" + } }, // Expand styles. This empty object has to be here for "&$expanded" to work below. expanded: {}, diff --git a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js index 14a60616e53..f5722b114e1 100644 --- a/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js +++ b/imports/plugins/core/navigation/client/hocs/withNavigationUIStore.js @@ -44,6 +44,7 @@ export default (Component) => ( const newNode = {}; newNode.id = node.navigationItem._id; newNode.title = this.getNavigationItemTitle(node.navigationItem).value; + newNode.expanded = node.navigationItem.expanded; newNode.subtitle = node.navigationItem.draftData.url; newNode.navigationItem = { ...node.navigationItem }; diff --git a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js index 2fe120cda3a..fd95a8ed918 100644 --- a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js +++ b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationItem.js @@ -105,6 +105,7 @@ export default (Component) => { {...this.props} deleteNavigationItem={deleteNavigationItem} updateNavigationItem={updateNavigationItem} + publishNavigationChanges={this.handlePublishNavigationChanges} /> )} diff --git a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js index 54e07ee0be2..033d3c418ae 100644 --- a/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js +++ b/imports/plugins/core/navigation/client/hocs/withUpdateNavigationTree.js @@ -39,12 +39,14 @@ export default (Component) => ( static propTypes = { defaultNavigationTreeId: PropTypes.string, onUpdateNavigationTree: PropTypes.func, + publishNavigationChanges: PropTypes.func, sortableNavigationTree: PropTypes.arrayOf(PropTypes.object) } handleUpdateNavigationTree = (data) => { const { updateNavigationTree: { navigationTree } } = data; this.props.onUpdateNavigationTree(navigationTree); + this.props.publishNavigationChanges(); } sortableNavigationTreeToDraftItems(sortableNavigationTree) { diff --git a/imports/plugins/core/router/client/theme/muiTheme.js b/imports/plugins/core/router/client/theme/muiTheme.js index 53b5e568206..a1031503bd6 100644 --- a/imports/plugins/core/router/client/theme/muiTheme.js +++ b/imports/plugins/core/router/client/theme/muiTheme.js @@ -15,6 +15,7 @@ export const colorSecondaryMain = colors.coolGrey; export const rawMuiTheme = { palette: { + colors, // TODO: De-structure these colors into various MUI properties rather than using them from this object primary: { light: colors.coolGrey300, main: colorPrimaryMain, From 7513a32a42beb4291ba5af20143ee3644708c527 Mon Sep 17 00:00:00 2001 From: Mike Murray Date: Thu, 7 Feb 2019 18:36:41 -0800 Subject: [PATCH 62/62] fix: lint issues Signed-off-by: Mike Murray --- .../core/navigation/client/components/NavigationItemForm.js | 2 +- imports/plugins/core/ui/client/containers/tagListContainer.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/imports/plugins/core/navigation/client/components/NavigationItemForm.js b/imports/plugins/core/navigation/client/components/NavigationItemForm.js index 35095612867..2d383bdf4a0 100644 --- a/imports/plugins/core/navigation/client/components/NavigationItemForm.js +++ b/imports/plugins/core/navigation/client/components/NavigationItemForm.js @@ -17,7 +17,7 @@ import ConfirmDialog from "/imports/client/ui/components/ConfirmDialog"; const styles = (theme) => ({ closeButtonContainer: { - textAlign: "right", + textAlign: "right" }, formActions: { textAlign: "right", diff --git a/imports/plugins/core/ui/client/containers/tagListContainer.js b/imports/plugins/core/ui/client/containers/tagListContainer.js index 60b3719521d..b59bcbd5496 100644 --- a/imports/plugins/core/ui/client/containers/tagListContainer.js +++ b/imports/plugins/core/ui/client/containers/tagListContainer.js @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import _ from "lodash"; import update from "immutability-helper"; import { compose } from "recompose"; -import { registerComponent, composeWithTracker, Components } from "@reactioncommerce/reaction-components"; +import { registerComponent, composeWithTracker } from "@reactioncommerce/reaction-components"; import { Meteor } from "meteor/meteor"; import { Reaction, i18next } from "/client/api"; import TagList from "../components/tags/tagList";