From 9d56eea4279049a3ecd502a03dfbf23b04efd9b7 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 16 Mar 2021 11:32:56 +0100 Subject: [PATCH 01/64] Add icon and empty drawer --- src/browser/components/icons/Icons.tsx | 12 ++++++++++++ src/browser/icons/monitor-play.svg | 19 +++++++++++++++++++ src/browser/modules/Sidebar/GuideDrawer.tsx | 7 +++++++ src/browser/modules/Sidebar/Sidebar.tsx | 14 ++++++++++++-- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 src/browser/icons/monitor-play.svg create mode 100644 src/browser/modules/Sidebar/GuideDrawer.tsx diff --git a/src/browser/components/icons/Icons.tsx b/src/browser/components/icons/Icons.tsx index a10b32b4b52..599786e7ce2 100644 --- a/src/browser/components/icons/Icons.tsx +++ b/src/browser/components/icons/Icons.tsx @@ -39,6 +39,7 @@ import expand from 'icons/expand.svg' import file from 'icons/file.svg' import folderEmpty from 'icons/folder-empty.svg' import help from 'icons/help.svg' +import monitorPlay from 'icons/monitor-play.svg' import navigationMenuVertical from 'icons/navigation-menu-vertical.svg' import neo4j from 'icons/neo-world.svg' import newFolder from 'icons/folder-add.svg' @@ -135,6 +136,17 @@ export const DatabaseIcon = (props: { ) } +export const GuideDrawerIcon = (props: { isOpen: boolean }): JSX.Element => ( + +) + interface SidebarIconProps { isOpen: boolean title: string diff --git a/src/browser/icons/monitor-play.svg b/src/browser/icons/monitor-play.svg new file mode 100644 index 00000000000..794fde86e03 --- /dev/null +++ b/src/browser/icons/monitor-play.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/browser/modules/Sidebar/GuideDrawer.tsx b/src/browser/modules/Sidebar/GuideDrawer.tsx new file mode 100644 index 00000000000..99c2b0eeb3e --- /dev/null +++ b/src/browser/modules/Sidebar/GuideDrawer.tsx @@ -0,0 +1,7 @@ +import React from 'react' + +function GuideDrawer(): JSX.Element { + return
+} + +export default GuideDrawer diff --git a/src/browser/modules/Sidebar/Sidebar.tsx b/src/browser/modules/Sidebar/Sidebar.tsx index 38b8dacab66..0537c8df4ee 100644 --- a/src/browser/modules/Sidebar/Sidebar.tsx +++ b/src/browser/modules/Sidebar/Sidebar.tsx @@ -24,6 +24,7 @@ import DatabaseDrawer from '../DBMSInfo/DBMSInfo' import DocumentsDrawer from './Documents' import AboutDrawer from './About' import SettingsDrawer from './Settings' +import GuideDrawer from './GuideDrawer' import Favorites from './favorites' import StaticScripts from './static-scripts' import ProjectFilesDrawer from './ProjectFiles' @@ -48,7 +49,8 @@ import { CloudSyncIcon, SettingsIcon, AboutIcon, - ProjectFilesIcon + ProjectFilesIcon, + GuideDrawerIcon } from 'browser-components/icons/Icons' import { getCurrentDraft } from 'shared/modules/sidebar/sidebarDuck' import { DrawerHeader } from 'browser-components/drawer' @@ -118,7 +120,15 @@ const Sidebar = ({ } } ] - : []) + : []), + { + name: 'Guides', + title: 'Guides', + icon: function guideDrawerIcon(isOpen: boolean): JSX.Element { + return + }, + content: GuideDrawer + } ] const bottomNavItemsList: NavItem[] = [ From aed65fd6b35bfcdea56e3f04e06d7efe0093b677 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 16 Mar 2021 13:30:54 +0100 Subject: [PATCH 02/64] Move link and command styling to be reusable --- src/browser/components/drawer/Drawer.tsx | 33 ++++++++++++++++ .../components/drawer/{index.ts => index.tsx} | 8 +++- src/browser/icons/external-link.svg | 5 +++ src/browser/modules/Sidebar/DocumentItems.tsx | 8 ++-- src/browser/modules/Sidebar/styled.tsx | 39 +++---------------- 5 files changed, 53 insertions(+), 40 deletions(-) rename src/browser/components/drawer/{index.ts => index.tsx} (89%) create mode 100644 src/browser/icons/external-link.svg diff --git a/src/browser/components/drawer/Drawer.tsx b/src/browser/components/drawer/Drawer.tsx index 2cb306ffc62..c2b679c32b2 100644 --- a/src/browser/components/drawer/Drawer.tsx +++ b/src/browser/components/drawer/Drawer.tsx @@ -19,6 +19,7 @@ */ import styled from 'styled-components' +import linkIcon from 'icons/external-link.svg' export const Drawer = styled.div` width: 290px; @@ -76,3 +77,35 @@ export const DrawerFooter = styled.div` margin-bottom: 20px; text-align: center; ` + +export const DrawerExternalLink = styled.a` + cursor: pointer; + text-decoration: none; + color: #68bdf4; + + &:active { + text-decoration: none; + } + + &:before { + display: inline-block; + content: ' '; + background-image: url("data:image/svg+xml;utf8,${linkIcon}"); + height: 12px; + width: 12px; + margin-right: 7px; + } +` + +export const DrawerBrowserCommand = styled.span` + background-color: #2a2c33; + border-radius: 2px; + padding: 3px; + + color: #e36962; + font-family: Fira Code; + + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +` diff --git a/src/browser/components/drawer/index.ts b/src/browser/components/drawer/index.tsx similarity index 89% rename from src/browser/components/drawer/index.ts rename to src/browser/components/drawer/index.tsx index e50ce497f07..dd1275ee9aa 100644 --- a/src/browser/components/drawer/index.ts +++ b/src/browser/components/drawer/index.tsx @@ -26,7 +26,9 @@ import { DrawerSection, DrawerSectionBody, DrawerToppedHeader, - DrawerFooter + DrawerFooter, + DrawerExternalLink, + DrawerBrowserCommand } from './Drawer' export { @@ -37,5 +39,7 @@ export { DrawerSection, DrawerSectionBody, DrawerToppedHeader, - DrawerFooter + DrawerFooter, + DrawerExternalLink, + DrawerBrowserCommand } diff --git a/src/browser/icons/external-link.svg b/src/browser/icons/external-link.svg new file mode 100644 index 00000000000..1796a1d0cf8 --- /dev/null +++ b/src/browser/icons/external-link.svg @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/src/browser/modules/Sidebar/DocumentItems.tsx b/src/browser/modules/Sidebar/DocumentItems.tsx index 875da8b2d83..b33d3930a1d 100644 --- a/src/browser/modules/Sidebar/DocumentItems.tsx +++ b/src/browser/modules/Sidebar/DocumentItems.tsx @@ -23,10 +23,10 @@ import { Action } from 'redux' import { DrawerSubHeader, DrawerSection, - DrawerSectionBody + DrawerSectionBody, + DrawerExternalLink } from 'browser-components/drawer' import { - StyledHelpLink, StyledHelpItem, StyledCommandListItem, StyledCommandNamePair, @@ -78,9 +78,9 @@ export const DocumentItems = ({ const listOfItems = items.map(item => 'url' in item ? ( - + {item.name} - + ) : ( "); - height: 12px; - width: 12px; - margin-right: 7px; - } -` export const StyledHelpItem = styled.li` list-style-type: none; margin: 8px 24px 0 24px; @@ -100,21 +82,6 @@ export const StyledName = styled.div` margin-right: 5%; ` -export const StyledCommand = styled.div` - background-color: #2a2c33; - border-radius: 2px; - padding: 3px; - - color: #e36962; - font-family: Fira Code; - - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - - max-width: 45%; -` - export const StyledFullSizeDrawerBody = styled(DrawerBody)` padding: 0; ` @@ -149,3 +116,7 @@ export const StyledFeedbackButton = styled(Button)` margin: 0 0 25px 25px !important; min-height: fit-content !important; ` + +export const StyledCommand = styled(DrawerBrowserCommand)` + max-width: 45%; +` From d15bb8710e8bf37edba59379dc539faed7152405 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 16 Mar 2021 13:31:12 +0100 Subject: [PATCH 03/64] Add help message in guide sidebar --- src/browser/modules/Sidebar/GuideDrawer.tsx | 52 ++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/src/browser/modules/Sidebar/GuideDrawer.tsx b/src/browser/modules/Sidebar/GuideDrawer.tsx index 99c2b0eeb3e..0cb67a35acb 100644 --- a/src/browser/modules/Sidebar/GuideDrawer.tsx +++ b/src/browser/modules/Sidebar/GuideDrawer.tsx @@ -1,7 +1,57 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + import React from 'react' +import { + Drawer, + DrawerBody, + DrawerHeader, + DrawerBrowserCommand +} from 'browser-components/drawer' +import styled from 'styled-components' +const sidebarGuides = [ + 'intro', + 'concepts', + 'cypher', + 'movie-graph', + 'northwind-graph' +] + +const StyledMessage = styled.div`` +const GuideContent = styled.div`` function GuideDrawer(): JSX.Element { - return
+ return ( + + Neo4j Browser Guides + +
dropdown goes here
+ guide goes here + + You can also access Browser guides by running + :play {''} + in the main editor + +
+
+ ) } export default GuideDrawer From 6196260c94b5b27d215c2faff59f143bde64b0d3 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 16 Mar 2021 18:43:24 +0100 Subject: [PATCH 04/64] Add types to documenation --- src/browser/documentation/index.ts | 140 ++++++++++++++++++++--- src/browser/modules/Stream/PlayFrame.tsx | 7 +- src/shared/services/commandUtils.ts | 2 +- 3 files changed, 129 insertions(+), 20 deletions(-) diff --git a/src/browser/documentation/index.ts b/src/browser/documentation/index.ts index 421f334de28..eeee9d03816 100644 --- a/src/browser/documentation/index.ts +++ b/src/browser/documentation/index.ts @@ -103,27 +103,135 @@ import guideTypography from './guides/typography' import guideUnfound from './guides/unfound' import guideWritecode from './guides/write-code' -export default { +type AllDocumentation = { + help: HelpDocs + cypher: CypherDocs + bolt: BoltDocs + play: GuideDocs +} +type GuideDocs = { + title: 'Guides & Examples' + chapters: Record +} + +export type GuideChapter = + | 'concepts' + | 'cypher' + | 'iconography' + | 'intro' + | 'learn' + | 'movieGraph' + | 'movies' + | 'northwind' + | 'northwindGraph' + | 'start' + | 'typography' + | 'unfound' + | 'writeCode' + +type DocItem = { + title: string + subtitle?: string + category?: string + content?: JSX.Element | null + footer?: JSX.Element + slides?: JSX.Element[] +} + +type BoltDocs = { title: 'Bolt'; chapters: Record } +type BoltChapter = 'boltEncryption' | 'boltRouting' +type CypherDocs = { title: 'Cypher'; chapters: Record } +type CypherChapter = + | 'alterUser' + | 'contains' + | 'create' + | 'createConstraintOn' + | 'createDatabase' + | 'createIndexOn' + | 'createRole' + | 'createUser' + | 'delete' + | 'deny' + | 'detachDelete' + | 'dropConstraintOn' + | 'dropDatabase' + | 'dropIndexOn' + | 'dropRole' + | 'dropUser' + | 'endsWith' + | 'explain' + | 'foreach' + | 'grant' + | 'grantRole' + | 'loadCsv' + | 'match' + | 'merge' + | 'param' + | 'params' + | 'profile' + | 'queryPlan' + | 'remove' + | 'rest' + | 'restDelete' + | 'restGet' + | 'restPost' + | 'restPut' + | 'return' + | 'revoke' + | 'revokeRole' + | 'schema' + | 'set' + | 'showDatabases' + | 'showPrivileges' + | 'showRoles' + | 'showUsers' + | 'startsWith' + | 'template' + | 'unwind' + | 'where' + | 'with' + +type HelpDocs = { title: 'Commands'; chapters: Record } +type HelpChapter = + | 'auto' + | 'bolt' + | 'clear' + | 'commands' + | 'cypher' + | 'guides' + | 'help' + | 'history' + | 'historyClear' + | 'keys' + | 'play' + | 'queries' + | 'server' + | 'serverUser' + | 'style' + | 'unfound' + | 'unknown' + +const docs: AllDocumentation = { help: { title: 'Commands', chapters: { auto: helpAuto, - clear: helpClear, - cypher: helpCypher, bolt: helpBolt, + clear: helpClear, commands: helpCommands, + cypher: helpCypher, guides: helpPlay, help: helpHelp, history: helpHistory, historyClear: helpHistoryClear, keys: helpKeys, play: helpPlay, + queries: helpQueries, server: helpServer, serverUser: helpServerUser, style: helpStyle, unfound: helpUnfound, - unknown: helpUnknown, - queries: helpQueries + unknown: helpUnknown } }, cypher: { @@ -131,12 +239,12 @@ export default { chapters: { alterUser: helpAlterUser, contains: helpContains, + create: helpCreate, createConstraintOn: helpCreateConstraintOn, createDatabase: helpCreateDatabase, createIndexOn: helpCreateIndexOn, createRole: helpCreateRole, createUser: helpCreateUser, - create: helpCreate, delete: helpDelete, deny: helpDeny, detachDelete: helpDetachDelete, @@ -191,22 +299,24 @@ export default { chapters: { concepts: guideConcepts, cypher: guideCypher, + iconography: guideIconography, intro: guideIntro, learn: guideLearn, movieGraph: guideMovieGraph, movies: guideMovieGraph, - northwindGraph: guideNorthwindGraph, northwind: guideNorthwindGraph, - iconography: guideIconography, + northwindGraph: guideNorthwindGraph, start: guideStart, typography: guideTypography, unfound: guideUnfound, - writeCode: guideWritecode, - // Commands only - 'query-template': { - title: 'Query Templates', - category: 'guides' - } - } as any + writeCode: guideWritecode + } } } + +// TypeGuard function to ts to understand that a string is a valid key +export function isGuideChapter(name: string): name is GuideChapter { + return name in docs.play.chapters +} + +export default docs diff --git a/src/browser/modules/Stream/PlayFrame.tsx b/src/browser/modules/Stream/PlayFrame.tsx index b8d6c45818c..789335b34bb 100644 --- a/src/browser/modules/Stream/PlayFrame.tsx +++ b/src/browser/modules/Stream/PlayFrame.tsx @@ -23,7 +23,7 @@ import { withBus } from 'react-suber' import { fetchGuideFromAllowlistAction } from 'shared/modules/commands/commandsDuck' import Docs from '../Docs/Docs' -import docs from '../../documentation' +import docs, { isGuideChapter } from '../../documentation' import FrameTemplate from '../Frame/FrameTemplate' import FrameAside from '../Frame/FrameAside' import { @@ -239,10 +239,9 @@ function generateContent( stackFrame.cmd.trim() === ':play' ? ':play start' : stackFrame.cmd ) - const guide = chapters[guideName] || {} // Check if content exists locally - if (Object.keys(guide).length) { - const { content, title, subtitle, slides = null } = guide + if (isGuideChapter(guideName)) { + const { content, title, subtitle, slides = null } = chapters[guideName] return { guide: ( { .toLowerCase() } -export const transformCommandToHelpTopic = (inputStr: any) => +export const transformCommandToHelpTopic = (inputStr?: string): string => [inputStr || ''] .map(stripPound) .map(getHelpTopic) From 2d66742966aba0c1ccc49e9a63def1a3262b943c Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Thu, 18 Mar 2021 19:49:24 +0100 Subject: [PATCH 05/64] Add carousel --- .../components/TabNavigation/styled.tsx | 2 +- .../documentation/guides/allGuides.tsx | 16 + .../documentation/guides/movie-graph.tsx | 1 - src/browser/documentation/index.ts | 3 + src/browser/modules/Carousel/Carousel.tsx | 9 +- src/browser/modules/Docs/Docs.tsx | 27 +- src/browser/modules/Sidebar/GuideDrawer.tsx | 296 ++++++++++++++++-- src/browser/modules/Stream/PlayFrame.tsx | 6 +- 8 files changed, 322 insertions(+), 38 deletions(-) create mode 100644 src/browser/documentation/guides/allGuides.tsx diff --git a/src/browser/components/TabNavigation/styled.tsx b/src/browser/components/TabNavigation/styled.tsx index 3b82cd6b3de..eebfd116c94 100644 --- a/src/browser/components/TabNavigation/styled.tsx +++ b/src/browser/components/TabNavigation/styled.tsx @@ -33,7 +33,7 @@ export const StyledDrawer: any = styled.div` background-color: #31333b; overflow-x: hidden; overflow-y: auto; - width: ${(props: any) => (props.open ? '300px' : '0px')}; + width: ${(props: any) => (props.open ? 'auto' : '0px')}; transition: 0.2s ease-out; z-index: 1; ` diff --git a/src/browser/documentation/guides/allGuides.tsx b/src/browser/documentation/guides/allGuides.tsx new file mode 100644 index 00000000000..b49afd0dc2e --- /dev/null +++ b/src/browser/documentation/guides/allGuides.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import { DrawerBrowserCommand } from 'browser-components/drawer' +import Slide from 'browser/modules/Carousel/Slide' + +const title = 'all guides' +const slides = [ + + use the dropdown to pick a guide +
+ You can also access Browser guides by running + :play {''} + in the main editor +
+] + +export default { title, slides } diff --git a/src/browser/documentation/guides/movie-graph.tsx b/src/browser/documentation/guides/movie-graph.tsx index 4317ad4bc23..46cd38d1b52 100644 --- a/src/browser/documentation/guides/movie-graph.tsx +++ b/src/browser/documentation/guides/movie-graph.tsx @@ -21,7 +21,6 @@ import React from 'react' import ManualLink from 'browser-components/ManualLink' import Slide from '../../modules/Carousel/Slide' -import TextCommand from 'browser/modules/DecoratedText/TextCommand' const title = 'Movie Graph' const category = 'graphExamples' diff --git a/src/browser/documentation/index.ts b/src/browser/documentation/index.ts index eeee9d03816..e330f7055d7 100644 --- a/src/browser/documentation/index.ts +++ b/src/browser/documentation/index.ts @@ -89,6 +89,7 @@ import helpHelp from './dynamic/help' import helpPlay from './dynamic/play' // Carousels +import allGuides from './guides/allGuides' import guideConcepts from './guides/concepts' import guideCypher from './guides/cypher' import guideIntro from './guides/intro' @@ -128,6 +129,7 @@ export type GuideChapter = | 'typography' | 'unfound' | 'writeCode' + | 'allGuides' type DocItem = { title: string @@ -297,6 +299,7 @@ const docs: AllDocumentation = { play: { title: 'Guides & Examples', chapters: { + allGuides: allGuides, concepts: guideConcepts, cypher: guideCypher, iconography: guideIconography, diff --git a/src/browser/modules/Carousel/Carousel.tsx b/src/browser/modules/Carousel/Carousel.tsx index d3016f8919d..fb9edf5d244 100644 --- a/src/browser/modules/Carousel/Carousel.tsx +++ b/src/browser/modules/Carousel/Carousel.tsx @@ -45,8 +45,9 @@ export default function Carousel({ withDirectives, originFrameId, initialSlide = 1, - slides = [] -}: any) { + slides = [], + showSideButtons +}: any): JSX.Element { const [visibleSlide, setVisibleSlide] = useState(() => { if (initialSlide <= slides.length) { return initialSlide - 1 @@ -110,7 +111,7 @@ export default function Carousel({ onKeyUp={(e: any) => onKeyDown(e)} tabIndex="0" > - {visibleSlide > 0 && ( + {showSideButtons && visibleSlide > 0 && ( - {visibleSlide < slides.length - 1 && ( + {showSideButtons && visibleSlide < slides.length - 1 && ( ([]) useEffect(() => { @@ -68,15 +82,13 @@ export default function Docs({ return } - if (withDirectives) { - slide = - } + slide = setStateSlides([slide]) if (onSlide) { onSlide({ hasPrev: false, hasNext: false, slideIndex: 0 }) } - }, [slides, content, html, withDirectives, lastUpdate]) + }, [slides, content, html, lastUpdate]) if (stateSlides.length > 1) { return ( @@ -86,6 +98,7 @@ export default function Docs({ initialSlide={initialSlide} withDirectives={withDirectives} originFrameId={originFrameId} + showSideButtons={showSideButtons} /> ) } else if (stateSlides.length) { diff --git a/src/browser/modules/Sidebar/GuideDrawer.tsx b/src/browser/modules/Sidebar/GuideDrawer.tsx index 0cb67a35acb..cd96bdded83 100644 --- a/src/browser/modules/Sidebar/GuideDrawer.tsx +++ b/src/browser/modules/Sidebar/GuideDrawer.tsx @@ -18,40 +18,292 @@ * along with this program. If not, see . */ -import React from 'react' -import { - Drawer, - DrawerBody, - DrawerHeader, - DrawerBrowserCommand -} from 'browser-components/drawer' +import React, { useState } from 'react' +import { Drawer, DrawerBody, DrawerHeader } from 'browser-components/drawer' import styled from 'styled-components' -const sidebarGuides = [ +import { GuideChapter, isGuideChapter } from 'browser/documentation' +import docs from '../../documentation' + +const StyledCarousel = styled.div` + padding-bottom: 20px; + min-height: 100%; + width: 100%; + outline: none; + + .row { + margin-left: 0; + margin-right: 0; + } +` + +const SlideContainer = styled.div` + padding: 0; + width: 100%; + display: inline-block; +` + +const StyledCarouselButtonContainer = styled.div` + color: ${props => props.theme.secondaryButtonText}; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + bottom: 0; + z-index: 10; + border-top: ${props => props.theme.inFrameBorder}; + margin-left: -40px; + height: 39px; + width: 100%; + + .is-fullscreen & { + bottom: 39px; + } +` +const StyledCarouselButtonContainerInner = styled.div` + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + position: relative; +` + +const StyledCarouselCount = styled.div` + display: flex; + align-items: center; + font-size: 10px; + font-weight: bold; + justify-content: flex-end; + border-radius: 3px; + min-width: 44px; + position: absolute; + right: 100%; + padding: 0; + margin-right: 10px; +` + +const CarouselIndicator = styled.li` + margin: 0; + cursor: pointer; + border-radius: 50%; + border: 3px solid transparent; + position: relative; + z-index: 1; + + > span { + background-color: ${props => props.theme.secondaryButtonText}; + display: block; + border-radius: 3px; + width: 6px; + height: 6px; + opacity: 0.4; + transition: opacity 0.1s ease-in-out; + } + + &::before { + border-radius: 2px; + content: attr(aria-label); + color: ${props => props.theme.primaryBackground}; + background-color: ${props => props.theme.primaryText}; + position: absolute; + font-size: 12px; + font-weight: bold; + left: 50%; + min-width: 24px; + bottom: calc(100% + 5px); + pointer-events: none; + transform: translateX(-50%); + padding: 5px; + line-height: 1; + text-align: center; + z-index: 100; + visibility: hidden; + } + + &::after { + border: solid; + border-color: ${props => props.theme.primaryText} transparent; + border-width: 6px 6px 0 6px; + bottom: 5px; + content: ''; + left: 50%; + pointer-events: none; + position: absolute; + transform: translateX(-50%); + z-index: 100; + visibility: hidden; + } + + &:hover::before, + &:hover::after { + visibility: visible; + } +` +const CarouselIndicatorInactive = styled(CarouselIndicator)` + &:hover > span { + opacity: 1; + } +` +const CarouselIndicatorActive = styled(CarouselIndicator)` + > span { + opacity: 1; + } +` + +const StyledUl = styled.ul` + list-style: none; + display: flex; + align-items: center; + justify-content: center; + margin: 0 !important; + padding-left: 0 !important; +` + +const WideDrawer = styled(Drawer)` + width: 500px; + position: relative; + background-color: white; +` + +const sidebarGuides: GuideChapter[] = [ + 'allGuides', 'intro', 'concepts', 'cypher', - 'movie-graph', - 'northwind-graph' + 'movies', + 'northwind' ] -const StyledMessage = styled.div`` -const GuideContent = styled.div`` +const GuideContent = styled.div` + padding-bottom: 40px; +` function GuideDrawer(): JSX.Element { + const [selectedGuide, setSelectedGuide] = useState('allGuides') + function onSelectChange(e: React.FormEvent) { + e.preventDefault() + if (!isGuideChapter(e.currentTarget.value)) { + throw Error('invalid select target') + } + setSelectedGuide(e.currentTarget.value) + } + return ( - + Neo4j Browser Guides -
dropdown goes here
- guide goes here - - You can also access Browser guides by running - :play {''} - in the main editor - + + + +
-
+ ) } - export default GuideDrawer + +import Directives from 'browser-components/Directives' +import { CarouselButton } from 'browser-components/buttons' +import { + SlidePreviousIcon, + SlideNextIcon +} from 'browser-components/icons/Icons' +import { useRef } from 'react' + +type CarouselProps = { slides?: JSX.Element[]; initialSlide?: number } +function Carousel({ + slides = [], + initialSlide = 0 +}: CarouselProps): JSX.Element { + const [currentSlideIndex, gotoSlide] = useState(initialSlide) + const currentSlide = slides[currentSlideIndex] + const onFirstSlide = currentSlideIndex === 0 + const onLastSlide = currentSlideIndex === slides.length - 1 + const scrollRef = useRef(null) + + function scrollToTop() { + if (scrollRef.current) scrollRef.current.scrollTop = 0 + } + + function nextSlide() { + if (!onLastSlide) { + gotoSlide(index => index + 1) + scrollToTop() + } + } + + function prevSlide() { + if (!onFirstSlide) { + gotoSlide(index => index - 1) + scrollToTop() + } + } + + function onKeyUp(e: React.KeyboardEvent) { + if (e.key === 'ArrowLeft') { + prevSlide() + } + if (e.key === 'ArrowRight') { + nextSlide() + } + } + + return ( + + + + + + + + {`${currentSlideIndex + 1} / ${slides.length}`} + + + + + + {slides.map((_, i) => + i !== currentSlideIndex ? ( + gotoSlide(i)} + > + + + ) : ( + gotoSlide(i)} + > + + + ) + )} + + + + + + + + ) +} diff --git a/src/browser/modules/Stream/PlayFrame.tsx b/src/browser/modules/Stream/PlayFrame.tsx index 789335b34bb..6c5780584fd 100644 --- a/src/browser/modules/Stream/PlayFrame.tsx +++ b/src/browser/modules/Stream/PlayFrame.tsx @@ -189,12 +189,12 @@ function generateContent( return { guide: ( ), hasCarousel: checkHtmlForSlides(stackFrame.result), From 4211d0fd06d59c40a6f722cef84d4b65df94a371 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Fri, 19 Mar 2021 10:50:43 +0100 Subject: [PATCH 06/64] Reset slide and add background --- .../documentation/guides/sandboxMovies.tsx | 1120 +++++++++++++++++ src/browser/modules/Sidebar/GuideDrawer.tsx | 10 +- 2 files changed, 1128 insertions(+), 2 deletions(-) create mode 100644 src/browser/documentation/guides/sandboxMovies.tsx diff --git a/src/browser/documentation/guides/sandboxMovies.tsx b/src/browser/documentation/guides/sandboxMovies.tsx new file mode 100644 index 00000000000..e50c2da603e --- /dev/null +++ b/src/browser/documentation/guides/sandboxMovies.tsx @@ -0,0 +1,1120 @@ +import React from 'react' +const title = 'sandboxMovies' + +const slides = [ +
+
+

What is Cypher?

+
+
+
+

+ Cypher is a graph query language that is used to query the Neo4j + Database. Just like you use SQL to query a MySQL database, you would + use Cypher to query the Neo4j Database. +

+
+
+

+ A simple cypher query can look something like this +

+
+
+
+
+              Match (m:Movie) where m.released > 2000 RETURN m limit 5
+            
+
+
+
+ Hint: You can click on the query above to populate it in the editor. +
+
+

+ Expected Result: The above query will return all + the movies that were released after the year 2000 limiting the + result to 5 items. +

+
+
+
+ 5 movies +
+
+
+

+ Try +

+
+
+
    +
  1. +

    + Write a query to retrieve all the movies released after the year + 2005. +

    +
    +
    +
    +                    Match (m:Movie) where m.released > 2005 RETURN m
    +                  
    +
    +
    +
  2. +
  3. +

    + Write a query to return the count of movies released after the + year 2005. (Hint: you can use the count(m) function + to return the count) +

    +
    +
    +
    +                    Match (m:Movie) where m.released > 2005 RETURN count(m)
    +                  
    +
    +
    +
  4. +
+
+
+
+
, +
+
+

Nodes and Relationships

+
+
+
+

+ Nodes and Relationships are the basic building blocks of a graph + database. +

+
+
+

+ Nodes +

+
+
+

+ Nodes represent entities. A node in graph database is similar to a + row in a relational database. In the picture below we can see 2 + kinds of nodes - Person and Movie. In + writing a cypher query, a node is enclosed between a parenthesis — + like (p:Person) where p is a variable and{' '} + Person is the type of node it is referring to. +

+
+
+
+ schema +
+
+
+

+ Relationship +

+
+
+

+ Two nodes can be connected with a relationship. In the above image{' '} + ACTED_IN, REVIEWED, PRODUCED,{' '} + WROTE and DIRECTED are all relationships + connecting the corresponding types of nodes. +

+
+
+

+ In writing a cypher query, relationships are enclosed in square + brackets - like [w:WORKS_FOR] where w is a + variable and WORKS_FOR is the type of relationship it + is referring to. +

+
+
+

+ Two nodes can be connected with more than one relationships. +

+
+
+
+
+              MATCH (p:Person)-[d:DIRECTED]-(m:Movie) where m.released > 2010
+              RETURN p,d,m
+            
+
+
+
+ Hint: You can click on the query above to populate it in the editor. +
+
+

+ Expected Result: The above query will return all + Person nodes who directed a movie that was released after 2010. +

+
+
+
+ movies after 2010 +
+
+
+

+ Try +

+
+
+
    +
  1. + b +

    + Query to get all the people who acted in a movie that was + released after 2010. +

    +
    +
    +
    +                    MATCH (p:Person)-[d:ACTED_IN]-(m:Movie) where m.released
    +                    > 2010 RETURN p,d,m
    +                  
    +
    +
    +
  2. +
+
+
+
+
+ /* + ,
+
+

Labels

+
+
+
+

+ Labels is a name or identifer of a Node or a Relationship. In the + image below Movie and Person are Node + labels and ACTED_IN, REVIEWED, etc are + Relationship labels. +

+
+
+
+ schema +
+
+
+

+ In writing a cypher query, Labels are prefixed with a colon - like{' '} + :Person or :ACTED_IN. You can assign the + node label to a variable by prefixing the syntax with the variable + name. Like (p:Person) means p variable + denoted Person labeled nodes. +

+
+
+

+ Labels are used when you want to perform operations only on a + specific types of Nodes. Like +

+
+
+
+
+              MATCH (p:Person) RETURN p limit 20
+            
+
+
+
+

+ will return only Person Nodes (limiting to 20 items) + while +

+
+
+
+
+              MATCH (n) RETURN n limit 20
+            
+
+
+
+

+ will return all kinds of nodes (limiting to 20 items). +

+
+
+
+
, +
+
+

Properties

+
+
+
+

+ Properties are name-value pairs that are used to add attributes to + nodes and relationships. +

+
+
+

+ To return specific properties of a node you can write - +

+
+
+
+
+              MATCH (m:Movie) return m.title, m.released
+            
+
+
+
+
+ movies properties +
+
+
+

+ Expected Result - This will return Movie nodes but + with only the title and released{' '} + properties. +

+
+
+

+ Try +

+
+
+
    +
  1. +

    + Write a query to get name and born{' '} + properties of the Person node. +

    +
    +
    +
    +                    MATCH (p:Person) return p.name, p.born
    +                  
    +
    +
    +
  2. +
+
+
+
+
, +
+
+

Create a Node

+
+
+
+

+ Create clause can be used to create a new node or a + relationship. +

+
+
+
+
+              Create (p:Person {'{'}name: "John Doe"{'}'}) RETURN p
+            
+
+
+
+

+ The above statement will create a new Person node with + property name having value John Doe. +

+
+
+

+ Try +

+
+
+
    +
  1. +

    + Create a new Person node with a property{' '} + name having the value of your name. +

    +
    +
    +
    +                    Create (p:Person {'{'}name: "<Your Name>"{'}'}) RETURN
    +                    p
    +                  
    +
    +
    +
  2. +
+
+
+
+
, +
+
+

+ Finding Nodes with Match and Where{' '} + Clause +

+
+
+
+

+ Match clause is used to find nodes that match a + particular pattern. This is the primary way of getting data from a + Neo4j database. +

+
+
+

+ In most cases, a Match is used along with certain + conditions to narrow down the result. +

+
+
+
+
+              Match (p:Person {'{'}name: "Tom Hanks"{'}'}) RETURN p
+            
+
+
+
+

+ This is one way of doing it. Although you can only do basic string + match based filtering this way (without using WHERE{' '} + clause). +

+
+
+

+ Another way would be to use a WHERE clause which allows + for more complex filtering including >,{' '} + <, Starts With, Ends With, + etc +

+
+
+
+
+              MATCH (p:Person) where p.name = "Tom Hanks" RETURN p
+            
+
+
+
+

+ Both of the above queries will return the same results. +

+
+
+

+ You can read more about Where clause and list of all filters here -{' '} + + https://neo4j.com/docs/cypher-manual/4.1/clauses/where/ + +

+
+
+

+ Try +

+
+
+
    +
  1. +

    Find the movie with title "Cloud Atlas"

    +
    +
    +
    +                    MATCH (m:Movie {'{'}title: "Cloud Atlas"{'}'}) return m
    +                  
    +
    +
    +
  2. +
  3. +

    + Get all the movies that were released between 2010 and 2015. +

    +
    +
    +
    +                    MATCH (m:Movie) where m.released > 2010 and m.released
    +                    < 2015 RETURN m
    +                  
    +
    +
    +
  4. +
+
+
+
+
, +
+
+

Merge Clause

+
+
+
+

+ The Merge clause is used to either +

+
+
+
    +
  1. +

    match the existing nodes and bind them or

    +
  2. +
  3. +

    create new node(s) and bind them

    +
  4. +
+
+
+

+ It is a combination of Match and Create{' '} + and additionally allows to specify additional actions if the data + was matched or created. +

+
+
+
+
+              MERGE (p:Person {'{'}name: "John Doe"{'}'}) ON MATCH SET
+              p.lastLoggedInAt = timestamp() ON CREATE SET p.createdAt =
+              timestamp() Return p
+            
+
+
+
+

+ The above statement will create the Person node if it does not + exist. If the node already exists, then it will set the property{' '} + lastLoggedInAt to the current timestamp. If node did + not exist and was newly created instead, then it will set the{' '} + createdAt property to the current timestamp. +

+
+
+

+ Try +

+
+
+
    +
  1. +

    + Write a query using Merge to create a movie node with title + "Greyhound". If the node does not exist then set its{' '} + released property to 2020 and{' '} + lastUpdatedAt property to the current time stamp. + If the node already exists, then only set{' '} + lastUpdatedAt to the current time stamp. Return the + movie node. +

    +
    +
    +
    +                    MERGE (m:movie {'{'}title: "Greyhound"{'}'}) ON MATCH SET
    +                    m.lastUpdatedAt = timestamp() ON CREATE SET m.released =
    +                    "2020", m.lastUpdatedAt = timestamp() Return m
    +                  
    +
    +
    +
  2. +
+
+
+
+
, +
+
+

Create a Relationship

+
+
+
+

+ A Relationship connects 2 nodes. +

+
+
+
+
+              MATCH (p:Person), (m:Movie) WHERE p.name = "Tom Hanks" and m.title
+              = "Cloud Atlas" CREATE (p)-[w:WATCHED]->(m) RETURN type(w)
+            
+
+
+
+

+ The above statement will create a relationship :WATCHED{' '} + between the existing Person and Movie{' '} + nodes and return the type of relationship (i.e WATCHED + ). +

+
+
+

+ Try +

+
+
+
    +
  1. +

    + Create a relationship :WATCHED between the node you + created for yourself previously in step 6 and the movie{' '} + Cloud Atlas and then return the type of created + relationship +

    +
    +
    +
    +                    MATCH (p:Person), (m:Movie) WHERE p.name = "<Your
    +                    Name>" and m.title = "Cloud Atlas" CREATE
    +                    (p)-[w:WATCHED]->(m) RETURN type(w)
    +                  
    +
    +
    +
  2. +
+
+
+
+
, +
+
+

Relationship Types

+
+
+
+

+ In Neo4j, there can be 2 kinds of relationships -{' '} + incoming and outgoing. +

+
+
+
+ relationship types +
+
+
+

+ In the above picture, the Tom Hanks node is said to have an outgoing + relationship while Forrest Gump node is said to have an incoming + relationship. +

+
+
+

+ Relationships always have a direction. However, you only have to pay + attention to the direction where it is useful. +

+
+
+

+ To denote an outgoing or an incoming relationship in cypher, we use{' '} + or . +

+
+
+

+ Example - +

+
+
+
+
+              MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p,r,m
+            
+
+
+
+

+ In the above query Person has an outgoing relationship and movie has + an incoming relationship. +

+
+
+

+ Although, in the case of the movies dataset, the direction of the + relationship is not that important and even without denoting the + direction in the query, it will return the same result. So the query + - +

+
+
+
+
+              MATCH (p:Person)-[r:ACTED_IN]-(m:Movie) RETURN p,r,m
+            
+
+
+
+

+ will return the same result as the above one. +

+
+
+

+ Try +

+
+
+
    +
  1. +

    + Write a query to find the nodes Person and{' '} + Movie which are connected by REVIEWED{' '} + relationship and is outgoing from the Person node + and incoming to the Movie node. +

    +
    +
    +
    +                    MATCH (p:Person)-[r:REVIEWED]-(m:Movie) return p,r,m
    +                  
    +
    +
    +
  2. +
+
+
+
+
, +
+
+

Advance Cypher queries

+
+
+
+

+ Let�s look at some questions that you can answer with cypher + queries. +

+
+
+
    +
  1. +

    + Finding who directed Cloud Atlas movie +

    +
    +
    +
    +                    MATCH (m:Movie {'{'}title: "Cloud Atlas"{'}'}
    +                    )<-[d:DIRECTED]-(p:Person) return p.name
    +                  
    +
    +
    +
  2. +
  3. +

    + + Finding all people who have co-acted with Tom Hanks in any + movie + +

    +
    +
    +
    +                    MATCH (tom:Person {'{'}name: "Tom Hanks"{'}'}
    +                    )-[:ACTED_IN]->(:Movie)<-[:ACTED_IN]-(p:Person) return
    +                    p.name
    +                  
    +
    +
    +
  4. +
  5. +

    + + Finding all people related to the movie Cloud Atlas in any way + +

    +
    +
    +
    +                    MATCH (p:Person)-[relatedTo]-(m:Movie {'{'}title: "Cloud
    +                    Atlas"{'}'}) return p.name, type(relatedTo)
    +                  
    +
    +
    +
    +

    + In the above query we only used the variable{' '} + relatedTo which will try to find all the + relationships between any Person node and the + movie node "Cloud Atlas" +

    +
    +
  6. +
  7. +

    + Finding Movies and Actors that are 3 hops away from Kevin Bacon. +

    +
    +
    +
    +                    MATCH (p:Person {'{'}name: "Kevin Bacon"{'}'}
    +                    )-[*1..3]-(hollywood) return DISTINCT p, hollywood
    +                  
    +
    +
    +
  8. +
+
+
+

+ Note: in the above query, hollywood refers to any node + in the database (in this case Person and{' '} + Movie nodes) +

+
+
+
+
, +
+
+

Great Job!

+
+
+
+

+ Now you know the basics of writing cypher query. You are on your way + to becoming a graphista! Congratulations. +

+
+
+

+ Feel free to play around with the data by writing more cypher + queries. If you want to learn more about cypher, you can use one of + the below resources - +

+
+
+
    +
  1. +

    + + Cypher Manual + {' '} + - detailed manual on cypher syntax +

    +
  2. +
  3. +

    + + Online Training - Introduction to Neo4j + {' '} + - If you are new to Neo4j and like to learn through an online + className, this is the best place to get started. +

    +
  4. +
+
+
+
+
+ */ +] + +export default { title, slides } diff --git a/src/browser/modules/Sidebar/GuideDrawer.tsx b/src/browser/modules/Sidebar/GuideDrawer.tsx index cd96bdded83..ad41c5a27e5 100644 --- a/src/browser/modules/Sidebar/GuideDrawer.tsx +++ b/src/browser/modules/Sidebar/GuideDrawer.tsx @@ -18,11 +18,12 @@ * along with this program. If not, see . */ -import React, { useState } from 'react' +import React, { useEffect, useState } from 'react' import { Drawer, DrawerBody, DrawerHeader } from 'browser-components/drawer' import styled from 'styled-components' import { GuideChapter, isGuideChapter } from 'browser/documentation' import docs from '../../documentation' +import Docs from '../Docs/Docs' const StyledCarousel = styled.div` padding-bottom: 20px; @@ -161,7 +162,7 @@ const StyledUl = styled.ul` const WideDrawer = styled(Drawer)` width: 500px; position: relative; - background-color: white; + background-color: ${props => props.theme.primaryBackground}; ` const sidebarGuides: GuideChapter[] = [ @@ -257,6 +258,11 @@ function Carousel({ } } + useEffect(() => { + // As slides change, switch to initial Slide + gotoSlide(initialSlide) + }, [initialSlide, slides]) + return ( From b7003803620bb0af6e7838b7b32e10181bb89934 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Sat, 10 Apr 2021 17:50:37 +0200 Subject: [PATCH 07/64] Basic guide resolving done --- .../{GuideDrawer.tsx => GuidesDrawer.tsx} | 65 +++------ src/browser/modules/Sidebar/Sidebar.tsx | 2 +- src/shared/globalState.ts | 2 + src/shared/modules/commands/helpers/play.ts | 2 +- src/shared/modules/guides/guidesDuck.ts | 64 ++++++++ .../modules/history/historyDuck.test.ts | 12 +- src/shared/modules/sidebar/sidebarDuck.ts | 1 + src/shared/rootReducer.ts | 2 + .../services/commandInterpreterHelper.ts | 22 +++ src/shared/services/guideResolverHelper.tsx | 138 ++++++++++++++++++ src/shared/services/remoteUtils.ts | 2 +- 11 files changed, 259 insertions(+), 53 deletions(-) rename src/browser/modules/Sidebar/{GuideDrawer.tsx => GuidesDrawer.tsx} (86%) create mode 100644 src/shared/modules/guides/guidesDuck.ts create mode 100644 src/shared/services/guideResolverHelper.tsx diff --git a/src/browser/modules/Sidebar/GuideDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx similarity index 86% rename from src/browser/modules/Sidebar/GuideDrawer.tsx rename to src/browser/modules/Sidebar/GuidesDrawer.tsx index ad41c5a27e5..46a7e415902 100644 --- a/src/browser/modules/Sidebar/GuideDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -21,9 +21,17 @@ import React, { useEffect, useState } from 'react' import { Drawer, DrawerBody, DrawerHeader } from 'browser-components/drawer' import styled from 'styled-components' -import { GuideChapter, isGuideChapter } from 'browser/documentation' -import docs from '../../documentation' -import Docs from '../Docs/Docs' +import Directives from 'browser-components/Directives' +import { CarouselButton } from 'browser-components/buttons' +import { + SlidePreviousIcon, + SlideNextIcon +} from 'browser-components/icons/Icons' +import { useRef } from 'react' +import { connect } from 'react-redux' +import { getGuide } from 'shared/modules/guides/guidesDuck' +import { GlobalState } from 'shared/globalState' +import { Guide } from '../../../shared/modules/guides/guidesDuck' const StyledCarousel = styled.div` padding-bottom: 20px; @@ -162,65 +170,34 @@ const StyledUl = styled.ul` const WideDrawer = styled(Drawer)` width: 500px; position: relative; - background-color: ${props => props.theme.primaryBackground}; + background-color: ${props => props.theme.secondaryBackground}; ` -const sidebarGuides: GuideChapter[] = [ - 'allGuides', - 'intro', - 'concepts', - 'cypher', - 'movies', - 'northwind' -] - const GuideContent = styled.div` padding-bottom: 40px; ` +const defaultGuide: Guide = { guideName: 'intro', initialSlide: 0, slides: [] } -function GuideDrawer(): JSX.Element { - const [selectedGuide, setSelectedGuide] = useState('allGuides') - function onSelectChange(e: React.FormEvent) { - e.preventDefault() - if (!isGuideChapter(e.currentTarget.value)) { - throw Error('invalid select target') - } - setSelectedGuide(e.currentTarget.value) - } - +type GuideDrawerProps = { guide: Guide | null } +// default är switchern +function GuideDrawer({ guide }: GuideDrawerProps): JSX.Element { + const guideOrDefault = guide ?? defaultGuide return ( Neo4j Browser Guides - - + ) } -export default GuideDrawer - -import Directives from 'browser-components/Directives' -import { CarouselButton } from 'browser-components/buttons' -import { - SlidePreviousIcon, - SlideNextIcon -} from 'browser-components/icons/Icons' -import { useRef } from 'react' +const mapStateToProps = (state: GlobalState) => ({ guide: getGuide(state) }) +export default connect(mapStateToProps)(GuideDrawer) type CarouselProps = { slides?: JSX.Element[]; initialSlide?: number } +// TODO handle there only being one slide function Carousel({ slides = [], initialSlide = 0 diff --git a/src/browser/modules/Sidebar/Sidebar.tsx b/src/browser/modules/Sidebar/Sidebar.tsx index 0537c8df4ee..08caa8a9bef 100644 --- a/src/browser/modules/Sidebar/Sidebar.tsx +++ b/src/browser/modules/Sidebar/Sidebar.tsx @@ -24,7 +24,7 @@ import DatabaseDrawer from '../DBMSInfo/DBMSInfo' import DocumentsDrawer from './Documents' import AboutDrawer from './About' import SettingsDrawer from './Settings' -import GuideDrawer from './GuideDrawer' +import GuideDrawer from './GuidesDrawer' import Favorites from './favorites' import StaticScripts from './static-scripts' import ProjectFilesDrawer from './ProjectFiles' diff --git a/src/shared/globalState.ts b/src/shared/globalState.ts index b2e417c0027..b38911b2f25 100644 --- a/src/shared/globalState.ts +++ b/src/shared/globalState.ts @@ -57,6 +57,7 @@ import { NAME as folders, Folder } from './modules/favorites/foldersDuck' import { NAME as commands } from './modules/commands/commandsDuck' import { NAME as udc, udcState } from './modules/udc/udcDuck' import { NAME as app } from './modules/app/appDuck' +import { NAME as guides, GuideState } from './modules/guides/guidesDuck' import { NAME as experimentalFeatures, initialState as experimentalFeaturesInitialState @@ -83,4 +84,5 @@ export interface GlobalState { [udc]: udcState [app]: Record [experimentalFeatures]: typeof experimentalFeaturesInitialState + [guides]: GuideState } diff --git a/src/shared/modules/commands/helpers/play.ts b/src/shared/modules/commands/helpers/play.ts index e412f74349b..a5a73a71dce 100644 --- a/src/shared/modules/commands/helpers/play.ts +++ b/src/shared/modules/commands/helpers/play.ts @@ -23,7 +23,7 @@ import { cleanHtml } from 'services/remoteUtils' import remote from 'services/remote' export const fetchRemoteGuide = (url: any, allowlist = null) => { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { if (!hostIsAllowed(url, allowlist)) { return reject( new Error('Hostname is not allowed according to server allowlist') diff --git a/src/shared/modules/guides/guidesDuck.ts b/src/shared/modules/guides/guidesDuck.ts new file mode 100644 index 00000000000..42ea0d10237 --- /dev/null +++ b/src/shared/modules/guides/guidesDuck.ts @@ -0,0 +1,64 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import { GlobalState } from 'shared/globalState' + +export const NAME = 'guides' +export const START_GUIDE = 'sidebar/START_GUIDE' + +export const getGuide = (state: GlobalState): Guide | null => state[NAME].guide +// Todo the refactor I want is for this to include +// everything needed to show the guide not this +// half measure +export type Guide = { + initialSlide: number + guideName: string + slides: JSX.Element[] +} + +export interface GuideState { + guide: Guide | null +} +const initialState: GuideState = { + guide: null +} + +type GuideAction = StartAction + +interface StartAction { + type: typeof START_GUIDE + guide: Guide +} + +export default function reducer( + state = initialState, + action: GuideAction +): GuideState { + switch (action.type) { + case START_GUIDE: + return { ...state, guide: action.guide } + default: + return state + } +} + +export function startGuide(guide: Guide): StartAction { + return { type: START_GUIDE, guide } +} diff --git a/src/shared/modules/history/historyDuck.test.ts b/src/shared/modules/history/historyDuck.test.ts index 7486e556198..bb28a77dafb 100644 --- a/src/shared/modules/history/historyDuck.test.ts +++ b/src/shared/modules/history/historyDuck.test.ts @@ -35,10 +35,10 @@ describe('editor reducer', () => { // Given const helpAction = actions.addHistory(':help', 20) const historyAction = actions.addHistory(':history', 20) - const initalState = [':help'] + const initialState = [':help'] // When - const nextState = reducer(initalState, helpAction) + const nextState = reducer(initialState, helpAction) // Then expect(nextState).toEqual([':help']) @@ -51,18 +51,18 @@ describe('editor reducer', () => { }) test('takes editor.actionTypes.SET_MAX_HISTORY into account', () => { - const initalState = [':help', ':help', ':help'] + const initialState = [':help', ':help', ':help'] const helpAction = actions.addHistory(':history', 3) - const nextState = reducer(initalState, helpAction) + const nextState = reducer(initialState, helpAction) expect(nextState).toEqual([':history', ':help', ':help']) }) test('handles editor.actionTypes.CLEAR_HISTORY', () => { // Given - const initalState = [':emily'] + const initialState = [':emily'] const anAction = actions.addHistory(':elliot', 3) - const state = reducer(initalState, anAction) + const state = reducer(initialState, anAction) // When const nextState = reducer(state, actions.clearHistory()) diff --git a/src/shared/modules/sidebar/sidebarDuck.ts b/src/shared/modules/sidebar/sidebarDuck.ts index 6a23b62490d..876a6f3746e 100644 --- a/src/shared/modules/sidebar/sidebarDuck.ts +++ b/src/shared/modules/sidebar/sidebarDuck.ts @@ -48,6 +48,7 @@ type DrawerId = | 'documents' | 'sync' | 'favorites' + | 'guides' | 'about' | 'project files' | 'settings' diff --git a/src/shared/rootReducer.ts b/src/shared/rootReducer.ts index 65c4fe96dfb..27c363d8262 100644 --- a/src/shared/rootReducer.ts +++ b/src/shared/rootReducer.ts @@ -62,6 +62,7 @@ import commandsReducer, { } from 'shared/modules/commands/commandsDuck' import udcReducer, { NAME as udc } from 'shared/modules/udc/udcDuck' import appReducer, { NAME as app } from 'shared/modules/app/appDuck' +import guideReducer, { NAME as guides } from 'shared/modules/guides/guidesDuck' import experimentalFeaturesReducer, { NAME as experimentalFeatures } from 'shared/modules/experimentalFeatures/experimentalFeaturesDuck' @@ -86,5 +87,6 @@ export default { [commands]: commandsReducer, [udc]: udcReducer, [app]: appReducer, + [guides]: guideReducer, [experimentalFeatures]: experimentalFeaturesReducer } diff --git a/src/shared/services/commandInterpreterHelper.ts b/src/shared/services/commandInterpreterHelper.ts index af894228239..23aa5eadd43 100644 --- a/src/shared/services/commandInterpreterHelper.ts +++ b/src/shared/services/commandInterpreterHelper.ts @@ -35,6 +35,8 @@ import { useDb, getUseDb } from 'shared/modules/connections/connectionsDuck' +import { open } from 'shared/modules/sidebar/sidebarDuck' +import { startGuide } from 'shared/modules/guides/guidesDuck' import { getParams } from 'shared/modules/params/paramsDuck' import { getUserCapabilities } from 'shared/modules/features/featuresDuck' import { @@ -99,6 +101,7 @@ import { } from './commandUtils' import { unescapeCypherIdentifier } from './utils' import { getLatestFromFrameStack } from 'browser/modules/Stream/stream.utils' +import { resolveGuide } from './guideResolverHelper' const PLAY_FRAME_TYPES = ['play', 'play-remote'] @@ -467,6 +470,25 @@ const availableCommands = [ } } }, + { + name: 'guide', + match: (cmd: any) => /^guide(\s|$)/.test(cmd), + exec(action: any, put: any, store: any) { + const guideName = action.cmd.substr(':guide'.length).trim() + const initialSlide = tryGetRemoteInitialSlideFromUrl(action.cmd) + resolveGuide(guideName, store).then(({ slides }) => { + put( + startGuide({ + initialSlide, + guideName, + slides + }) + ) + + put(open('guides')) + }) + } + }, { name: 'play-remote', match: (cmd: any) => /^play(\s|$)https?/.test(cmd), diff --git a/src/shared/services/guideResolverHelper.tsx b/src/shared/services/guideResolverHelper.tsx new file mode 100644 index 00000000000..330e32bdb6d --- /dev/null +++ b/src/shared/services/guideResolverHelper.tsx @@ -0,0 +1,138 @@ +import React from 'react' +import MdxSlide from 'browser/modules/Docs/MDX/MdxSlide' +import Slide from 'browser/modules/Carousel/Slide' +import docs, { isGuideChapter } from 'browser/documentation' +import guideUnfound from 'browser/documentation/guides/unfound' +import { ErrorView } from 'browser/modules/Stream/ErrorFrame' +import { + addProtocolsToUrlList, + extractAllowlistFromConfigString, + resolveAllowlistWildcard +} from './utils' +import { fetchRemoteGuide } from 'shared/modules/commands/helpers/play' +import { + getDefaultRemoteContentHostnameAllowlist, + getRemoteContentHostnameAllowlist +} from 'shared/modules/dbMeta/dbMetaDuck' +import { splitMdxSlides } from 'browser/modules/Docs/MDX/splitMdx' + +const { chapters } = docs.play +const unfound = [guideUnfound.content] + +export async function resolveGuide( + guideName: string, + store: any +): Promise<{ slides: JSX.Element[] }> { + const isUrl = guideName.startsWith('http') + if (isUrl) { + return await resolveRemoteGuideFromURL(guideName, store) + } + + if (isGuideChapter(guideName)) { + // TODO Fix so all guides have slides, to avoid this dance + const guide = chapters[guideName] + if (guide.slides) { + return { slides: guide.slides } + } + if (guide.content) { + return { slides: [guide.content] } + } + return { slides: [] } + } + + try { + const text = await resolveRemoteGuideFromName(guideName, store) + return { slides: htmlTextToSlides(text) } + } catch (e) {} + + return { slides: unfound } +} + +function mdxTextToSlides(mdx: string): JSX.Element[] { + return splitMdxSlides(mdx).map((slide, index) => ( + // index is fine since we'll never move or delete slides + + )) +} + +function htmlTextToSlides(html: string): JSX.Element[] { + const tmpDiv = document.createElement('div') + tmpDiv.innerHTML = html + const htmlSlides = tmpDiv.getElementsByTagName('slide') + if (htmlSlides && htmlSlides.length) { + return Array.from(htmlSlides).map((slide, index) => ( + + )) + } + return [] +} + +async function resolveRemoteGuideFromURL( + guideName: string, + store: any +): Promise<{ slides: JSX.Element[] }> { + const url = guideName + const urlObject = new URL(url) + urlObject.href = url + const filenameExtension = + (urlObject.pathname.includes('.') && urlObject.pathname.split('.').pop()) || + 'html' + + const allowlist = getRemoteContentHostnameAllowlist(store.getState()) + + try { + const remoteGuide = await fetchRemoteGuide(url, allowlist) + if (['md', 'mdx'].includes(filenameExtension)) { + return { + slides: mdxTextToSlides(remoteGuide) + } + } else { + return { + slides: htmlTextToSlides(remoteGuide) + } + } + } catch (e) { + if (e.response && e.response.status === 404) { + return { slides: unfound } + } + return { + slides: [ + + ] + } + } +} + +async function resolveRemoteGuideFromName( + guideName: string, + store: any +): Promise { + const allowlistStr = getRemoteContentHostnameAllowlist(store.getState()) + const allowlist = extractAllowlistFromConfigString(allowlistStr) + const defaultAllowlist = extractAllowlistFromConfigString( + getDefaultRemoteContentHostnameAllowlist(store.getState()) + ) + const resolvedWildcardAllowlist = resolveAllowlistWildcard( + allowlist, + defaultAllowlist + ) + const urlAllowlist = addProtocolsToUrlList(resolvedWildcardAllowlist) + const possibleGuidesUrls: string[] = urlAllowlist.map( + (url: string) => `${url}/${guideName}` + ) + + return possibleGuidesUrls.reduce( + (promiseChain: Promise, currentUrl: string) => + promiseChain + .catch(() => fetchRemoteGuide(currentUrl, allowlistStr)) + .then(r => Promise.resolve(r)), + Promise.reject(new Error()) + ) +} diff --git a/src/shared/services/remoteUtils.ts b/src/shared/services/remoteUtils.ts index 73e7880678f..e1fee32eccc 100644 --- a/src/shared/services/remoteUtils.ts +++ b/src/shared/services/remoteUtils.ts @@ -33,7 +33,7 @@ const removeOnHandlersFromHtml = (string: any) => '' ) -export function cleanHtml(string: any) { +export function cleanHtml(string: string): string { if (typeof string !== 'string') return string const stringWithoutHandlers = removeOnHandlersFromHtml(string) const stringWithoutScript = removeScriptTags(stringWithoutHandlers) From c594181908cd3d6e6682c5a02b7e0b5fe1bc7552 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Sat, 10 Apr 2021 19:16:50 +0200 Subject: [PATCH 08/64] Remove dialog and support fullscreen --- src/browser/modules/Frame/FrameTitlebar.tsx | 11 +++++++++++ src/browser/modules/Frame/styled.tsx | 15 ++++++++------- .../CypherFrame/VisualizationView.styled.tsx | 7 +++---- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/browser/modules/Frame/FrameTitlebar.tsx b/src/browser/modules/Frame/FrameTitlebar.tsx index 61292f6695f..0a8fdb952da 100644 --- a/src/browser/modules/Frame/FrameTitlebar.tsx +++ b/src/browser/modules/Frame/FrameTitlebar.tsx @@ -161,6 +161,17 @@ function FrameTitlebar(props: FrameTitleBarProps) { }, [props.frame.isRerun]) useEffect(gainFocusCallback, [gainFocusCallback]) + useEffect(() => { + if (props.bus && props.fullscreen) { + return props.bus.take(editor.SET_CONTENT, ({ message }) => { + setRenderEditor(true) + // timeout needed for editor to mount if not yet mounted. + setTimeout(() => editorRef.current?.setValue(message), 0) + }) + } + return undefined + }, [props.bus, props.fullscreen]) + function hasData() { return props.numRecords > 0 } diff --git a/src/browser/modules/Frame/styled.tsx b/src/browser/modules/Frame/styled.tsx index 37bdf848f1e..fd672dc7c67 100644 --- a/src/browser/modules/Frame/styled.tsx +++ b/src/browser/modules/Frame/styled.tsx @@ -31,12 +31,13 @@ export const StyledFrame = styled.article` ${props => props.fullscreen ? `margin: 0; -position: fixed; -left: 0; +position: absolute; +left: -9px; top: 0; -bottom: 0; -right: 0; -z-index: 130;` +bottom: 0px; +right: -10px; +z-index: 130; +` : 'margin 0 0 10px 0;'} &:hover .carousel-intro-animation { @@ -105,9 +106,9 @@ export const StyledFrameContents = styled.div` min-height: ${dim.frameBodyHeight / 2}px; max-height: ${props => props.fullscreen - ? '100vh' + ? 'auto' : dim.frameBodyHeight - dim.frameStatusbarHeight * 2 + 'px'}; - ${props => (props.fullscreen ? 'height: 100vh' : null)}; + ${props => (props.fullscreen ? 'height: calc(100vh - 40px)' : null)}; flex: auto; display: flex; width: 100%; diff --git a/src/browser/modules/Stream/CypherFrame/VisualizationView.styled.tsx b/src/browser/modules/Stream/CypherFrame/VisualizationView.styled.tsx index b9a3badbdb4..fdfc6a0b2db 100644 --- a/src/browser/modules/Stream/CypherFrame/VisualizationView.styled.tsx +++ b/src/browser/modules/Stream/CypherFrame/VisualizationView.styled.tsx @@ -21,13 +21,12 @@ import styled from 'styled-components' import { dim } from 'browser-styles/constants' -export const StyledVisContainer: any = styled.div` +export const StyledVisContainer = styled.div<{ fullscreen: boolean }>` width: 100%; overflow: hidden; - ${(props: any) => (props.fullscreen ? 'padding-bottom: 39px' : null)}; - height: ${(props: any) => + height: ${props => props.fullscreen - ? '100vh' + ? 'calc(100vh - 40px)' : dim.frameBodyHeight - dim.frameTitlebarHeight * 2 + 'px'}; > svg { width: 100%; From c11ccc9a108c770129c6469609b72e1090ea26df Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Sat, 10 Apr 2021 19:47:18 +0200 Subject: [PATCH 09/64] landing page --- .../components/TabNavigation/styled.tsx | 4 +- .../documentation/guides/allGuides.tsx | 26 +++++++++--- src/browser/modules/Sidebar/GuidesDrawer.tsx | 40 ++++++++++++++----- src/shared/modules/guides/guidesDuck.ts | 14 +++++-- .../services/commandInterpreterHelper.ts | 6 +++ 5 files changed, 69 insertions(+), 21 deletions(-) diff --git a/src/browser/components/TabNavigation/styled.tsx b/src/browser/components/TabNavigation/styled.tsx index eebfd116c94..321a655c999 100644 --- a/src/browser/components/TabNavigation/styled.tsx +++ b/src/browser/components/TabNavigation/styled.tsx @@ -28,12 +28,12 @@ export const StyledSidebar = styled.div` color: #fff; ` -export const StyledDrawer: any = styled.div` +export const StyledDrawer = styled.div<{ open: boolean }>` flex: 0 0 auto; background-color: #31333b; overflow-x: hidden; overflow-y: auto; - width: ${(props: any) => (props.open ? 'auto' : '0px')}; + width: ${props => (props.open ? 'auto' : '0px')}; transition: 0.2s ease-out; z-index: 1; ` diff --git a/src/browser/documentation/guides/allGuides.tsx b/src/browser/documentation/guides/allGuides.tsx index b49afd0dc2e..4fcc1011fd3 100644 --- a/src/browser/documentation/guides/allGuides.tsx +++ b/src/browser/documentation/guides/allGuides.tsx @@ -1,15 +1,29 @@ import React from 'react' -import { DrawerBrowserCommand } from 'browser-components/drawer' import Slide from 'browser/modules/Carousel/Slide' const title = 'all guides' const slides = [ - use the dropdown to pick a guide -
- You can also access Browser guides by running - :play {''} - in the main editor +

Guides in Neo4j Browser

+
] diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index 46a7e415902..63c12c032ed 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -29,9 +29,10 @@ import { } from 'browser-components/icons/Icons' import { useRef } from 'react' import { connect } from 'react-redux' -import { getGuide } from 'shared/modules/guides/guidesDuck' +import { getGuide, startGuide } from 'shared/modules/guides/guidesDuck' import { GlobalState } from 'shared/globalState' import { Guide } from '../../../shared/modules/guides/guidesDuck' +import docs from 'browser/documentation' const StyledCarousel = styled.div` padding-bottom: 20px; @@ -176,28 +177,49 @@ const WideDrawer = styled(Drawer)` const GuideContent = styled.div` padding-bottom: 40px; ` -const defaultGuide: Guide = { guideName: 'intro', initialSlide: 0, slides: [] } +// TODO fixa fina namn och bakåtknapp -type GuideDrawerProps = { guide: Guide | null } -// default är switchern -function GuideDrawer({ guide }: GuideDrawerProps): JSX.Element { - const guideOrDefault = guide ?? defaultGuide +type GuideDrawerProps = { guide: Guide; backToAllGuides: () => void } +function GuideDrawer({ + guide, + backToAllGuides +}: GuideDrawerProps): JSX.Element { return ( - Neo4j Browser Guides + + {guide.guideName !== 'allGuides' && ( +
back to all guides
+ )} + {guide.guideName} Guides{' '} +
- +
) } const mapStateToProps = (state: GlobalState) => ({ guide: getGuide(state) }) -export default connect(mapStateToProps)(GuideDrawer) +const mapDispatchToProps = (dispatch: any) => ({ + backToAllGuides: () => dispatch(startGuide()) +}) +const ConnectedGuidesDrawer = connect( + mapStateToProps, + mapDispatchToProps +)(GuideDrawer) +export default ConnectedGuidesDrawer type CarouselProps = { slides?: JSX.Element[]; initialSlide?: number } +// TODO move carousel to own file +// TODO fix styling issue when guide is longer than 100vh +// TODO there's no autocompletion for :guide +// TODO names are gargabe +// TODO it all looks ugly +// TODO test and check for all things that can go wrong +// TODO format guides to fit better // TODO handle there only being one slide +// known bugs, drops out of fullscreen and runs things in the background function Carousel({ slides = [], initialSlide = 0 diff --git a/src/shared/modules/guides/guidesDuck.ts b/src/shared/modules/guides/guidesDuck.ts index 42ea0d10237..890dedd12d8 100644 --- a/src/shared/modules/guides/guidesDuck.ts +++ b/src/shared/modules/guides/guidesDuck.ts @@ -18,12 +18,13 @@ * along with this program. If not, see . */ +import docs from 'browser/documentation' import { GlobalState } from 'shared/globalState' export const NAME = 'guides' export const START_GUIDE = 'sidebar/START_GUIDE' -export const getGuide = (state: GlobalState): Guide | null => state[NAME].guide +export const getGuide = (state: GlobalState): Guide => state[NAME].guide // Todo the refactor I want is for this to include // everything needed to show the guide not this // half measure @@ -33,11 +34,16 @@ export type Guide = { slides: JSX.Element[] } +const defaultGuide: Guide = { + guideName: 'allGuides', + initialSlide: 0, + slides: docs.play.chapters.allGuides.slides || [] +} export interface GuideState { - guide: Guide | null + guide: Guide } const initialState: GuideState = { - guide: null + guide: defaultGuide } type GuideAction = StartAction @@ -59,6 +65,6 @@ export default function reducer( } } -export function startGuide(guide: Guide): StartAction { +export function startGuide(guide: Guide = defaultGuide): StartAction { return { type: START_GUIDE, guide } } diff --git a/src/shared/services/commandInterpreterHelper.ts b/src/shared/services/commandInterpreterHelper.ts index 23aa5eadd43..1e9a5773f08 100644 --- a/src/shared/services/commandInterpreterHelper.ts +++ b/src/shared/services/commandInterpreterHelper.ts @@ -475,6 +475,12 @@ const availableCommands = [ match: (cmd: any) => /^guide(\s|$)/.test(cmd), exec(action: any, put: any, store: any) { const guideName = action.cmd.substr(':guide'.length).trim() + if (!guideName) { + // open the drawer and show default + put(open('guides')) + return + } + const initialSlide = tryGetRemoteInitialSlideFromUrl(action.cmd) resolveGuide(guideName, store).then(({ slides }) => { put( From fda3cf83ea8e7ef4e19041d471d3461fcfcbebe3 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Sun, 11 Apr 2021 10:18:59 +0200 Subject: [PATCH 10/64] Move carousel to own file --- .../documentation/guides/allGuides.tsx | 4 +- src/browser/modules/Sidebar/GuideCarousel.tsx | 113 +++++++ src/browser/modules/Sidebar/GuidesDrawer.tsx | 276 +----------------- src/browser/modules/Sidebar/styled.tsx | 150 +++++++++- .../modules/currentUser/currentUserDuck.ts | 2 +- src/shared/modules/guides/guidesDuck.ts | 7 +- .../services/commandInterpreterHelper.ts | 4 +- src/shared/services/guideResolverHelper.tsx | 51 ++-- 8 files changed, 314 insertions(+), 293 deletions(-) create mode 100644 src/browser/modules/Sidebar/GuideCarousel.tsx diff --git a/src/browser/documentation/guides/allGuides.tsx b/src/browser/documentation/guides/allGuides.tsx index 4fcc1011fd3..5ece265d067 100644 --- a/src/browser/documentation/guides/allGuides.tsx +++ b/src/browser/documentation/guides/allGuides.tsx @@ -20,8 +20,8 @@ const slides = [ :guide https://guides.neo4j.com/sandbox/movies/index.html - tip: unsure what domains are whitelisted? run{' '} - CALL dbms.clientConfig + tip: unsure what domains are whitelisted? +
CALL dbms.clientConfig
diff --git a/src/browser/modules/Sidebar/GuideCarousel.tsx b/src/browser/modules/Sidebar/GuideCarousel.tsx new file mode 100644 index 00000000000..8177f1284e0 --- /dev/null +++ b/src/browser/modules/Sidebar/GuideCarousel.tsx @@ -0,0 +1,113 @@ +import React, { useEffect, useState } from 'react' +import Directives from 'browser-components/Directives' +import { CarouselButton } from 'browser-components/buttons' +import { + SlidePreviousIcon, + SlideNextIcon +} from 'browser-components/icons/Icons' +import { useRef } from 'react' +import { + CarouselIndicatorActive, + CarouselIndicatorInactive, + SlideContainer, + StyledCarousel, + StyledCarouselButtonContainer, + StyledCarouselButtonContainerInner, + StyledCarouselCount, + StyledUl +} from './styled' + +type GuideCarouselProps = { slides?: JSX.Element[]; initialSlide?: number } +function GuideCarousel({ + slides = [], + initialSlide = 0 +}: GuideCarouselProps): JSX.Element { + const [currentSlideIndex, gotoSlide] = useState(initialSlide) + const currentSlide = slides[currentSlideIndex] + const onFirstSlide = currentSlideIndex === 0 + const onLastSlide = currentSlideIndex === slides.length - 1 + const scrollRef = useRef(null) + + function scrollToTop() { + if (scrollRef.current) scrollRef.current.scrollTop = 0 + } + + function nextSlide() { + if (!onLastSlide) { + gotoSlide(index => index + 1) + scrollToTop() + } + } + + function prevSlide() { + if (!onFirstSlide) { + gotoSlide(index => index - 1) + scrollToTop() + } + } + + function onKeyUp(e: React.KeyboardEvent) { + if (e.key === 'ArrowLeft') { + prevSlide() + } + if (e.key === 'ArrowRight') { + nextSlide() + } + } + + useEffect(() => { + // As slides change, switch to initial Slide + gotoSlide(initialSlide) + }, [initialSlide, slides]) + + return ( + + + + + + + + {`${currentSlideIndex + 1} / ${slides.length}`} + + + + + + {slides.map((_, i) => + i !== currentSlideIndex ? ( + gotoSlide(i)} + > + + + ) : ( + gotoSlide(i)} + > + + + ) + )} + + + + + + + + ) +} +export default GuideCarousel diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index 63c12c032ed..5e80b5d77a1 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -18,166 +18,23 @@ * along with this program. If not, see . */ -import React, { useEffect, useState } from 'react' -import { Drawer, DrawerBody, DrawerHeader } from 'browser-components/drawer' -import styled from 'styled-components' -import Directives from 'browser-components/Directives' -import { CarouselButton } from 'browser-components/buttons' -import { - SlidePreviousIcon, - SlideNextIcon -} from 'browser-components/icons/Icons' -import { useRef } from 'react' +import React from 'react' +import { DrawerBody, DrawerHeader } from 'browser-components/drawer' import { connect } from 'react-redux' import { getGuide, startGuide } from 'shared/modules/guides/guidesDuck' import { GlobalState } from 'shared/globalState' import { Guide } from '../../../shared/modules/guides/guidesDuck' -import docs from 'browser/documentation' +import GuideCarousel from './GuideCarousel' +import { GuideContent, WideDrawer } from './styled' -const StyledCarousel = styled.div` - padding-bottom: 20px; - min-height: 100%; - width: 100%; - outline: none; - - .row { - margin-left: 0; - margin-right: 0; - } -` - -const SlideContainer = styled.div` - padding: 0; - width: 100%; - display: inline-block; -` - -const StyledCarouselButtonContainer = styled.div` - color: ${props => props.theme.secondaryButtonText}; - display: flex; - align-items: center; - justify-content: center; - position: absolute; - bottom: 0; - z-index: 10; - border-top: ${props => props.theme.inFrameBorder}; - margin-left: -40px; - height: 39px; - width: 100%; - - .is-fullscreen & { - bottom: 39px; - } -` -const StyledCarouselButtonContainerInner = styled.div` - display: flex; - align-items: center; - justify-content: center; - border-radius: 3px; - position: relative; -` - -const StyledCarouselCount = styled.div` - display: flex; - align-items: center; - font-size: 10px; - font-weight: bold; - justify-content: flex-end; - border-radius: 3px; - min-width: 44px; - position: absolute; - right: 100%; - padding: 0; - margin-right: 10px; -` - -const CarouselIndicator = styled.li` - margin: 0; - cursor: pointer; - border-radius: 50%; - border: 3px solid transparent; - position: relative; - z-index: 1; - - > span { - background-color: ${props => props.theme.secondaryButtonText}; - display: block; - border-radius: 3px; - width: 6px; - height: 6px; - opacity: 0.4; - transition: opacity 0.1s ease-in-out; - } - - &::before { - border-radius: 2px; - content: attr(aria-label); - color: ${props => props.theme.primaryBackground}; - background-color: ${props => props.theme.primaryText}; - position: absolute; - font-size: 12px; - font-weight: bold; - left: 50%; - min-width: 24px; - bottom: calc(100% + 5px); - pointer-events: none; - transform: translateX(-50%); - padding: 5px; - line-height: 1; - text-align: center; - z-index: 100; - visibility: hidden; - } - - &::after { - border: solid; - border-color: ${props => props.theme.primaryText} transparent; - border-width: 6px 6px 0 6px; - bottom: 5px; - content: ''; - left: 50%; - pointer-events: none; - position: absolute; - transform: translateX(-50%); - z-index: 100; - visibility: hidden; - } - - &:hover::before, - &:hover::after { - visibility: visible; - } -` -const CarouselIndicatorInactive = styled(CarouselIndicator)` - &:hover > span { - opacity: 1; - } -` -const CarouselIndicatorActive = styled(CarouselIndicator)` - > span { - opacity: 1; - } -` - -const StyledUl = styled.ul` - list-style: none; - display: flex; - align-items: center; - justify-content: center; - margin: 0 !important; - padding-left: 0 !important; -` - -const WideDrawer = styled(Drawer)` - width: 500px; - position: relative; - background-color: ${props => props.theme.secondaryBackground}; -` - -const GuideContent = styled.div` - padding-bottom: 40px; -` -// TODO fixa fina namn och bakåtknapp +// TODO fix styling issue when guide is longer than 100vh +// TODO there's no autocompletion for :guide +// TODO drawer width +// TODO it all looks ugly +// TODO test and check for all things that can go wrong +// TODO format guides to fit better +// TODO handle there only being one slide +// known bugs, drops out of fullscreen and runs things in the background type GuideDrawerProps = { guide: Guide; backToAllGuides: () => void } function GuideDrawer({ @@ -187,14 +44,14 @@ function GuideDrawer({ return ( - {guide.guideName !== 'allGuides' && ( + {guide.title !== 'allGuides' && (
back to all guides
)} - {guide.guideName} Guides{' '} + {guide.title} Guides{' '}
- +
@@ -209,106 +66,3 @@ const ConnectedGuidesDrawer = connect( mapDispatchToProps )(GuideDrawer) export default ConnectedGuidesDrawer - -type CarouselProps = { slides?: JSX.Element[]; initialSlide?: number } -// TODO move carousel to own file -// TODO fix styling issue when guide is longer than 100vh -// TODO there's no autocompletion for :guide -// TODO names are gargabe -// TODO it all looks ugly -// TODO test and check for all things that can go wrong -// TODO format guides to fit better -// TODO handle there only being one slide -// known bugs, drops out of fullscreen and runs things in the background -function Carousel({ - slides = [], - initialSlide = 0 -}: CarouselProps): JSX.Element { - const [currentSlideIndex, gotoSlide] = useState(initialSlide) - const currentSlide = slides[currentSlideIndex] - const onFirstSlide = currentSlideIndex === 0 - const onLastSlide = currentSlideIndex === slides.length - 1 - const scrollRef = useRef(null) - - function scrollToTop() { - if (scrollRef.current) scrollRef.current.scrollTop = 0 - } - - function nextSlide() { - if (!onLastSlide) { - gotoSlide(index => index + 1) - scrollToTop() - } - } - - function prevSlide() { - if (!onFirstSlide) { - gotoSlide(index => index - 1) - scrollToTop() - } - } - - function onKeyUp(e: React.KeyboardEvent) { - if (e.key === 'ArrowLeft') { - prevSlide() - } - if (e.key === 'ArrowRight') { - nextSlide() - } - } - - useEffect(() => { - // As slides change, switch to initial Slide - gotoSlide(initialSlide) - }, [initialSlide, slides]) - - return ( - - - - - - - - {`${currentSlideIndex + 1} / ${slides.length}`} - - - - - - {slides.map((_, i) => - i !== currentSlideIndex ? ( - gotoSlide(i)} - > - - - ) : ( - gotoSlide(i)} - > - - - ) - )} - - - - - - - - ) -} diff --git a/src/browser/modules/Sidebar/styled.tsx b/src/browser/modules/Sidebar/styled.tsx index 41eaa29022e..8edb661098c 100644 --- a/src/browser/modules/Sidebar/styled.tsx +++ b/src/browser/modules/Sidebar/styled.tsx @@ -19,7 +19,11 @@ */ import { Button } from 'semantic-ui-react' import styled from 'styled-components' -import { DrawerBody, DrawerBrowserCommand } from 'browser-components/drawer' +import { + Drawer, + DrawerBody, + DrawerBrowserCommand +} from 'browser-components/drawer' export const StyledSetting = styled.div` padding-bottom: 15px; @@ -120,3 +124,147 @@ export const StyledFeedbackButton = styled(Button)` export const StyledCommand = styled(DrawerBrowserCommand)` max-width: 45%; ` + +export const StyledCarousel = styled.div` + padding-bottom: 20px; + min-height: 100%; + width: 100%; + outline: none; + + .row { + margin-left: 0; + margin-right: 0; + } +` + +export const SlideContainer = styled.div` + padding: 0; + width: 100%; + display: inline-block; +` + +export const StyledCarouselButtonContainer = styled.div` + color: ${props => props.theme.secondaryButtonText}; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + bottom: 0; + z-index: 10; + border-top: ${props => props.theme.inFrameBorder}; + margin-left: -40px; + height: 39px; + width: 100%; + + .is-fullscreen & { + bottom: 39px; + } +` +export const StyledCarouselButtonContainerInner = styled.div` + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + position: relative; +` + +export const StyledCarouselCount = styled.div` + display: flex; + align-items: center; + font-size: 10px; + font-weight: bold; + justify-content: flex-end; + border-radius: 3px; + min-width: 44px; + position: absolute; + right: 100%; + padding: 0; + margin-right: 10px; +` + +export const CarouselIndicator = styled.li` + margin: 0; + cursor: pointer; + border-radius: 50%; + border: 3px solid transparent; + position: relative; + z-index: 1; + + > span { + background-color: ${props => props.theme.secondaryButtonText}; + display: block; + border-radius: 3px; + width: 6px; + height: 6px; + opacity: 0.4; + transition: opacity 0.1s ease-in-out; + } + + &::before { + border-radius: 2px; + content: attr(aria-label); + color: ${props => props.theme.primaryBackground}; + background-color: ${props => props.theme.primaryText}; + position: absolute; + font-size: 12px; + font-weight: bold; + left: 50%; + min-width: 24px; + bottom: calc(100% + 5px); + pointer-events: none; + transform: translateX(-50%); + padding: 5px; + line-height: 1; + text-align: center; + z-index: 100; + visibility: hidden; + } + + &::after { + border: solid; + border-color: ${props => props.theme.primaryText} transparent; + border-width: 6px 6px 0 6px; + bottom: 5px; + content: ''; + left: 50%; + pointer-events: none; + position: absolute; + transform: translateX(-50%); + z-index: 100; + visibility: hidden; + } + + &:hover::before, + &:hover::after { + visibility: visible; + } +` +export const CarouselIndicatorInactive = styled(CarouselIndicator)` + &:hover > span { + opacity: 1; + } +` +export const CarouselIndicatorActive = styled(CarouselIndicator)` + > span { + opacity: 1; + } +` + +export const StyledUl = styled.ul` + list-style: none; + display: flex; + align-items: center; + justify-content: center; + margin: 0 !important; + padding-left: 0 !important; +` + +export const WideDrawer = styled(Drawer)` + width: 500px; + position: relative; + background-color: ${props => props.theme.secondaryBackground}; +` + +export const GuideContent = styled.div` + padding-bottom: 40px; +` diff --git a/src/shared/modules/currentUser/currentUserDuck.ts b/src/shared/modules/currentUser/currentUserDuck.ts index f93d44ddc25..ac1f3793e9c 100644 --- a/src/shared/modules/currentUser/currentUserDuck.ts +++ b/src/shared/modules/currentUser/currentUserDuck.ts @@ -56,7 +56,7 @@ export function getCurrentUser(state: any) { /** * Reducer */ -export default function user(state = initialState, action: any) { +export default function reducer(state = initialState, action: any) { switch (action.type) { case APP_START: return { ...initialState, ...state } diff --git a/src/shared/modules/guides/guidesDuck.ts b/src/shared/modules/guides/guidesDuck.ts index 890dedd12d8..9c33696f7e0 100644 --- a/src/shared/modules/guides/guidesDuck.ts +++ b/src/shared/modules/guides/guidesDuck.ts @@ -25,17 +25,14 @@ export const NAME = 'guides' export const START_GUIDE = 'sidebar/START_GUIDE' export const getGuide = (state: GlobalState): Guide => state[NAME].guide -// Todo the refactor I want is for this to include -// everything needed to show the guide not this -// half measure export type Guide = { initialSlide: number - guideName: string + title: string slides: JSX.Element[] } const defaultGuide: Guide = { - guideName: 'allGuides', + title: 'allGuides', initialSlide: 0, slides: docs.play.chapters.allGuides.slides || [] } diff --git a/src/shared/services/commandInterpreterHelper.ts b/src/shared/services/commandInterpreterHelper.ts index 1e9a5773f08..1ccf27a7684 100644 --- a/src/shared/services/commandInterpreterHelper.ts +++ b/src/shared/services/commandInterpreterHelper.ts @@ -482,11 +482,11 @@ const availableCommands = [ } const initialSlide = tryGetRemoteInitialSlideFromUrl(action.cmd) - resolveGuide(guideName, store).then(({ slides }) => { + resolveGuide(guideName, store).then(({ slides, title }) => { put( startGuide({ initialSlide, - guideName, + title, slides }) ) diff --git a/src/shared/services/guideResolverHelper.tsx b/src/shared/services/guideResolverHelper.tsx index 330e32bdb6d..14b72eff0d2 100644 --- a/src/shared/services/guideResolverHelper.tsx +++ b/src/shared/services/guideResolverHelper.tsx @@ -17,12 +17,12 @@ import { import { splitMdxSlides } from 'browser/modules/Docs/MDX/splitMdx' const { chapters } = docs.play -const unfound = [guideUnfound.content] +const unfound = { slides: [guideUnfound.content], title: guideUnfound.title } export async function resolveGuide( guideName: string, store: any -): Promise<{ slides: JSX.Element[] }> { +): Promise<{ slides: JSX.Element[]; title: string }> { const isUrl = guideName.startsWith('http') if (isUrl) { return await resolveRemoteGuideFromURL(guideName, store) @@ -31,21 +31,21 @@ export async function resolveGuide( if (isGuideChapter(guideName)) { // TODO Fix so all guides have slides, to avoid this dance const guide = chapters[guideName] + const title = guide.title if (guide.slides) { - return { slides: guide.slides } + return { slides: guide.slides, title } } if (guide.content) { - return { slides: [guide.content] } + return { slides: [guide.content], title } } - return { slides: [] } + return { slides: [], title } } try { - const text = await resolveRemoteGuideFromName(guideName, store) - return { slides: htmlTextToSlides(text) } + return await resolveRemoteGuideFromName(guideName, store) } catch (e) {} - return { slides: unfound } + return unfound } function mdxTextToSlides(mdx: string): JSX.Element[] { @@ -70,32 +70,36 @@ function htmlTextToSlides(html: string): JSX.Element[] { async function resolveRemoteGuideFromURL( guideName: string, store: any -): Promise<{ slides: JSX.Element[] }> { +): Promise<{ slides: JSX.Element[]; title: string }> { const url = guideName const urlObject = new URL(url) urlObject.href = url const filenameExtension = (urlObject.pathname.includes('.') && urlObject.pathname.split('.').pop()) || 'html' - const allowlist = getRemoteContentHostnameAllowlist(store.getState()) try { const remoteGuide = await fetchRemoteGuide(url, allowlist) + const titleRegexMatch = remoteGuide.match('(.*?)') + const title = (titleRegexMatch && titleRegexMatch[1])?.trim() || guideName if (['md', 'mdx'].includes(filenameExtension)) { return { - slides: mdxTextToSlides(remoteGuide) + slides: mdxTextToSlides(remoteGuide), + title } } else { return { - slides: htmlTextToSlides(remoteGuide) + slides: htmlTextToSlides(remoteGuide), + title } } } catch (e) { if (e.response && e.response.status === 404) { - return { slides: unfound } + return unfound } return { + title: 'Error', slides: [ { +): Promise<{ slides: JSX.Element[]; title: string }> { const allowlistStr = getRemoteContentHostnameAllowlist(store.getState()) const allowlist = extractAllowlistFromConfigString(allowlistStr) const defaultAllowlist = extractAllowlistFromConfigString( @@ -128,11 +132,16 @@ async function resolveRemoteGuideFromName( (url: string) => `${url}/${guideName}` ) - return possibleGuidesUrls.reduce( - (promiseChain: Promise, currentUrl: string) => - promiseChain - .catch(() => fetchRemoteGuide(currentUrl, allowlistStr)) - .then(r => Promise.resolve(r)), - Promise.reject(new Error()) - ) + return possibleGuidesUrls + .reduce( + (promiseChain: Promise, currentUrl: string) => + promiseChain + .catch(() => fetchRemoteGuide(currentUrl, allowlistStr)) + .then(r => Promise.resolve(r)), + Promise.reject(new Error()) + ) + .then(text => ({ + slides: htmlTextToSlides(text), + title: guideName + })) } From 2d86d5b0b5f75e94e7355659ef66ae64bafd4cde Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Sun, 11 Apr 2021 10:22:21 +0200 Subject: [PATCH 11/64] Only render next slide buttons when we have lides --- src/browser/modules/Sidebar/GuideCarousel.tsx | 87 ++++++++++--------- 1 file changed, 45 insertions(+), 42 deletions(-) diff --git a/src/browser/modules/Sidebar/GuideCarousel.tsx b/src/browser/modules/Sidebar/GuideCarousel.tsx index 8177f1284e0..7abaac712c7 100644 --- a/src/browser/modules/Sidebar/GuideCarousel.tsx +++ b/src/browser/modules/Sidebar/GuideCarousel.tsx @@ -59,54 +59,57 @@ function GuideCarousel({ // As slides change, switch to initial Slide gotoSlide(initialSlide) }, [initialSlide, slides]) + const moreThanOneSlide = slides.length > 1 return ( - - - - {`${currentSlideIndex + 1} / ${slides.length}`} - - - - - - {slides.map((_, i) => - i !== currentSlideIndex ? ( - gotoSlide(i)} - > - - - ) : ( - gotoSlide(i)} - > - - - ) - )} - - - - - - + {moreThanOneSlide && ( + + + + {`${currentSlideIndex + 1} / ${slides.length}`} + + + + + + {slides.map((_, i) => + i !== currentSlideIndex ? ( + gotoSlide(i)} + > + + + ) : ( + gotoSlide(i)} + > + + + ) + )} + + + + + + + )} ) } From a2de5c67871a392bf5ade914a6348dcc24df915b Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Mon, 12 Apr 2021 13:43:33 +0200 Subject: [PATCH 12/64] Revert "Remove dialog and support fullscreen" This reverts commit e66b02b2b5e0e9bb3209ad783e736cd4d85905eb. --- src/browser/components/ConfirmationDialog.tsx | 63 +++++++++++++++++++ src/browser/modules/Frame/FrameTitlebar.tsx | 39 ++++++++---- src/browser/modules/Frame/styled.tsx | 15 +++-- .../CypherFrame/VisualizationView.styled.tsx | 7 ++- 4 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 src/browser/components/ConfirmationDialog.tsx diff --git a/src/browser/components/ConfirmationDialog.tsx b/src/browser/components/ConfirmationDialog.tsx new file mode 100644 index 00000000000..3195d9a56d6 --- /dev/null +++ b/src/browser/components/ConfirmationDialog.tsx @@ -0,0 +1,63 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React from 'react' +import { Button, Modal } from 'semantic-ui-react' + +interface ConfirmationDialogProps { + cancelLabel?: string + confirmLabel?: string + onClose: () => void + onConfirm: () => void + open: boolean +} + +function ConfirmationDialog({ + cancelLabel = 'Cancel', + children, + confirmLabel = 'OK', + onClose, + onConfirm, + open +}: React.PropsWithChildren): JSX.Element { + return ( + onClose()} + open={open} + size="tiny" + style={{ fontSize: '16px', lineHeight: '1.5' }} + > + {children} + + +
, -
-
-

Nodes and Relationships

-
-
-
-

- Nodes and Relationships are the basic building blocks of a graph - database. -

-
-
-

- Nodes -

-
-
-

- Nodes represent entities. A node in graph database is similar to a - row in a relational database. In the picture below we can see 2 - kinds of nodes - Person and Movie. In - writing a cypher query, a node is enclosed between a parenthesis — - like (p:Person) where p is a variable and{' '} - Person is the type of node it is referring to. -

-
-
-
- schema -
-
-
-

- Relationship -

-
-
-

- Two nodes can be connected with a relationship. In the above image{' '} - ACTED_IN, REVIEWED, PRODUCED,{' '} - WROTE and DIRECTED are all relationships - connecting the corresponding types of nodes. -

-
-
-

- In writing a cypher query, relationships are enclosed in square - brackets - like [w:WORKS_FOR] where w is a - variable and WORKS_FOR is the type of relationship it - is referring to. -

-
-
-

- Two nodes can be connected with more than one relationships. -

-
-
-
-
-              MATCH (p:Person)-[d:DIRECTED]-(m:Movie) where m.released > 2010
-              RETURN p,d,m
-            
-
-
-
- Hint: You can click on the query above to populate it in the editor. -
-
-

- Expected Result: The above query will return all - Person nodes who directed a movie that was released after 2010. -

-
-
-
- movies after 2010 -
-
-
-

- Try -

-
-
-
    -
  1. - b -

    - Query to get all the people who acted in a movie that was - released after 2010. -

    -
    -
    -
    -                    MATCH (p:Person)-[d:ACTED_IN]-(m:Movie) where m.released
    -                    > 2010 RETURN p,d,m
    -                  
    -
    -
    -
  2. -
-
-
-
-
- /* - ,
-
-

Labels

-
-
-
-

- Labels is a name or identifer of a Node or a Relationship. In the - image below Movie and Person are Node - labels and ACTED_IN, REVIEWED, etc are - Relationship labels. -

-
-
-
- schema -
-
-
-

- In writing a cypher query, Labels are prefixed with a colon - like{' '} - :Person or :ACTED_IN. You can assign the - node label to a variable by prefixing the syntax with the variable - name. Like (p:Person) means p variable - denoted Person labeled nodes. -

-
-
-

- Labels are used when you want to perform operations only on a - specific types of Nodes. Like -

-
-
-
-
-              MATCH (p:Person) RETURN p limit 20
-            
-
-
-
-

- will return only Person Nodes (limiting to 20 items) - while -

-
-
-
-
-              MATCH (n) RETURN n limit 20
-            
-
-
-
-

- will return all kinds of nodes (limiting to 20 items). -

-
-
-
-
, -
-
-

Properties

-
-
-
-

- Properties are name-value pairs that are used to add attributes to - nodes and relationships. -

-
-
-

- To return specific properties of a node you can write - -

-
-
-
-
-              MATCH (m:Movie) return m.title, m.released
-            
-
-
-
-
- movies properties -
-
-
-

- Expected Result - This will return Movie nodes but - with only the title and released{' '} - properties. -

-
-
-

- Try -

-
-
-
    -
  1. -

    - Write a query to get name and born{' '} - properties of the Person node. -

    -
    -
    -
    -                    MATCH (p:Person) return p.name, p.born
    -                  
    -
    -
    -
  2. -
-
-
-
-
, -
-
-

Create a Node

-
-
-
-

- Create clause can be used to create a new node or a - relationship. -

-
-
-
-
-              Create (p:Person {'{'}name: "John Doe"{'}'}) RETURN p
-            
-
-
-
-

- The above statement will create a new Person node with - property name having value John Doe. -

-
-
-

- Try -

-
-
-
    -
  1. -

    - Create a new Person node with a property{' '} - name having the value of your name. -

    -
    -
    -
    -                    Create (p:Person {'{'}name: "<Your Name>"{'}'}) RETURN
    -                    p
    -                  
    -
    -
    -
  2. -
-
-
-
-
, -
-
-

- Finding Nodes with Match and Where{' '} - Clause -

-
-
-
-

- Match clause is used to find nodes that match a - particular pattern. This is the primary way of getting data from a - Neo4j database. -

-
-
-

- In most cases, a Match is used along with certain - conditions to narrow down the result. -

-
-
-
-
-              Match (p:Person {'{'}name: "Tom Hanks"{'}'}) RETURN p
-            
-
-
-
-

- This is one way of doing it. Although you can only do basic string - match based filtering this way (without using WHERE{' '} - clause). -

-
-
-

- Another way would be to use a WHERE clause which allows - for more complex filtering including >,{' '} - <, Starts With, Ends With, - etc -

-
-
-
-
-              MATCH (p:Person) where p.name = "Tom Hanks" RETURN p
-            
-
-
-
-

- Both of the above queries will return the same results. -

-
-
-

- You can read more about Where clause and list of all filters here -{' '} - - https://neo4j.com/docs/cypher-manual/4.1/clauses/where/ - -

-
-
-

- Try -

-
-
-
    -
  1. -

    Find the movie with title "Cloud Atlas"

    -
    -
    -
    -                    MATCH (m:Movie {'{'}title: "Cloud Atlas"{'}'}) return m
    -                  
    -
    -
    -
  2. -
  3. -

    - Get all the movies that were released between 2010 and 2015. -

    -
    -
    -
    -                    MATCH (m:Movie) where m.released > 2010 and m.released
    -                    < 2015 RETURN m
    -                  
    -
    -
    -
  4. -
-
-
-
-
, -
-
-

Merge Clause

-
-
-
-

- The Merge clause is used to either -

-
-
-
    -
  1. -

    match the existing nodes and bind them or

    -
  2. -
  3. -

    create new node(s) and bind them

    -
  4. -
-
-
-

- It is a combination of Match and Create{' '} - and additionally allows to specify additional actions if the data - was matched or created. -

-
-
-
-
-              MERGE (p:Person {'{'}name: "John Doe"{'}'}) ON MATCH SET
-              p.lastLoggedInAt = timestamp() ON CREATE SET p.createdAt =
-              timestamp() Return p
-            
-
-
-
-

- The above statement will create the Person node if it does not - exist. If the node already exists, then it will set the property{' '} - lastLoggedInAt to the current timestamp. If node did - not exist and was newly created instead, then it will set the{' '} - createdAt property to the current timestamp. -

-
-
-

- Try -

-
-
-
    -
  1. -

    - Write a query using Merge to create a movie node with title - "Greyhound". If the node does not exist then set its{' '} - released property to 2020 and{' '} - lastUpdatedAt property to the current time stamp. - If the node already exists, then only set{' '} - lastUpdatedAt to the current time stamp. Return the - movie node. -

    -
    -
    -
    -                    MERGE (m:movie {'{'}title: "Greyhound"{'}'}) ON MATCH SET
    -                    m.lastUpdatedAt = timestamp() ON CREATE SET m.released =
    -                    "2020", m.lastUpdatedAt = timestamp() Return m
    -                  
    -
    -
    -
  2. -
-
-
-
-
, -
-
-

Create a Relationship

-
-
-
-

- A Relationship connects 2 nodes. -

-
-
-
-
-              MATCH (p:Person), (m:Movie) WHERE p.name = "Tom Hanks" and m.title
-              = "Cloud Atlas" CREATE (p)-[w:WATCHED]->(m) RETURN type(w)
-            
-
-
-
-

- The above statement will create a relationship :WATCHED{' '} - between the existing Person and Movie{' '} - nodes and return the type of relationship (i.e WATCHED - ). -

-
-
-

- Try -

-
-
-
    -
  1. -

    - Create a relationship :WATCHED between the node you - created for yourself previously in step 6 and the movie{' '} - Cloud Atlas and then return the type of created - relationship -

    -
    -
    -
    -                    MATCH (p:Person), (m:Movie) WHERE p.name = "<Your
    -                    Name>" and m.title = "Cloud Atlas" CREATE
    -                    (p)-[w:WATCHED]->(m) RETURN type(w)
    -                  
    -
    -
    -
  2. -
-
-
-
-
, -
-
-

Relationship Types

-
-
-
-

- In Neo4j, there can be 2 kinds of relationships -{' '} - incoming and outgoing. -

-
-
-
- relationship types -
-
-
-

- In the above picture, the Tom Hanks node is said to have an outgoing - relationship while Forrest Gump node is said to have an incoming - relationship. -

-
-
-

- Relationships always have a direction. However, you only have to pay - attention to the direction where it is useful. -

-
-
-

- To denote an outgoing or an incoming relationship in cypher, we use{' '} - or . -

-
-
-

- Example - -

-
-
-
-
-              MATCH (p:Person)-[r:ACTED_IN]->(m:Movie) RETURN p,r,m
-            
-
-
-
-

- In the above query Person has an outgoing relationship and movie has - an incoming relationship. -

-
-
-

- Although, in the case of the movies dataset, the direction of the - relationship is not that important and even without denoting the - direction in the query, it will return the same result. So the query - - -

-
-
-
-
-              MATCH (p:Person)-[r:ACTED_IN]-(m:Movie) RETURN p,r,m
-            
-
-
-
-

- will return the same result as the above one. -

-
-
-

- Try -

-
-
-
    -
  1. -

    - Write a query to find the nodes Person and{' '} - Movie which are connected by REVIEWED{' '} - relationship and is outgoing from the Person node - and incoming to the Movie node. -

    -
    -
    -
    -                    MATCH (p:Person)-[r:REVIEWED]-(m:Movie) return p,r,m
    -                  
    -
    -
    -
  2. -
-
-
-
-
, -
-
-

Advance Cypher queries

-
-
-
-

- Let�s look at some questions that you can answer with cypher - queries. -

-
-
-
    -
  1. -

    - Finding who directed Cloud Atlas movie -

    -
    -
    -
    -                    MATCH (m:Movie {'{'}title: "Cloud Atlas"{'}'}
    -                    )<-[d:DIRECTED]-(p:Person) return p.name
    -                  
    -
    -
    -
  2. -
  3. -

    - - Finding all people who have co-acted with Tom Hanks in any - movie - -

    -
    -
    -
    -                    MATCH (tom:Person {'{'}name: "Tom Hanks"{'}'}
    -                    )-[:ACTED_IN]->(:Movie)<-[:ACTED_IN]-(p:Person) return
    -                    p.name
    -                  
    -
    -
    -
  4. -
  5. -

    - - Finding all people related to the movie Cloud Atlas in any way - -

    -
    -
    -
    -                    MATCH (p:Person)-[relatedTo]-(m:Movie {'{'}title: "Cloud
    -                    Atlas"{'}'}) return p.name, type(relatedTo)
    -                  
    -
    -
    -
    -

    - In the above query we only used the variable{' '} - relatedTo which will try to find all the - relationships between any Person node and the - movie node "Cloud Atlas" -

    -
    -
  6. -
  7. -

    - Finding Movies and Actors that are 3 hops away from Kevin Bacon. -

    -
    -
    -
    -                    MATCH (p:Person {'{'}name: "Kevin Bacon"{'}'}
    -                    )-[*1..3]-(hollywood) return DISTINCT p, hollywood
    -                  
    -
    -
    -
  8. -
-
-
-

- Note: in the above query, hollywood refers to any node - in the database (in this case Person and{' '} - Movie nodes) -

-
-
-
-
, -
-
-

Great Job!

-
-
-
-

- Now you know the basics of writing cypher query. You are on your way - to becoming a graphista! Congratulations. -

-
-
-

- Feel free to play around with the data by writing more cypher - queries. If you want to learn more about cypher, you can use one of - the below resources - -

-
-
-
    -
  1. -

    - - Cypher Manual - {' '} - - detailed manual on cypher syntax -

    -
  2. -
  3. -

    - - Online Training - Introduction to Neo4j - {' '} - - If you are new to Neo4j and like to learn through an online - className, this is the best place to get started. -

    -
  4. -
-
-
-
-
- */ -] - -export default { title, slides } diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index 5e80b5d77a1..2ee44d9bd7f 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -36,11 +36,11 @@ import { GuideContent, WideDrawer } from './styled' // TODO handle there only being one slide // known bugs, drops out of fullscreen and runs things in the background -type GuideDrawerProps = { guide: Guide; backToAllGuides: () => void } -function GuideDrawer({ +type GuidesDrawerProps = { guide: Guide; backToAllGuides: () => void } +function GuidesDrawer({ guide, backToAllGuides -}: GuideDrawerProps): JSX.Element { +}: GuidesDrawerProps): JSX.Element { return ( @@ -64,5 +64,5 @@ const mapDispatchToProps = (dispatch: any) => ({ const ConnectedGuidesDrawer = connect( mapStateToProps, mapDispatchToProps -)(GuideDrawer) +)(GuidesDrawer) export default ConnectedGuidesDrawer diff --git a/src/browser/modules/Sidebar/Sidebar.tsx b/src/browser/modules/Sidebar/Sidebar.tsx index 08caa8a9bef..d25994a2273 100644 --- a/src/browser/modules/Sidebar/Sidebar.tsx +++ b/src/browser/modules/Sidebar/Sidebar.tsx @@ -24,7 +24,7 @@ import DatabaseDrawer from '../DBMSInfo/DBMSInfo' import DocumentsDrawer from './Documents' import AboutDrawer from './About' import SettingsDrawer from './Settings' -import GuideDrawer from './GuidesDrawer' +import GuidesDrawer from './GuidesDrawer' import Favorites from './favorites' import StaticScripts from './static-scripts' import ProjectFilesDrawer from './ProjectFiles' @@ -50,7 +50,7 @@ import { SettingsIcon, AboutIcon, ProjectFilesIcon, - GuideDrawerIcon + GuidesDrawerIcon } from 'browser-components/icons/Icons' import { getCurrentDraft } from 'shared/modules/sidebar/sidebarDuck' import { DrawerHeader } from 'browser-components/drawer' @@ -124,10 +124,10 @@ const Sidebar = ({ { name: 'Guides', title: 'Guides', - icon: function guideDrawerIcon(isOpen: boolean): JSX.Element { - return + icon: function guidesDrawerIcon(isOpen: boolean): JSX.Element { + return }, - content: GuideDrawer + content: GuidesDrawer } ] From 3d577a3751552838ea27546d99754256c2918495 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Mon, 12 Apr 2021 14:54:47 +0200 Subject: [PATCH 14/64] Add new slide for guides --- build_scripts/webpack-rules.js | 19 +- .../documentation/guides/allGuides.tsx | 11 +- src/browser/modules/Carousel/Slide.tsx | 2 +- .../Carousel/{style.less => carousel.less} | 13 +- .../GuideCarousel.tsx | 6 +- .../modules/GuideCarousel/GuideSlide.tsx | 63 +++ src/browser/modules/GuideCarousel/guide.less | 389 ++++++++++++++++++ src/browser/modules/GuideCarousel/styled.tsx | 304 ++++++++++++++ src/browser/modules/Sidebar/GuidesDrawer.tsx | 19 +- src/browser/modules/Sidebar/styled.tsx | 1 - 10 files changed, 796 insertions(+), 31 deletions(-) rename src/browser/modules/Carousel/{style.less => carousel.less} (98%) rename src/browser/modules/{Sidebar => GuideCarousel}/GuideCarousel.tsx (97%) create mode 100644 src/browser/modules/GuideCarousel/GuideSlide.tsx create mode 100644 src/browser/modules/GuideCarousel/guide.less create mode 100644 src/browser/modules/GuideCarousel/styled.tsx diff --git a/build_scripts/webpack-rules.js b/build_scripts/webpack-rules.js index 649d500cc18..8d7685b204f 100644 --- a/build_scripts/webpack-rules.js +++ b/build_scripts/webpack-rules.js @@ -79,7 +79,7 @@ module.exports = [ 'file-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=assets/fonts/[name].[ext]' }, { - test: /\.less$/, // Carousel + test: /carousel\.less$/, // Carousel include: path.resolve(helpers.browserPath, 'modules/Carousel'), use: [ 'style-loader', @@ -95,6 +95,23 @@ module.exports = [ 'postcss-loader' ] }, + { + test: /guide\.less$/, + include: path.resolve(helpers.browserPath, 'modules/GuideCarousel'), + use: [ + 'style-loader', + { + loader: 'css-loader', + options: { + modules: true, + importLoaders: 1, + camelCase: true, + localIdentName: '[local]' + } + }, + 'postcss-loader' + ] + }, { test: /\.css$/, include: path.resolve(helpers.sourcePath), // css modules for component css files diff --git a/src/browser/documentation/guides/allGuides.tsx b/src/browser/documentation/guides/allGuides.tsx index 5ece265d067..aa8f04a8c37 100644 --- a/src/browser/documentation/guides/allGuides.tsx +++ b/src/browser/documentation/guides/allGuides.tsx @@ -1,5 +1,5 @@ import React from 'react' -import Slide from 'browser/modules/Carousel/Slide' +import Slide from 'browser/modules/GuideCarousel/GuideSlide' const title = 'all guides' const slides = [ @@ -8,20 +8,17 @@ const slides = [ diff --git a/src/browser/modules/Carousel/Slide.tsx b/src/browser/modules/Carousel/Slide.tsx index 563ec950f5b..29485e4dee1 100644 --- a/src/browser/modules/Carousel/Slide.tsx +++ b/src/browser/modules/Carousel/Slide.tsx @@ -18,7 +18,7 @@ * along with this program. If not, see . */ import React from 'react' -import styles from './style.less' +import styles from './carousel.less' import { StyledSlide } from './styled' const Slide = React.forwardRef(({ children, content, html }, ref) => { diff --git a/src/browser/modules/Carousel/style.less b/src/browser/modules/Carousel/carousel.less similarity index 98% rename from src/browser/modules/Carousel/style.less rename to src/browser/modules/Carousel/carousel.less index cd87d40ad4e..4dec152dbb1 100644 --- a/src/browser/modules/Carousel/style.less +++ b/src/browser/modules/Carousel/carousel.less @@ -424,9 +424,14 @@ margin: 6px 0 20px 0; overflow: hidden; } - } - .padding5 {padding: 5px;} - .padding30 {padding-top: 30px;} - .table-padding {padding-top: 10px;} + .padding5 { + padding: 5px; + } + .padding30 { + padding-top: 30px; + } + .table-padding { + padding-top: 10px; + } } diff --git a/src/browser/modules/Sidebar/GuideCarousel.tsx b/src/browser/modules/GuideCarousel/GuideCarousel.tsx similarity index 97% rename from src/browser/modules/Sidebar/GuideCarousel.tsx rename to src/browser/modules/GuideCarousel/GuideCarousel.tsx index 7abaac712c7..f6f7f65fd1f 100644 --- a/src/browser/modules/Sidebar/GuideCarousel.tsx +++ b/src/browser/modules/GuideCarousel/GuideCarousel.tsx @@ -15,10 +15,10 @@ import { StyledCarouselButtonContainerInner, StyledCarouselCount, StyledUl -} from './styled' +} from '../Sidebar/styled' type GuideCarouselProps = { slides?: JSX.Element[]; initialSlide?: number } -function GuideCarousel({ +function GuidesCarousel({ slides = [], initialSlide = 0 }: GuideCarouselProps): JSX.Element { @@ -113,4 +113,4 @@ function GuideCarousel({ ) } -export default GuideCarousel +export default GuidesCarousel diff --git a/src/browser/modules/GuideCarousel/GuideSlide.tsx b/src/browser/modules/GuideCarousel/GuideSlide.tsx new file mode 100644 index 00000000000..269a7963460 --- /dev/null +++ b/src/browser/modules/GuideCarousel/GuideSlide.tsx @@ -0,0 +1,63 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +import React from 'react' +import styles from './guide.less' +import { StyledGuideSlide } from './styled' + +type SlideProps = { + children: JSX.Element[] + content?: JSX.Element + html?: string +} +const Slide = React.forwardRef( + ({ children, content, html }: SlideProps, ref: React.Ref) => { + if (children) { + return ( + + {children} + + ) + } + + if (content) { + return ( + + {content} + + ) + } + + if (html) { + return ( + + ) + } + + return null + } +) + +Slide.displayName = 'Slide' + +export default Slide diff --git a/src/browser/modules/GuideCarousel/guide.less b/src/browser/modules/GuideCarousel/guide.less new file mode 100644 index 00000000000..e2da404999c --- /dev/null +++ b/src/browser/modules/GuideCarousel/guide.less @@ -0,0 +1,389 @@ +.guideSlide { + overflow-y: auto; + font-family: 'Open Sans', 'HelveticaNeue-Light', 'Helvetica Neue Light', + 'Helvetica Neue', Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: 400; + + & code, + & .code, + & kbd, + & pre, + & samp, + & figure pre { + font-family: 'Fira Code', 'Monaco', 'Lucida Console', Courier, monospace; + background: #f3f3f3; + color: #333; + padding-left: 0.25em; + padding-right: 0.25em; + white-space: pre-wrap; + word-break: break-all; + word-wrap: break-word; + + .disable-font-ligatures & { + font-variant-ligatures: none !important; + } + } + & th { + min-width: 50px; + } + & pre.code { + padding: 0.5em; + margin-top: 0.5em; + margin-bottom: 0.5em; + + &.pre-scrollable { + max-height: 230px; + } + &.runnable { + cursor: pointer; + border: transparent; + border-radius: 2px; + background-color: rgba(0, 0, 0, 0.1); + } + &.clicked { + border: 2px solid #8dd465; + opacity: 0.5; + } + } + & .text-right { + text-align: right; + } + & .nobreak { + word-break: keep-all; + white-space: nowrap; + } + + & .italic { + font-style: italic; + } + + & .light { + font-weight: 300; + } + + & .semi-bold { + font-weight: 600; + } + + & .bold { + font-weight: 700; + } + + & .extra-bold { + font-weight: 800; + } + + & small, + & .small, + & .btn--s { + font-size: 0.8em; + } + + & .caps { + font-variant: small-caps; + } + + & .muted { + opacity: 0.7; + } + + & h1, + & h2, + & h3, + & h4, + & h5, + & h6 { + line-height: 1.5em; + margin: 0 0 0.25em; + font-weight: 400; + } + + & h1, + & .h1 { + font-size: 2.441em; + } + & h2, + & .h2 { + font-size: 1.953em; + &.vtop { + margin-top: 0.2em; + margin-bottom: 0.35em; + } + } + & h3, + & .h3 { + font-size: 1.563em; + font-weight: 600; + &.vtop { + margin-top: 0.48em; + margin-bottom: 0.4em; + } + } + & h4, + & .h4 { + font-size: 1.25em; + &.vtop { + margin-top: 0.8em; + margin-bottom: 0.55em; + } + } + & h5, + & .h5 { + font-size: 1em; + &.vtop { + margin-top: 1.2em; + margin-bottom: 0.7em; + } + } + & h6, + & .h6 { + font-size: 0.8em; + &.vtop { + margin-top: 1.75em; + margin-bottom: 0.9em; + } + } + + & p { + margin-bottom: 1em; + &.lead { + color: #666; + } + } + + & ul, + & ol { + list-style-position: outside; + padding-left: 1em; + margin-top: 0.5em; + } + + & ul.big { + line-height: 2em; + } + + & ul.vtop { + margin-top: 0; + } + & .icon.icon-sm { + font-size: 0.5em; + } + & .icon.icon-2x { + font-size: 2em; + } + & .icon.icon-3x { + font-size: 3em; + } + & .icon.icon-4x { + font-size: 4em; + } + + & article.help { + font-size: 15px; + color: #666; + display: block; + width: 100%; + padding: 30px; + & .main { + display: flex; + flex: 0 0 25%; + } + } + & .headings { + flex: 0 0 25%; + & .title { + font-size: 24px; + font-weight: 400; + color: #333; + } + } + & .content { + p { + margin-bottom: 10px; + } + img { + max-width: 900px; + max-height: 300px; + } + } + & .example { + margin-top: 1rem; + } + & .links { + display: table; + } + & .runnable { + & pre { + cursor: pointer; + padding-left: 1em; + padding-right: 1em; + } + } + & .links { + display: table; + & .link { + display: table-row; + & .title, + & .content { + display: table-cell; + padding: 5px; + font-size: 13px; + } + & .title { + text-align: right; + font-weight: bold; + } + } + } + + & a[help-topic], + [play-topic], + [server-topic], + [exec-topic] { + background-color: #f8f8f8; + border-radius: 3px; + border: 0px; + display: inline-block; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, + Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', + sans-serif /*"Fira Code",Monaco,"Courier New",Terminal,monospace*/; + font-size: 12px; + line-height: 18px; + margin-bottom: 5px; + margin-right: 5px; + padding: 0 4px; + color: #428bca; + cursor: pointer; + text-decoration: none; + + .disable-font-ligatures & { + font-variant-ligatures: none !important; + } + } + & pre { + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + padding: 10px !important; + } + & code { + color: #fd766e; + border-radius: 2px; + + a { + color: #fd766e !important; + } + } + & figcaption { + font-style: italic; + font-size: 13px; + text-align: center; + } + & footer { + text-align: center; + } + .btn-cta { + padding: 1em; + background-color: #008cc1; + color: #fff; + border-radius: 4px; + border: 0; + display: inline-block; + } + .teasers { + display: flex; + justify-content: center; + align-items: stretch; + flex-wrap: wrap; + } + .teaser { + margin: 0.5em; + padding: 10px; + border: 0; + border-radius: 4px; + height: 270px; + float: left; + position: relative; + box-shadow: 0px 0px 2px rgba(52, 58, 67, 0.1), + 0px 1px 2px rgba(52, 58, 67, 0.08), 0px 1px 4px rgba(52, 58, 67, 0.08); + &.teaser-2 { + width: 45%; + min-width: 285px; + height: 320px; + overflow: hidden; + .icon-holder { + max-height: 160px; + overflow-y: auto; + } + .icon { + width: 12%; + } + .topic-bullets { + max-width: 80%; + } + } + &.teaser-3 { + width: 30%; + min-width: 215px; + overflow: hidden; + } + button { + font-weight: 600; + position: absolute; + bottom: 10px; + width: 90%; + left: 5%; + outline: none; + } + .icon { + float: left; + max-width: 60px; + width: 18%; + } + .icon.sl { + padding-left: 0; + font-size: 48px; + } + .icon.sl.green { + color: #65b144; + } + .icon.sl.yellow { + color: #fdcc59; + } + .icon.sl.red { + color: #ff5641; + } + .topic-bullets { + font-size: 13px; + line-height: 1.3em; + float: left; + word-wrap: break-word; + min-width: 100px; + max-width: 75%; + list-style: none; + padding-left: 10px; + margin-top: 0; + overflow: hidden; + } + .topic-bullets :first-child { + margin-top: 0; + } + .topic-bullets li { + margin-top: 5px; + } + .icon-holder { + margin: 6px 0 20px 0; + overflow: hidden; + } + } + .padding5 { + padding: 5px; + } + .padding30 { + padding-top: 30px; + } + .table-padding { + padding-top: 10px; + } +} diff --git a/src/browser/modules/GuideCarousel/styled.tsx b/src/browser/modules/GuideCarousel/styled.tsx new file mode 100644 index 00000000000..42f28b2d801 --- /dev/null +++ b/src/browser/modules/GuideCarousel/styled.tsx @@ -0,0 +1,304 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import styled from 'styled-components' +import { bounceRight } from 'browser-styles/animations' +import { dark } from 'browser-styles/themes' + +export const StyledCarousel: any = styled.div` + padding-bottom: 20px; + min-height: 100%; + width: 100%; + outline: none; + + .row { + margin-left: 0; + margin-right: 0; + } +` + +export const SlideContainer = styled.div` + padding: 0; + width: 100%; + display: inline-block; + max-height: 420px; + overflow-y: auto; +` + +export const StyledCarouselButtonContainer = styled.div` + color: ${dark.secondaryButtonText}; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + bottom: 0; + z-index: 10; + border-top: ${dark.inFrameBorder}; + margin-left: -40px; + height: 39px; + width: 100%; + + .is-fullscreen & { + bottom: 39px; + } +` +export const StyledCarouselButtonContainerInner = styled.div` + display: flex; + align-items: center; + justify-content: center; + border-radius: 3px; + position: relative; +` + +export const StyledCarouselCount = styled.div` + display: flex; + align-items: center; + font-size: 10px; + font-weight: bold; + justify-content: flex-end; + border-radius: 3px; + min-width: 44px; + position: absolute; + right: 100%; + padding: 0; + margin-right: 10px; +` + +const CarouselIndicator = styled.li` + margin: 0; + cursor: pointer; + border-radius: 50%; + border: 3px solid transparent; + position: relative; + z-index: 1; + + > span { + background-color: ${dark.secondaryButtonText}; + display: block; + border-radius: 3px; + width: 6px; + height: 6px; + opacity: 0.4; + transition: opacity 0.1s ease-in-out; + } + + &::before { + border-radius: 2px; + content: attr(aria-label); + color: ${dark.primaryBackground}; + background-color: ${dark.primaryText}; + position: absolute; + font-size: 12px; + font-weight: bold; + left: 50%; + min-width: 24px; + bottom: calc(100% + 5px); + pointer-events: none; + transform: translateX(-50%); + padding: 5px; + line-height: 1; + text-align: center; + z-index: 100; + visibility: hidden; + } + + &::after { + border: solid; + border-color: ${dark.primaryText} transparent; + border-width: 6px 6px 0 6px; + bottom: 5px; + content: ''; + left: 50%; + pointer-events: none; + position: absolute; + transform: translateX(-50%); + z-index: 100; + visibility: hidden; + } + + &:hover::before, + &:hover::after { + visibility: visible; + } +` +export const CarouselIndicatorInactive = styled(CarouselIndicator)` + &:hover > span { + opacity: 1; + } +` +export const CarouselIndicatorActive = styled(CarouselIndicator)` + > span { + opacity: 1; + } +` + +export const StyledCarouselIntroAnimated = styled.div` + align-items: center; + animation: ${bounceRight} 4s ease-in-out infinite; + animation-fill-mode: forwards; + color: #222; + display: flex; + opacity: 0.8; + position: absolute; + right: calc(100% + 50px); + pointer-events: none; + transition: opacity 0.2s ease-in-out; +` + +export const StyledCarouselIntro = styled.div` + align-items: center; + background-color: #f6e58d; + border-radius: 20px; + color: #222; + display: flex; + font-family: 'Fira Code', 'Monaco', 'Lucida Console', Courier, monospace; + font-size: 10px; + font-weight: 500; + padding: 3px 10px; + user-select: none; + white-space: nowrap; + + span:first-child { + min-width: 140px; + margin-right: 5px; + } +` + +export const StyledUl = styled.ul` + list-style: none; + display: flex; + align-items: center; + justify-content: center; + margin: 0 !important; + padding-left: 0 !important; +` +export const StyledGuideSlide = styled.div` + color: ${dark.primaryText}; + & p.lead, + .title, + .subtitle, + .content > p, + .table-help { + color: ${dark.primaryText} !important; + line-height: 1.3; + + th { + padding-right: 20px; + text-align: left; + } + + &--header { + th { + border-bottom: ${dark.topicBorder}; + font-size: 2rem; + padding: 15px 0 0 0; + } + + &:first-child { + th { + padding-top: 0; + } + } + } + + &--subheader { + th { + border-bottom: ${dark.topicBorder}; + font-size: 1.5rem; + padding: 10px 0 0 0; + } + + &:first-child { + th { + padding-top: 0; + } + } + } + + &--commands { + margin-top: 2rem; + + td { + padding: 3px 10px 3px 0; + } + } + + &--keys { + th { + border-bottom: ${dark.topicBorder}; + } + td { + padding: 3px 10px 3px 0; + } + } + } + & a { + color: ${dark.link}; + text-decoration: ${props => + props.theme.name === 'dark' ? 'underline' : 'none'}; + } + & kbd { + color: ${dark.primaryBackground} !important; /* inverted */ + background-color: ${dark.primaryText} !important; + } + & .content > pre { + background-color: ${dark.secondaryBackground}; + color: ${dark.preText}; + } + & pre.runnable { + background-color: ${dark.preBackground}; + color: ${dark.preText}; + } + & pre.content { + background-color: ${dark.secondaryBackground}; + color: ${dark.preText}; + } + & a[help-topic], + a[play-topic], + a[server-topic], + a[exec-topic] { + background-color: ${dark.topicBackground} !important; + color: #5ca6d9; + } + & button [help-topic], + button [play-topic], + button [server-topic], + button [exec-topic] { + background-color: ${dark.primaryButtonBackground}; + color: ${dark.primaryButtonText}; + } + &.slide .code { + background-color: transparent; + } + &.slide .key { + background-color: ${dark.preBackground}; + border-radius: 3px; + font-size: 12px; + display: inline-block; + padding: 0 6px; + } + + .has-carousel & { + overflow: visible; + } + &.slide .teaser { + background-color: ${dark.teaserCardBackground} !important; + } +` diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index 2ee44d9bd7f..b36f5d71eaa 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -19,22 +19,13 @@ */ import React from 'react' -import { DrawerBody, DrawerHeader } from 'browser-components/drawer' import { connect } from 'react-redux' -import { getGuide, startGuide } from 'shared/modules/guides/guidesDuck' + +import { DrawerBody, DrawerHeader } from 'browser-components/drawer' +import { getGuide, startGuide, Guide } from 'shared/modules/guides/guidesDuck' import { GlobalState } from 'shared/globalState' -import { Guide } from '../../../shared/modules/guides/guidesDuck' -import GuideCarousel from './GuideCarousel' import { GuideContent, WideDrawer } from './styled' - -// TODO fix styling issue when guide is longer than 100vh -// TODO there's no autocompletion for :guide -// TODO drawer width -// TODO it all looks ugly -// TODO test and check for all things that can go wrong -// TODO format guides to fit better -// TODO handle there only being one slide -// known bugs, drops out of fullscreen and runs things in the background +import GuidesCarousel from '../GuideCarousel/GuideCarousel' type GuidesDrawerProps = { guide: Guide; backToAllGuides: () => void } function GuidesDrawer({ @@ -51,7 +42,7 @@ function GuidesDrawer({ - + diff --git a/src/browser/modules/Sidebar/styled.tsx b/src/browser/modules/Sidebar/styled.tsx index 8edb661098c..bd1130e1b8b 100644 --- a/src/browser/modules/Sidebar/styled.tsx +++ b/src/browser/modules/Sidebar/styled.tsx @@ -262,7 +262,6 @@ export const StyledUl = styled.ul` export const WideDrawer = styled(Drawer)` width: 500px; position: relative; - background-color: ${props => props.theme.secondaryBackground}; ` export const GuideContent = styled.div` From d935a091ad620df51a74df2da34ee92027ee3634 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Mon, 12 Apr 2021 16:01:51 +0200 Subject: [PATCH 15/64] Guide migration plumbing --- build_scripts/webpack-rules.js | 19 +- src/browser/documentation/index.ts | 108 ++- .../{guides => play-guides}/concepts.tsx | 0 .../{guides => play-guides}/cypher.tsx | 0 .../{guides => play-guides}/iconography.tsx | 0 .../{guides => play-guides}/intro.tsx | 0 .../{guides => play-guides}/learn.tsx | 0 .../{guides => play-guides}/movie-graph.tsx | 0 .../northwind-graph.tsx | 0 .../{guides => play-guides}/start.tsx | 0 .../{guides => play-guides}/typography.tsx | 0 .../{guides => play-guides}/unfound.tsx | 0 .../{guides => play-guides}/write-code.tsx | 0 .../guideIndex.tsx} | 8 +- .../sidebar-guides/movie-graph.tsx | 857 ++++++++++++++++++ .../documentation/sidebar-guides/unfound.tsx | 44 + src/browser/modules/Carousel/Slide.tsx | 63 +- .../Carousel/{carousel.less => style.less} | 0 src/browser/modules/Carousel/styled.tsx | 115 +++ src/browser/modules/Docs/MDX/MdxSlide.tsx | 16 +- .../modules/GuideCarousel/GuideSlide.tsx | 63 -- src/browser/modules/GuideCarousel/guide.less | 389 -------- src/browser/modules/GuideCarousel/styled.tsx | 304 ------- src/shared/modules/guides/guidesDuck.ts | 5 +- src/shared/services/exceptions.ts | 2 +- src/shared/services/guideResolverHelper.tsx | 2 +- 26 files changed, 1136 insertions(+), 859 deletions(-) rename src/browser/documentation/{guides => play-guides}/concepts.tsx (100%) rename src/browser/documentation/{guides => play-guides}/cypher.tsx (100%) rename src/browser/documentation/{guides => play-guides}/iconography.tsx (100%) rename src/browser/documentation/{guides => play-guides}/intro.tsx (100%) rename src/browser/documentation/{guides => play-guides}/learn.tsx (100%) rename src/browser/documentation/{guides => play-guides}/movie-graph.tsx (100%) rename src/browser/documentation/{guides => play-guides}/northwind-graph.tsx (100%) rename src/browser/documentation/{guides => play-guides}/start.tsx (100%) rename src/browser/documentation/{guides => play-guides}/typography.tsx (100%) rename src/browser/documentation/{guides => play-guides}/unfound.tsx (100%) rename src/browser/documentation/{guides => play-guides}/write-code.tsx (100%) rename src/browser/documentation/{guides/allGuides.tsx => sidebar-guides/guideIndex.tsx} (72%) create mode 100644 src/browser/documentation/sidebar-guides/movie-graph.tsx create mode 100644 src/browser/documentation/sidebar-guides/unfound.tsx rename src/browser/modules/Carousel/{carousel.less => style.less} (100%) delete mode 100644 src/browser/modules/GuideCarousel/GuideSlide.tsx delete mode 100644 src/browser/modules/GuideCarousel/guide.less delete mode 100644 src/browser/modules/GuideCarousel/styled.tsx diff --git a/build_scripts/webpack-rules.js b/build_scripts/webpack-rules.js index 8d7685b204f..649d500cc18 100644 --- a/build_scripts/webpack-rules.js +++ b/build_scripts/webpack-rules.js @@ -79,7 +79,7 @@ module.exports = [ 'file-loader?limit=65000&mimetype=application/vnd.ms-fontobject&name=assets/fonts/[name].[ext]' }, { - test: /carousel\.less$/, // Carousel + test: /\.less$/, // Carousel include: path.resolve(helpers.browserPath, 'modules/Carousel'), use: [ 'style-loader', @@ -95,23 +95,6 @@ module.exports = [ 'postcss-loader' ] }, - { - test: /guide\.less$/, - include: path.resolve(helpers.browserPath, 'modules/GuideCarousel'), - use: [ - 'style-loader', - { - loader: 'css-loader', - options: { - modules: true, - importLoaders: 1, - camelCase: true, - localIdentName: '[local]' - } - }, - 'postcss-loader' - ] - }, { test: /\.css$/, include: path.resolve(helpers.sourcePath), // css modules for component css files diff --git a/src/browser/documentation/index.ts b/src/browser/documentation/index.ts index e330f7055d7..a2cba13ca71 100644 --- a/src/browser/documentation/index.ts +++ b/src/browser/documentation/index.ts @@ -88,34 +88,57 @@ import helpCypher from './dynamic/cypher' import helpHelp from './dynamic/help' import helpPlay from './dynamic/play' -// Carousels -import allGuides from './guides/allGuides' -import guideConcepts from './guides/concepts' -import guideCypher from './guides/cypher' -import guideIntro from './guides/intro' -import guideLearn from './guides/learn' -import guideMovieGraph from './guides/movie-graph' -import guideNorthwindGraph from './guides/northwind-graph' +// Play guides +import playConcepts from './play-guides/concepts' +import playCypher from './play-guides/cypher' +import playIntro from './play-guides/intro' +import playLearn from './play-guides/learn' +import playMovieGraph from './play-guides/movie-graph' +import playNorthwindGraph from './play-guides/northwind-graph' +import playIconography from './play-guides/iconography' +import playStart from './play-guides/start' +import playTypography from './play-guides/typography' +import playUnfound from './play-guides/unfound' +import playWritecode from './play-guides/write-code' -// Pages -import guideIconography from './guides/iconography' -import guideStart from './guides/start' -import guideTypography from './guides/typography' -import guideUnfound from './guides/unfound' -import guideWritecode from './guides/write-code' +// Migrated sidebar guides +import guideMovieGraph from './sidebar-guides/movie-graph' +import guideIndex from './sidebar-guides/guideIndex' +import guideUnfound from './sidebar-guides/unfound' type AllDocumentation = { help: HelpDocs cypher: CypherDocs bolt: BoltDocs - play: GuideDocs + play: PlayDocs + guide: GuideDocs } +type DocItem = { + title: string + subtitle?: string + category?: string + content?: JSX.Element | null + footer?: JSX.Element + slides?: JSX.Element[] +} + +type GuideItem = { + title: string + slides: JSX.Element[] +} + type GuideDocs = { + title: 'Built-in Browser guides' + chapters: Record +} +type GuideChapter = 'index' | 'movieGraph' | 'movies' | 'unfound' + +type PlayDocs = { title: 'Guides & Examples' - chapters: Record + chapters: Record } -export type GuideChapter = +export type PlayChapter = | 'concepts' | 'cypher' | 'iconography' @@ -129,15 +152,10 @@ export type GuideChapter = | 'typography' | 'unfound' | 'writeCode' - | 'allGuides' -type DocItem = { - title: string - subtitle?: string - category?: string - content?: JSX.Element | null - footer?: JSX.Element - slides?: JSX.Element[] +// TypeGuard function to ts to understand that a string is a valid key +export function isGuideChapter(name: string): name is PlayChapter { + return name in docs.play.chapters } type BoltDocs = { title: 'Bolt'; chapters: Record } @@ -299,27 +317,31 @@ const docs: AllDocumentation = { play: { title: 'Guides & Examples', chapters: { - allGuides: allGuides, - concepts: guideConcepts, - cypher: guideCypher, - iconography: guideIconography, - intro: guideIntro, - learn: guideLearn, - movieGraph: guideMovieGraph, + concepts: playConcepts, + cypher: playCypher, + iconography: playIconography, + intro: playIntro, + learn: playLearn, + movieGraph: playMovieGraph, + movies: playMovieGraph, + northwind: playNorthwindGraph, + northwindGraph: playNorthwindGraph, + start: playStart, + typography: playTypography, + unfound: playUnfound, + writeCode: playWritecode + } + }, + // Guides are play-guides but migrated to be viewable in the sidebar + guide: { + title: 'Built-in Browser guides', + chapters: { + index: guideIndex, movies: guideMovieGraph, - northwind: guideNorthwindGraph, - northwindGraph: guideNorthwindGraph, - start: guideStart, - typography: guideTypography, - unfound: guideUnfound, - writeCode: guideWritecode + movieGraph: guideMovieGraph, + unfound: guideUnfound } } } -// TypeGuard function to ts to understand that a string is a valid key -export function isGuideChapter(name: string): name is GuideChapter { - return name in docs.play.chapters -} - export default docs diff --git a/src/browser/documentation/guides/concepts.tsx b/src/browser/documentation/play-guides/concepts.tsx similarity index 100% rename from src/browser/documentation/guides/concepts.tsx rename to src/browser/documentation/play-guides/concepts.tsx diff --git a/src/browser/documentation/guides/cypher.tsx b/src/browser/documentation/play-guides/cypher.tsx similarity index 100% rename from src/browser/documentation/guides/cypher.tsx rename to src/browser/documentation/play-guides/cypher.tsx diff --git a/src/browser/documentation/guides/iconography.tsx b/src/browser/documentation/play-guides/iconography.tsx similarity index 100% rename from src/browser/documentation/guides/iconography.tsx rename to src/browser/documentation/play-guides/iconography.tsx diff --git a/src/browser/documentation/guides/intro.tsx b/src/browser/documentation/play-guides/intro.tsx similarity index 100% rename from src/browser/documentation/guides/intro.tsx rename to src/browser/documentation/play-guides/intro.tsx diff --git a/src/browser/documentation/guides/learn.tsx b/src/browser/documentation/play-guides/learn.tsx similarity index 100% rename from src/browser/documentation/guides/learn.tsx rename to src/browser/documentation/play-guides/learn.tsx diff --git a/src/browser/documentation/guides/movie-graph.tsx b/src/browser/documentation/play-guides/movie-graph.tsx similarity index 100% rename from src/browser/documentation/guides/movie-graph.tsx rename to src/browser/documentation/play-guides/movie-graph.tsx diff --git a/src/browser/documentation/guides/northwind-graph.tsx b/src/browser/documentation/play-guides/northwind-graph.tsx similarity index 100% rename from src/browser/documentation/guides/northwind-graph.tsx rename to src/browser/documentation/play-guides/northwind-graph.tsx diff --git a/src/browser/documentation/guides/start.tsx b/src/browser/documentation/play-guides/start.tsx similarity index 100% rename from src/browser/documentation/guides/start.tsx rename to src/browser/documentation/play-guides/start.tsx diff --git a/src/browser/documentation/guides/typography.tsx b/src/browser/documentation/play-guides/typography.tsx similarity index 100% rename from src/browser/documentation/guides/typography.tsx rename to src/browser/documentation/play-guides/typography.tsx diff --git a/src/browser/documentation/guides/unfound.tsx b/src/browser/documentation/play-guides/unfound.tsx similarity index 100% rename from src/browser/documentation/guides/unfound.tsx rename to src/browser/documentation/play-guides/unfound.tsx diff --git a/src/browser/documentation/guides/write-code.tsx b/src/browser/documentation/play-guides/write-code.tsx similarity index 100% rename from src/browser/documentation/guides/write-code.tsx rename to src/browser/documentation/play-guides/write-code.tsx diff --git a/src/browser/documentation/guides/allGuides.tsx b/src/browser/documentation/sidebar-guides/guideIndex.tsx similarity index 72% rename from src/browser/documentation/guides/allGuides.tsx rename to src/browser/documentation/sidebar-guides/guideIndex.tsx index aa8f04a8c37..e8ab346679f 100644 --- a/src/browser/documentation/guides/allGuides.tsx +++ b/src/browser/documentation/sidebar-guides/guideIndex.tsx @@ -1,11 +1,13 @@ import React from 'react' -import Slide from 'browser/modules/GuideCarousel/GuideSlide' +import { DrawerBrowserCommand } from 'browser-components/drawer' +import { SidebarSlide } from 'browser/modules/Carousel/styled' const title = 'all guides' const slides = [ - +

Guides in Neo4j Browser

    + hej
  • a guide about getting started :guide intro @@ -21,7 +23,7 @@ const slides = [
-
+ ] export default { title, slides } diff --git a/src/browser/documentation/sidebar-guides/movie-graph.tsx b/src/browser/documentation/sidebar-guides/movie-graph.tsx new file mode 100644 index 00000000000..46cd38d1b52 --- /dev/null +++ b/src/browser/documentation/sidebar-guides/movie-graph.tsx @@ -0,0 +1,857 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React from 'react' +import ManualLink from 'browser-components/ManualLink' +import Slide from '../../modules/Carousel/Slide' + +const title = 'Movie Graph' +const category = 'graphExamples' +const slides = [ + +
+

Movie Graph

+

Pop-cultural connections between actors and movies

+
+
+

+ The Movie Graph is a mini graph application containing actors + and directors that are related through the movies they've collaborated + on. +

+

This guide will show you how to:

+
    +
  1. Create: insert movie data into the graph
  2. +
  3. Find: retrieve individual movies and actors
  4. +
  5. Query: discover related actors and directors
  6. +
  7. Solve: the Bacon Path
  8. +
+

+

+ WARNING: This guide will modify the data in the currently active + database.{' '} +

+
+
, + +
+
The Movie Graph
+
+

Create

+

+ To the right is a giant code block containing a single Cypher query + statement composed of multiple CREATE clauses. This will create the + movie graph. +

+
    +
  1. Click on the code block
  2. +
  3. Notice it gets copied to the editor above ↑
  4. +
  5. Click the editor's play button to execute
  6. +
  7. Wait for the query to finish
  8. +
+

+ WARNING: This adds data to the current database, each time it is run! +

+
+

+ :help cypher{' '} + CREATE +

+
+
+
+
+          {`CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
+CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
+CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
+CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
+CREATE (Hugo:Person {name:'Hugo Weaving', born:1960})
+CREATE (LillyW:Person {name:'Lilly Wachowski', born:1967})
+CREATE (LanaW:Person {name:'Lana Wachowski', born:1965})
+CREATE (JoelS:Person {name:'Joel Silver', born:1952})
+CREATE
+(Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrix),
+(Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrix),
+(Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrix),
+(Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrix),
+(LillyW)-[:DIRECTED]->(TheMatrix),
+(LanaW)-[:DIRECTED]->(TheMatrix),
+(JoelS)-[:PRODUCED]->(TheMatrix)
+
+CREATE (Emil:Person {name:"Emil Eifrem", born:1978})
+CREATE (Emil)-[:ACTED_IN {roles:["Emil"]}]->(TheMatrix)
+
+CREATE (TheMatrixReloaded:Movie {title:'The Matrix Reloaded', released:2003, tagline:'Free your mind'})
+CREATE
+(Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixReloaded),
+(Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixReloaded),
+(Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixReloaded),
+(Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixReloaded),
+(LillyW)-[:DIRECTED]->(TheMatrixReloaded),
+(LanaW)-[:DIRECTED]->(TheMatrixReloaded),
+(JoelS)-[:PRODUCED]->(TheMatrixReloaded)
+
+CREATE (TheMatrixRevolutions:Movie {title:'The Matrix Revolutions', released:2003, tagline:'Everything that has a beginning has an end'})
+CREATE
+(Keanu)-[:ACTED_IN {roles:['Neo']}]->(TheMatrixRevolutions),
+(Carrie)-[:ACTED_IN {roles:['Trinity']}]->(TheMatrixRevolutions),
+(Laurence)-[:ACTED_IN {roles:['Morpheus']}]->(TheMatrixRevolutions),
+(Hugo)-[:ACTED_IN {roles:['Agent Smith']}]->(TheMatrixRevolutions),
+(LillyW)-[:DIRECTED]->(TheMatrixRevolutions),
+(LanaW)-[:DIRECTED]->(TheMatrixRevolutions),
+(JoelS)-[:PRODUCED]->(TheMatrixRevolutions)
+
+CREATE (TheDevilsAdvocate:Movie {title:"The Devil's Advocate", released:1997, tagline:'Evil has its winning ways'})
+CREATE (Charlize:Person {name:'Charlize Theron', born:1975})
+CREATE (Al:Person {name:'Al Pacino', born:1940})
+CREATE (Taylor:Person {name:'Taylor Hackford', born:1944})
+CREATE
+(Keanu)-[:ACTED_IN {roles:['Kevin Lomax']}]->(TheDevilsAdvocate),
+(Charlize)-[:ACTED_IN {roles:['Mary Ann Lomax']}]->(TheDevilsAdvocate),
+(Al)-[:ACTED_IN {roles:['John Milton']}]->(TheDevilsAdvocate),
+(Taylor)-[:DIRECTED]->(TheDevilsAdvocate)
+
+CREATE (AFewGoodMen:Movie {title:"A Few Good Men", released:1992, tagline:"In the heart of the nation's capital, in a courthouse of the U.S. government, one man will stop at nothing to keep his honor, and one will stop at nothing to find the truth."})
+CREATE (TomC:Person {name:'Tom Cruise', born:1962})
+CREATE (JackN:Person {name:'Jack Nicholson', born:1937})
+CREATE (DemiM:Person {name:'Demi Moore', born:1962})
+CREATE (KevinB:Person {name:'Kevin Bacon', born:1958})
+CREATE (KieferS:Person {name:'Kiefer Sutherland', born:1966})
+CREATE (NoahW:Person {name:'Noah Wyle', born:1971})
+CREATE (CubaG:Person {name:'Cuba Gooding Jr.', born:1968})
+CREATE (KevinP:Person {name:'Kevin Pollak', born:1957})
+CREATE (JTW:Person {name:'J.T. Walsh', born:1943})
+CREATE (JamesM:Person {name:'James Marshall', born:1967})
+CREATE (ChristopherG:Person {name:'Christopher Guest', born:1948})
+CREATE (RobR:Person {name:'Rob Reiner', born:1947})
+CREATE (AaronS:Person {name:'Aaron Sorkin', born:1961})
+CREATE
+(TomC)-[:ACTED_IN {roles:['Lt. Daniel Kaffee']}]->(AFewGoodMen),
+(JackN)-[:ACTED_IN {roles:['Col. Nathan R. Jessup']}]->(AFewGoodMen),
+(DemiM)-[:ACTED_IN {roles:['Lt. Cdr. JoAnne Galloway']}]->(AFewGoodMen),
+(KevinB)-[:ACTED_IN {roles:['Capt. Jack Ross']}]->(AFewGoodMen),
+(KieferS)-[:ACTED_IN {roles:['Lt. Jonathan Kendrick']}]->(AFewGoodMen),
+(NoahW)-[:ACTED_IN {roles:['Cpl. Jeffrey Barnes']}]->(AFewGoodMen),
+(CubaG)-[:ACTED_IN {roles:['Cpl. Carl Hammaker']}]->(AFewGoodMen),
+(KevinP)-[:ACTED_IN {roles:['Lt. Sam Weinberg']}]->(AFewGoodMen),
+(JTW)-[:ACTED_IN {roles:['Lt. Col. Matthew Andrew Markinson']}]->(AFewGoodMen),
+(JamesM)-[:ACTED_IN {roles:['Pfc. Louden Downey']}]->(AFewGoodMen),
+(ChristopherG)-[:ACTED_IN {roles:['Dr. Stone']}]->(AFewGoodMen),
+(AaronS)-[:ACTED_IN {roles:['Man in Bar']}]->(AFewGoodMen),
+(RobR)-[:DIRECTED]->(AFewGoodMen),
+(AaronS)-[:WROTE]->(AFewGoodMen)
+
+CREATE (TopGun:Movie {title:"Top Gun", released:1986, tagline:'I feel the need, the need for speed.'})
+CREATE (KellyM:Person {name:'Kelly McGillis', born:1957})
+CREATE (ValK:Person {name:'Val Kilmer', born:1959})
+CREATE (AnthonyE:Person {name:'Anthony Edwards', born:1962})
+CREATE (TomS:Person {name:'Tom Skerritt', born:1933})
+CREATE (MegR:Person {name:'Meg Ryan', born:1961})
+CREATE (TonyS:Person {name:'Tony Scott', born:1944})
+CREATE (JimC:Person {name:'Jim Cash', born:1941})
+CREATE
+(TomC)-[:ACTED_IN {roles:['Maverick']}]->(TopGun),
+(KellyM)-[:ACTED_IN {roles:['Charlie']}]->(TopGun),
+(ValK)-[:ACTED_IN {roles:['Iceman']}]->(TopGun),
+(AnthonyE)-[:ACTED_IN {roles:['Goose']}]->(TopGun),
+(TomS)-[:ACTED_IN {roles:['Viper']}]->(TopGun),
+(MegR)-[:ACTED_IN {roles:['Carole']}]->(TopGun),
+(TonyS)-[:DIRECTED]->(TopGun),
+(JimC)-[:WROTE]->(TopGun)
+
+CREATE (JerryMaguire:Movie {title:'Jerry Maguire', released:2000, tagline:'The rest of his life begins now.'})
+CREATE (ReneeZ:Person {name:'Renee Zellweger', born:1969})
+CREATE (KellyP:Person {name:'Kelly Preston', born:1962})
+CREATE (JerryO:Person {name:"Jerry O'Connell", born:1974})
+CREATE (JayM:Person {name:'Jay Mohr', born:1970})
+CREATE (BonnieH:Person {name:'Bonnie Hunt', born:1961})
+CREATE (ReginaK:Person {name:'Regina King', born:1971})
+CREATE (JonathanL:Person {name:'Jonathan Lipnicki', born:1996})
+CREATE (CameronC:Person {name:'Cameron Crowe', born:1957})
+CREATE
+(TomC)-[:ACTED_IN {roles:['Jerry Maguire']}]->(JerryMaguire),
+(CubaG)-[:ACTED_IN {roles:['Rod Tidwell']}]->(JerryMaguire),
+(ReneeZ)-[:ACTED_IN {roles:['Dorothy Boyd']}]->(JerryMaguire),
+(KellyP)-[:ACTED_IN {roles:['Avery Bishop']}]->(JerryMaguire),
+(JerryO)-[:ACTED_IN {roles:['Frank Cushman']}]->(JerryMaguire),
+(JayM)-[:ACTED_IN {roles:['Bob Sugar']}]->(JerryMaguire),
+(BonnieH)-[:ACTED_IN {roles:['Laurel Boyd']}]->(JerryMaguire),
+(ReginaK)-[:ACTED_IN {roles:['Marcee Tidwell']}]->(JerryMaguire),
+(JonathanL)-[:ACTED_IN {roles:['Ray Boyd']}]->(JerryMaguire),
+(CameronC)-[:DIRECTED]->(JerryMaguire),
+(CameronC)-[:PRODUCED]->(JerryMaguire),
+(CameronC)-[:WROTE]->(JerryMaguire)
+
+CREATE (StandByMe:Movie {title:"Stand By Me", released:1986, tagline:"For some, it's the last real taste of innocence, and the first real taste of life. But for everyone, it's the time that memories are made of."})
+CREATE (RiverP:Person {name:'River Phoenix', born:1970})
+CREATE (CoreyF:Person {name:'Corey Feldman', born:1971})
+CREATE (WilW:Person {name:'Wil Wheaton', born:1972})
+CREATE (JohnC:Person {name:'John Cusack', born:1966})
+CREATE (MarshallB:Person {name:'Marshall Bell', born:1942})
+CREATE
+(WilW)-[:ACTED_IN {roles:['Gordie Lachance']}]->(StandByMe),
+(RiverP)-[:ACTED_IN {roles:['Chris Chambers']}]->(StandByMe),
+(JerryO)-[:ACTED_IN {roles:['Vern Tessio']}]->(StandByMe),
+(CoreyF)-[:ACTED_IN {roles:['Teddy Duchamp']}]->(StandByMe),
+(JohnC)-[:ACTED_IN {roles:['Denny Lachance']}]->(StandByMe),
+(KieferS)-[:ACTED_IN {roles:['Ace Merrill']}]->(StandByMe),
+(MarshallB)-[:ACTED_IN {roles:['Mr. Lachance']}]->(StandByMe),
+(RobR)-[:DIRECTED]->(StandByMe)
+
+CREATE (AsGoodAsItGets:Movie {title:'As Good as It Gets', released:1997, tagline:'A comedy from the heart that goes for the throat.'})
+CREATE (HelenH:Person {name:'Helen Hunt', born:1963})
+CREATE (GregK:Person {name:'Greg Kinnear', born:1963})
+CREATE (JamesB:Person {name:'James L. Brooks', born:1940})
+CREATE
+(JackN)-[:ACTED_IN {roles:['Melvin Udall']}]->(AsGoodAsItGets),
+(HelenH)-[:ACTED_IN {roles:['Carol Connelly']}]->(AsGoodAsItGets),
+(GregK)-[:ACTED_IN {roles:['Simon Bishop']}]->(AsGoodAsItGets),
+(CubaG)-[:ACTED_IN {roles:['Frank Sachs']}]->(AsGoodAsItGets),
+(JamesB)-[:DIRECTED]->(AsGoodAsItGets)
+
+CREATE (WhatDreamsMayCome:Movie {title:'What Dreams May Come', released:1998, tagline:'After life there is more. The end is just the beginning.'})
+CREATE (AnnabellaS:Person {name:'Annabella Sciorra', born:1960})
+CREATE (MaxS:Person {name:'Max von Sydow', born:1929})
+CREATE (WernerH:Person {name:'Werner Herzog', born:1942})
+CREATE (Robin:Person {name:'Robin Williams', born:1951})
+CREATE (VincentW:Person {name:'Vincent Ward', born:1956})
+CREATE
+(Robin)-[:ACTED_IN {roles:['Chris Nielsen']}]->(WhatDreamsMayCome),
+(CubaG)-[:ACTED_IN {roles:['Albert Lewis']}]->(WhatDreamsMayCome),
+(AnnabellaS)-[:ACTED_IN {roles:['Annie Collins-Nielsen']}]->(WhatDreamsMayCome),
+(MaxS)-[:ACTED_IN {roles:['The Tracker']}]->(WhatDreamsMayCome),
+(WernerH)-[:ACTED_IN {roles:['The Face']}]->(WhatDreamsMayCome),
+(VincentW)-[:DIRECTED]->(WhatDreamsMayCome)
+
+CREATE (SnowFallingonCedars:Movie {title:'Snow Falling on Cedars', released:1999, tagline:'First loves last. Forever.'})
+CREATE (EthanH:Person {name:'Ethan Hawke', born:1970})
+CREATE (RickY:Person {name:'Rick Yune', born:1971})
+CREATE (JamesC:Person {name:'James Cromwell', born:1940})
+CREATE (ScottH:Person {name:'Scott Hicks', born:1953})
+CREATE
+(EthanH)-[:ACTED_IN {roles:['Ishmael Chambers']}]->(SnowFallingonCedars),
+(RickY)-[:ACTED_IN {roles:['Kazuo Miyamoto']}]->(SnowFallingonCedars),
+(MaxS)-[:ACTED_IN {roles:['Nels Gudmundsson']}]->(SnowFallingonCedars),
+(JamesC)-[:ACTED_IN {roles:['Judge Fielding']}]->(SnowFallingonCedars),
+(ScottH)-[:DIRECTED]->(SnowFallingonCedars)
+
+CREATE (YouveGotMail:Movie {title:"You've Got Mail", released:1998, tagline:'At odds in life... in love on-line.'})
+CREATE (ParkerP:Person {name:'Parker Posey', born:1968})
+CREATE (DaveC:Person {name:'Dave Chappelle', born:1973})
+CREATE (SteveZ:Person {name:'Steve Zahn', born:1967})
+CREATE (TomH:Person {name:'Tom Hanks', born:1956})
+CREATE (NoraE:Person {name:'Nora Ephron', born:1941})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Joe Fox']}]->(YouveGotMail),
+(MegR)-[:ACTED_IN {roles:['Kathleen Kelly']}]->(YouveGotMail),
+(GregK)-[:ACTED_IN {roles:['Frank Navasky']}]->(YouveGotMail),
+(ParkerP)-[:ACTED_IN {roles:['Patricia Eden']}]->(YouveGotMail),
+(DaveC)-[:ACTED_IN {roles:['Kevin Jackson']}]->(YouveGotMail),
+(SteveZ)-[:ACTED_IN {roles:['George Pappas']}]->(YouveGotMail),
+(NoraE)-[:DIRECTED]->(YouveGotMail)
+
+CREATE (SleeplessInSeattle:Movie {title:'Sleepless in Seattle', released:1993, tagline:'What if someone you never met, someone you never saw, someone you never knew was the only someone for you?'})
+CREATE (RitaW:Person {name:'Rita Wilson', born:1956})
+CREATE (BillPull:Person {name:'Bill Pullman', born:1953})
+CREATE (VictorG:Person {name:'Victor Garber', born:1949})
+CREATE (RosieO:Person {name:"Rosie O'Donnell", born:1962})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Sam Baldwin']}]->(SleeplessInSeattle),
+(MegR)-[:ACTED_IN {roles:['Annie Reed']}]->(SleeplessInSeattle),
+(RitaW)-[:ACTED_IN {roles:['Suzy']}]->(SleeplessInSeattle),
+(BillPull)-[:ACTED_IN {roles:['Walter']}]->(SleeplessInSeattle),
+(VictorG)-[:ACTED_IN {roles:['Greg']}]->(SleeplessInSeattle),
+(RosieO)-[:ACTED_IN {roles:['Becky']}]->(SleeplessInSeattle),
+(NoraE)-[:DIRECTED]->(SleeplessInSeattle)
+
+CREATE (JoeVersustheVolcano:Movie {title:'Joe Versus the Volcano', released:1990, tagline:'A story of love, lava and burning desire.'})
+CREATE (JohnS:Person {name:'John Patrick Stanley', born:1950})
+CREATE (Nathan:Person {name:'Nathan Lane', born:1956})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Joe Banks']}]->(JoeVersustheVolcano),
+(MegR)-[:ACTED_IN {roles:['DeDe', 'Angelica Graynamore', 'Patricia Graynamore']}]->(JoeVersustheVolcano),
+(Nathan)-[:ACTED_IN {roles:['Baw']}]->(JoeVersustheVolcano),
+(JohnS)-[:DIRECTED]->(JoeVersustheVolcano)
+
+CREATE (WhenHarryMetSally:Movie {title:'When Harry Met Sally', released:1998, tagline:'Can two friends sleep together and still love each other in the morning?'})
+CREATE (BillyC:Person {name:'Billy Crystal', born:1948})
+CREATE (CarrieF:Person {name:'Carrie Fisher', born:1956})
+CREATE (BrunoK:Person {name:'Bruno Kirby', born:1949})
+CREATE
+(BillyC)-[:ACTED_IN {roles:['Harry Burns']}]->(WhenHarryMetSally),
+(MegR)-[:ACTED_IN {roles:['Sally Albright']}]->(WhenHarryMetSally),
+(CarrieF)-[:ACTED_IN {roles:['Marie']}]->(WhenHarryMetSally),
+(BrunoK)-[:ACTED_IN {roles:['Jess']}]->(WhenHarryMetSally),
+(RobR)-[:DIRECTED]->(WhenHarryMetSally),
+(RobR)-[:PRODUCED]->(WhenHarryMetSally),
+(NoraE)-[:PRODUCED]->(WhenHarryMetSally),
+(NoraE)-[:WROTE]->(WhenHarryMetSally)
+
+CREATE (ThatThingYouDo:Movie {title:'That Thing You Do', released:1996, tagline:'In every life there comes a time when that thing you dream becomes that thing you do'})
+CREATE (LivT:Person {name:'Liv Tyler', born:1977})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Mr. White']}]->(ThatThingYouDo),
+(LivT)-[:ACTED_IN {roles:['Faye Dolan']}]->(ThatThingYouDo),
+(Charlize)-[:ACTED_IN {roles:['Tina']}]->(ThatThingYouDo),
+(TomH)-[:DIRECTED]->(ThatThingYouDo)
+
+CREATE (TheReplacements:Movie {title:'The Replacements', released:2000, tagline:'Pain heals, Chicks dig scars... Glory lasts forever'})
+CREATE (Brooke:Person {name:'Brooke Langton', born:1970})
+CREATE (Gene:Person {name:'Gene Hackman', born:1930})
+CREATE (Orlando:Person {name:'Orlando Jones', born:1968})
+CREATE (Howard:Person {name:'Howard Deutch', born:1950})
+CREATE
+(Keanu)-[:ACTED_IN {roles:['Shane Falco']}]->(TheReplacements),
+(Brooke)-[:ACTED_IN {roles:['Annabelle Farrell']}]->(TheReplacements),
+(Gene)-[:ACTED_IN {roles:['Jimmy McGinty']}]->(TheReplacements),
+(Orlando)-[:ACTED_IN {roles:['Clifford Franklin']}]->(TheReplacements),
+(Howard)-[:DIRECTED]->(TheReplacements)
+
+CREATE (RescueDawn:Movie {title:'RescueDawn', released:2006, tagline:"Based on the extraordinary true story of one man's fight for freedom"})
+CREATE (ChristianB:Person {name:'Christian Bale', born:1974})
+CREATE (ZachG:Person {name:'Zach Grenier', born:1954})
+CREATE
+(MarshallB)-[:ACTED_IN {roles:['Admiral']}]->(RescueDawn),
+(ChristianB)-[:ACTED_IN {roles:['Dieter Dengler']}]->(RescueDawn),
+(ZachG)-[:ACTED_IN {roles:['Squad Leader']}]->(RescueDawn),
+(SteveZ)-[:ACTED_IN {roles:['Duane']}]->(RescueDawn),
+(WernerH)-[:DIRECTED]->(RescueDawn)
+
+CREATE (TheBirdcage:Movie {title:'The Birdcage', released:1996, tagline:'Come as you are'})
+CREATE (MikeN:Person {name:'Mike Nichols', born:1931})
+CREATE
+(Robin)-[:ACTED_IN {roles:['Armand Goldman']}]->(TheBirdcage),
+(Nathan)-[:ACTED_IN {roles:['Albert Goldman']}]->(TheBirdcage),
+(Gene)-[:ACTED_IN {roles:['Sen. Kevin Keeley']}]->(TheBirdcage),
+(MikeN)-[:DIRECTED]->(TheBirdcage)
+
+CREATE (Unforgiven:Movie {title:'Unforgiven', released:1992, tagline:"It's a hell of a thing, killing a man"})
+CREATE (RichardH:Person {name:'Richard Harris', born:1930})
+CREATE (ClintE:Person {name:'Clint Eastwood', born:1930})
+CREATE
+(RichardH)-[:ACTED_IN {roles:['English Bob']}]->(Unforgiven),
+(ClintE)-[:ACTED_IN {roles:['Bill Munny']}]->(Unforgiven),
+(Gene)-[:ACTED_IN {roles:['Little Bill Daggett']}]->(Unforgiven),
+(ClintE)-[:DIRECTED]->(Unforgiven)
+
+CREATE (JohnnyMnemonic:Movie {title:'Johnny Mnemonic', released:1995, tagline:'The hottest data on earth. In the coolest head in town'})
+CREATE (Takeshi:Person {name:'Takeshi Kitano', born:1947})
+CREATE (Dina:Person {name:'Dina Meyer', born:1968})
+CREATE (IceT:Person {name:'Ice-T', born:1958})
+CREATE (RobertL:Person {name:'Robert Longo', born:1953})
+CREATE
+(Keanu)-[:ACTED_IN {roles:['Johnny Mnemonic']}]->(JohnnyMnemonic),
+(Takeshi)-[:ACTED_IN {roles:['Takahashi']}]->(JohnnyMnemonic),
+(Dina)-[:ACTED_IN {roles:['Jane']}]->(JohnnyMnemonic),
+(IceT)-[:ACTED_IN {roles:['J-Bone']}]->(JohnnyMnemonic),
+(RobertL)-[:DIRECTED]->(JohnnyMnemonic)
+
+CREATE (CloudAtlas:Movie {title:'Cloud Atlas', released:2012, tagline:'Everything is connected'})
+CREATE (HalleB:Person {name:'Halle Berry', born:1966})
+CREATE (JimB:Person {name:'Jim Broadbent', born:1949})
+CREATE (TomT:Person {name:'Tom Tykwer', born:1965})
+CREATE (DavidMitchell:Person {name:'David Mitchell', born:1969})
+CREATE (StefanArndt:Person {name:'Stefan Arndt', born:1961})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Zachry', 'Dr. Henry Goose', 'Isaac Sachs', 'Dermot Hoggins']}]->(CloudAtlas),
+(Hugo)-[:ACTED_IN {roles:['Bill Smoke', 'Haskell Moore', 'Tadeusz Kesselring', 'Nurse Noakes', 'Boardman Mephi', 'Old Georgie']}]->(CloudAtlas),
+(HalleB)-[:ACTED_IN {roles:['Luisa Rey', 'Jocasta Ayrs', 'Ovid', 'Meronym']}]->(CloudAtlas),
+(JimB)-[:ACTED_IN {roles:['Vyvyan Ayrs', 'Captain Molyneux', 'Timothy Cavendish']}]->(CloudAtlas),
+(TomT)-[:DIRECTED]->(CloudAtlas),
+(LillyW)-[:DIRECTED]->(CloudAtlas),
+(LanaW)-[:DIRECTED]->(CloudAtlas),
+(DavidMitchell)-[:WROTE]->(CloudAtlas),
+(StefanArndt)-[:PRODUCED]->(CloudAtlas)
+
+CREATE (TheDaVinciCode:Movie {title:'The Da Vinci Code', released:2006, tagline:'Break The Codes'})
+CREATE (IanM:Person {name:'Ian McKellen', born:1939})
+CREATE (AudreyT:Person {name:'Audrey Tautou', born:1976})
+CREATE (PaulB:Person {name:'Paul Bettany', born:1971})
+CREATE (RonH:Person {name:'Ron Howard', born:1954})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Dr. Robert Langdon']}]->(TheDaVinciCode),
+(IanM)-[:ACTED_IN {roles:['Sir Leight Teabing']}]->(TheDaVinciCode),
+(AudreyT)-[:ACTED_IN {roles:['Sophie Neveu']}]->(TheDaVinciCode),
+(PaulB)-[:ACTED_IN {roles:['Silas']}]->(TheDaVinciCode),
+(RonH)-[:DIRECTED]->(TheDaVinciCode)
+
+CREATE (VforVendetta:Movie {title:'V for Vendetta', released:2006, tagline:'Freedom! Forever!'})
+CREATE (NatalieP:Person {name:'Natalie Portman', born:1981})
+CREATE (StephenR:Person {name:'Stephen Rea', born:1946})
+CREATE (JohnH:Person {name:'John Hurt', born:1940})
+CREATE (BenM:Person {name: 'Ben Miles', born:1967})
+CREATE
+(Hugo)-[:ACTED_IN {roles:['V']}]->(VforVendetta),
+(NatalieP)-[:ACTED_IN {roles:['Evey Hammond']}]->(VforVendetta),
+(StephenR)-[:ACTED_IN {roles:['Eric Finch']}]->(VforVendetta),
+(JohnH)-[:ACTED_IN {roles:['High Chancellor Adam Sutler']}]->(VforVendetta),
+(BenM)-[:ACTED_IN {roles:['Dascomb']}]->(VforVendetta),
+(JamesM)-[:DIRECTED]->(VforVendetta),
+(LillyW)-[:PRODUCED]->(VforVendetta),
+(LanaW)-[:PRODUCED]->(VforVendetta),
+(JoelS)-[:PRODUCED]->(VforVendetta),
+(LillyW)-[:WROTE]->(VforVendetta),
+(LanaW)-[:WROTE]->(VforVendetta)
+
+CREATE (SpeedRacer:Movie {title:'Speed Racer', released:2008, tagline:'Speed has no limits'})
+CREATE (EmileH:Person {name:'Emile Hirsch', born:1985})
+CREATE (JohnG:Person {name:'John Goodman', born:1960})
+CREATE (SusanS:Person {name:'Susan Sarandon', born:1946})
+CREATE (MatthewF:Person {name:'Matthew Fox', born:1966})
+CREATE (ChristinaR:Person {name:'Christina Ricci', born:1980})
+CREATE (Rain:Person {name:'Rain', born:1982})
+CREATE
+(EmileH)-[:ACTED_IN {roles:['Speed Racer']}]->(SpeedRacer),
+(JohnG)-[:ACTED_IN {roles:['Pops']}]->(SpeedRacer),
+(SusanS)-[:ACTED_IN {roles:['Mom']}]->(SpeedRacer),
+(MatthewF)-[:ACTED_IN {roles:['Racer X']}]->(SpeedRacer),
+(ChristinaR)-[:ACTED_IN {roles:['Trixie']}]->(SpeedRacer),
+(Rain)-[:ACTED_IN {roles:['Taejo Togokahn']}]->(SpeedRacer),
+(BenM)-[:ACTED_IN {roles:['Cass Jones']}]->(SpeedRacer),
+(LillyW)-[:DIRECTED]->(SpeedRacer),
+(LanaW)-[:DIRECTED]->(SpeedRacer),
+(LillyW)-[:WROTE]->(SpeedRacer),
+(LanaW)-[:WROTE]->(SpeedRacer),
+(JoelS)-[:PRODUCED]->(SpeedRacer)
+
+CREATE (NinjaAssassin:Movie {title:'Ninja Assassin', released:2009, tagline:'Prepare to enter a secret world of assassins'})
+CREATE (NaomieH:Person {name:'Naomie Harris'})
+CREATE
+(Rain)-[:ACTED_IN {roles:['Raizo']}]->(NinjaAssassin),
+(NaomieH)-[:ACTED_IN {roles:['Mika Coretti']}]->(NinjaAssassin),
+(RickY)-[:ACTED_IN {roles:['Takeshi']}]->(NinjaAssassin),
+(BenM)-[:ACTED_IN {roles:['Ryan Maslow']}]->(NinjaAssassin),
+(JamesM)-[:DIRECTED]->(NinjaAssassin),
+(LillyW)-[:PRODUCED]->(NinjaAssassin),
+(LanaW)-[:PRODUCED]->(NinjaAssassin),
+(JoelS)-[:PRODUCED]->(NinjaAssassin)
+
+CREATE (TheGreenMile:Movie {title:'The Green Mile', released:1999, tagline:"Walk a mile you'll never forget."})
+CREATE (MichaelD:Person {name:'Michael Clarke Duncan', born:1957})
+CREATE (DavidM:Person {name:'David Morse', born:1953})
+CREATE (SamR:Person {name:'Sam Rockwell', born:1968})
+CREATE (GaryS:Person {name:'Gary Sinise', born:1955})
+CREATE (PatriciaC:Person {name:'Patricia Clarkson', born:1959})
+CREATE (FrankD:Person {name:'Frank Darabont', born:1959})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Paul Edgecomb']}]->(TheGreenMile),
+(MichaelD)-[:ACTED_IN {roles:['John Coffey']}]->(TheGreenMile),
+(DavidM)-[:ACTED_IN {roles:['Brutus "Brutal" Howell']}]->(TheGreenMile),
+(BonnieH)-[:ACTED_IN {roles:['Jan Edgecomb']}]->(TheGreenMile),
+(JamesC)-[:ACTED_IN {roles:['Warden Hal Moores']}]->(TheGreenMile),
+(SamR)-[:ACTED_IN {roles:['"Wild Bill" Wharton']}]->(TheGreenMile),
+(GaryS)-[:ACTED_IN {roles:['Burt Hammersmith']}]->(TheGreenMile),
+(PatriciaC)-[:ACTED_IN {roles:['Melinda Moores']}]->(TheGreenMile),
+(FrankD)-[:DIRECTED]->(TheGreenMile)
+
+CREATE (FrostNixon:Movie {title:'Frost/Nixon', released:2008, tagline:'400 million people were waiting for the truth.'})
+CREATE (FrankL:Person {name:'Frank Langella', born:1938})
+CREATE (MichaelS:Person {name:'Michael Sheen', born:1969})
+CREATE (OliverP:Person {name:'Oliver Platt', born:1960})
+CREATE
+(FrankL)-[:ACTED_IN {roles:['Richard Nixon']}]->(FrostNixon),
+(MichaelS)-[:ACTED_IN {roles:['David Frost']}]->(FrostNixon),
+(KevinB)-[:ACTED_IN {roles:['Jack Brennan']}]->(FrostNixon),
+(OliverP)-[:ACTED_IN {roles:['Bob Zelnick']}]->(FrostNixon),
+(SamR)-[:ACTED_IN {roles:['James Reston, Jr.']}]->(FrostNixon),
+(RonH)-[:DIRECTED]->(FrostNixon)
+
+CREATE (Hoffa:Movie {title:'Hoffa', released:1992, tagline:"He didn't want law. He wanted justice."})
+CREATE (DannyD:Person {name:'Danny DeVito', born:1944})
+CREATE (JohnR:Person {name:'John C. Reilly', born:1965})
+CREATE
+(JackN)-[:ACTED_IN {roles:['Hoffa']}]->(Hoffa),
+(DannyD)-[:ACTED_IN {roles:['Robert "Bobby" Ciaro']}]->(Hoffa),
+(JTW)-[:ACTED_IN {roles:['Frank Fitzsimmons']}]->(Hoffa),
+(JohnR)-[:ACTED_IN {roles:['Peter "Pete" Connelly']}]->(Hoffa),
+(DannyD)-[:DIRECTED]->(Hoffa)
+
+CREATE (Apollo13:Movie {title:'Apollo 13', released:1995, tagline:'Houston, we have a problem.'})
+CREATE (EdH:Person {name:'Ed Harris', born:1950})
+CREATE (BillPax:Person {name:'Bill Paxton', born:1955})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Jim Lovell']}]->(Apollo13),
+(KevinB)-[:ACTED_IN {roles:['Jack Swigert']}]->(Apollo13),
+(EdH)-[:ACTED_IN {roles:['Gene Kranz']}]->(Apollo13),
+(BillPax)-[:ACTED_IN {roles:['Fred Haise']}]->(Apollo13),
+(GaryS)-[:ACTED_IN {roles:['Ken Mattingly']}]->(Apollo13),
+(RonH)-[:DIRECTED]->(Apollo13)
+
+CREATE (Twister:Movie {title:'Twister', released:1996, tagline:"Don't Breathe. Don't Look Back."})
+CREATE (PhilipH:Person {name:'Philip Seymour Hoffman', born:1967})
+CREATE (JanB:Person {name:'Jan de Bont', born:1943})
+CREATE
+(BillPax)-[:ACTED_IN {roles:['Bill Harding']}]->(Twister),
+(HelenH)-[:ACTED_IN {roles:['Dr. Jo Harding']}]->(Twister),
+(ZachG)-[:ACTED_IN {roles:['Eddie']}]->(Twister),
+(PhilipH)-[:ACTED_IN {roles:['Dustin "Dusty" Davis']}]->(Twister),
+(JanB)-[:DIRECTED]->(Twister)
+
+CREATE (CastAway:Movie {title:'Cast Away', released:2000, tagline:'At the edge of the world, his journey begins.'})
+CREATE (RobertZ:Person {name:'Robert Zemeckis', born:1951})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Chuck Noland']}]->(CastAway),
+(HelenH)-[:ACTED_IN {roles:['Kelly Frears']}]->(CastAway),
+(RobertZ)-[:DIRECTED]->(CastAway)
+
+CREATE (OneFlewOvertheCuckoosNest:Movie {title:"One Flew Over the Cuckoo's Nest", released:1975, tagline:"If he's crazy, what does that make you?"})
+CREATE (MilosF:Person {name:'Milos Forman', born:1932})
+CREATE
+(JackN)-[:ACTED_IN {roles:['Randle McMurphy']}]->(OneFlewOvertheCuckoosNest),
+(DannyD)-[:ACTED_IN {roles:['Martini']}]->(OneFlewOvertheCuckoosNest),
+(MilosF)-[:DIRECTED]->(OneFlewOvertheCuckoosNest)
+
+CREATE (SomethingsGottaGive:Movie {title:"Something's Gotta Give", released:2003})
+CREATE (DianeK:Person {name:'Diane Keaton', born:1946})
+CREATE (NancyM:Person {name:'Nancy Meyers', born:1949})
+CREATE
+(JackN)-[:ACTED_IN {roles:['Harry Sanborn']}]->(SomethingsGottaGive),
+(DianeK)-[:ACTED_IN {roles:['Erica Barry']}]->(SomethingsGottaGive),
+(Keanu)-[:ACTED_IN {roles:['Julian Mercer']}]->(SomethingsGottaGive),
+(NancyM)-[:DIRECTED]->(SomethingsGottaGive),
+(NancyM)-[:PRODUCED]->(SomethingsGottaGive),
+(NancyM)-[:WROTE]->(SomethingsGottaGive)
+
+CREATE (BicentennialMan:Movie {title:'Bicentennial Man', released:1999, tagline:"One robot's 200 year journey to become an ordinary man."})
+CREATE (ChrisC:Person {name:'Chris Columbus', born:1958})
+CREATE
+(Robin)-[:ACTED_IN {roles:['Andrew Marin']}]->(BicentennialMan),
+(OliverP)-[:ACTED_IN {roles:['Rupert Burns']}]->(BicentennialMan),
+(ChrisC)-[:DIRECTED]->(BicentennialMan)
+
+CREATE (CharlieWilsonsWar:Movie {title:"Charlie Wilson's War", released:2007, tagline:"A stiff drink. A little mascara. A lot of nerve. Who said they couldn't bring down the Soviet empire."})
+CREATE (JuliaR:Person {name:'Julia Roberts', born:1967})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Rep. Charlie Wilson']}]->(CharlieWilsonsWar),
+(JuliaR)-[:ACTED_IN {roles:['Joanne Herring']}]->(CharlieWilsonsWar),
+(PhilipH)-[:ACTED_IN {roles:['Gust Avrakotos']}]->(CharlieWilsonsWar),
+(MikeN)-[:DIRECTED]->(CharlieWilsonsWar)
+
+CREATE (ThePolarExpress:Movie {title:'The Polar Express', released:2004, tagline:'This Holiday Season... Believe'})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Hero Boy', 'Father', 'Conductor', 'Hobo', 'Scrooge', 'Santa Claus']}]->(ThePolarExpress),
+(RobertZ)-[:DIRECTED]->(ThePolarExpress)
+
+CREATE (ALeagueofTheirOwn:Movie {title:'A League of Their Own', released:1992, tagline:'Once in a lifetime you get a chance to do something different.'})
+CREATE (Madonna:Person {name:'Madonna', born:1954})
+CREATE (GeenaD:Person {name:'Geena Davis', born:1956})
+CREATE (LoriP:Person {name:'Lori Petty', born:1963})
+CREATE (PennyM:Person {name:'Penny Marshall', born:1943})
+CREATE
+(TomH)-[:ACTED_IN {roles:['Jimmy Dugan']}]->(ALeagueofTheirOwn),
+(GeenaD)-[:ACTED_IN {roles:['Dottie Hinson']}]->(ALeagueofTheirOwn),
+(LoriP)-[:ACTED_IN {roles:['Kit Keller']}]->(ALeagueofTheirOwn),
+(RosieO)-[:ACTED_IN {roles:['Doris Murphy']}]->(ALeagueofTheirOwn),
+(Madonna)-[:ACTED_IN {roles:['"All the Way" Mae Mordabito']}]->(ALeagueofTheirOwn),
+(BillPax)-[:ACTED_IN {roles:['Bob Hinson']}]->(ALeagueofTheirOwn),
+(PennyM)-[:DIRECTED]->(ALeagueofTheirOwn)
+
+CREATE (PaulBlythe:Person {name:'Paul Blythe'})
+CREATE (AngelaScope:Person {name:'Angela Scope'})
+CREATE (JessicaThompson:Person {name:'Jessica Thompson'})
+CREATE (JamesThompson:Person {name:'James Thompson'})
+
+CREATE
+(JamesThompson)-[:FOLLOWS]->(JessicaThompson),
+(AngelaScope)-[:FOLLOWS]->(JessicaThompson),
+(PaulBlythe)-[:FOLLOWS]->(AngelaScope)
+
+CREATE
+(JessicaThompson)-[:REVIEWED {summary:'An amazing journey', rating:95}]->(CloudAtlas),
+(JessicaThompson)-[:REVIEWED {summary:'Silly, but fun', rating:65}]->(TheReplacements),
+(JamesThompson)-[:REVIEWED {summary:'The coolest football movie ever', rating:100}]->(TheReplacements),
+(AngelaScope)-[:REVIEWED {summary:'Pretty funny at times', rating:62}]->(TheReplacements),
+(JessicaThompson)-[:REVIEWED {summary:'Dark, but compelling', rating:85}]->(Unforgiven),
+(JessicaThompson)-[:REVIEWED {summary:"Slapstick redeemed only by the Robin Williams and Gene Hackman's stellar performances", rating:45}]->(TheBirdcage),
+(JessicaThompson)-[:REVIEWED {summary:'A solid romp', rating:68}]->(TheDaVinciCode),
+(JamesThompson)-[:REVIEWED {summary:'Fun, but a little far fetched', rating:65}]->(TheDaVinciCode),
+(JessicaThompson)-[:REVIEWED {summary:'You had me at Jerry', rating:92}]->(JerryMaguire)
+
+WITH TomH as a
+MATCH (a)-[:ACTED_IN]->(m)<-[:DIRECTED]-(d) RETURN a,m,d LIMIT 10;`}
+        
+
+
+
, + +
+
The Movie Graph
+
+

Find

+

Example queries for finding individual nodes.

+
    +
  1. Click on any query example
  2. +
  3. Run the query from the editor
  4. +
  5. Notice the syntax pattern
  6. +
  7. Try looking for other movies or actors
  8. +
+
+

+ :help MATCH{' '} + WHERE RETURN +

+
+
+

Find the actor named "Tom Hanks"...

+
+
+          {'MATCH (tom {name: "Tom Hanks"}) RETURN tom'}
+        
+
+

Find the movie with title "Cloud Atlas"...

+
+
+          {'MATCH (cloudAtlas {title: "Cloud Atlas"}) RETURN cloudAtlas'}
+        
+
+

Find 10 people...

+
+
+          MATCH (people:Person) RETURN people.name LIMIT 10
+        
+
+

Find movies released in the 1990s...

+
+
+          {
+            'MATCH (nineties:Movie) WHERE nineties.released >= 1990 AND nineties.released < 2000 RETURN nineties.title'
+          }
+        
+
+
+
, + +
+
The Movie Graph
+
+

Query

+

Finding patterns within the graph.

+
    +
  1. Actors are people who acted in movies
  2. +
  3. Directors are people who directed a movie
  4. +
  5. What other relationships exist?
  6. +
+
+

+ :help MATCH +

+
+
+

List all Tom Hanks movies...

+
+
+          {
+            'MATCH (tom:Person {name: "Tom Hanks"})-[:ACTED_IN]->(tomHanksMovies) RETURN tom,tomHanksMovies'
+          }
+        
+
+

Who directed "Cloud Atlas"?

+
+
+          {
+            'MATCH (cloudAtlas {title: "Cloud Atlas"})<-[:DIRECTED]-(directors) RETURN directors.name'
+          }
+        
+
+

Tom Hanks' co-actors...

+
+
+          {
+            'MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors) RETURN coActors.name'
+          }
+        
+
+

How people are related to "Cloud Atlas"...

+
+
+          {
+            'MATCH (people:Person)-[relatedTo]-(:Movie {title: "Cloud Atlas"}) RETURN people.name, Type(relatedTo), relatedTo'
+          }
+        
+
+
+
, + +
+
The Movie Graph
+
+

Solve

+

+ You've heard of the classic "Six Degrees of Kevin Bacon"? That is simply + a shortest path query called the "Bacon Path". +

+
    +
  1. Variable length patterns
  2. +
  3. Built-in shortestPath() algorithm
  4. +
+
+
+

+ Movies and actors up to 4 "hops" away from Kevin Bacon +

+
+
+          {`MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..4]-(hollywood)
+RETURN DISTINCT hollywood`}
+        
+
+

+ Bacon path, the shortest path of any relationships to Meg Ryan +

+
+
+          {`MATCH p=shortestPath(
+(bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"})
+)
+RETURN p`}
+        
+ +
+
+
, + +
+
The Movie Graph
+
+

Recommend

+

+ Let's recommend new co-actors for Tom Hanks. A basic recommendation + approach is to find connections past an immediate neighborhood which are + themselves well connected. +

+

For Tom Hanks, that means:

+
    +
  1. + Find actors that Tom Hanks hasn't yet worked with, but his co-actors + have. +
  2. +
  3. Find someone who can introduce Tom to his potential co-actor.
  4. +
+
+
+

+ Extend Tom Hanks co-actors, to find co-co-actors who haven't worked with + Tom Hanks... +

+
+
+          {`MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
+  (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors)
+WHERE NOT (tom)-[:ACTED_IN]->()<-[:ACTED_IN]-(cocoActors) AND tom <> cocoActors
+RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC`}
+        
+
+

Find someone to introduce Tom Hanks to Tom Cruise

+
+
+          {`MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
+  (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cruise:Person {name:"Tom Cruise"})
+RETURN tom, m, coActors, m2, cruise`}
+        
+
+
+
, + +
+
The Movie Graph
+
+

Clean up

+

When you're done experimenting, you can remove the movie data set.

+

Note:

+
    +
  1. Nodes can't be deleted if relationships exist
  2. +
  3. Delete both nodes and relationships together
  4. +
+

+ WARNING: This will remove all Person and Movie nodes! +

+
+

+ :help DELETE +

+
+
+

+ Delete all Movie and Person nodes, and their relationships +

+
+
+          MATCH (n) DETACH DELETE n
+        
+ +
+

Prove that the Movie Graph is gone

+
+
MATCH (n) RETURN n
+
+
+
, + +
+

Next steps

+ + +
+
+

Documentation

+ +
+
+] + +export default { title, category, slides } diff --git a/src/browser/documentation/sidebar-guides/unfound.tsx b/src/browser/documentation/sidebar-guides/unfound.tsx new file mode 100644 index 00000000000..3f8819f1e7e --- /dev/null +++ b/src/browser/documentation/sidebar-guides/unfound.tsx @@ -0,0 +1,44 @@ +/* + * Copyright (c) "Neo4j" + * Neo4j Sweden AB [http://neo4j.com] + * + * This file is part of Neo4j. + * + * Neo4j is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import React from 'react' +const title = 'Not found' + +const slides = [ + +

Apologies, but there doesn{"'"}t seem to be any content about that.

+
Try:
+
    +
  • + :help - for general help about using Neo4j + Browser +
  • +
  • + :play start - to see a few available guides +
  • +
  • + Neo4j Documentation - for detailed + information about Neo4j +
  • +
+
+] + +export default { title, slides } diff --git a/src/browser/modules/Carousel/Slide.tsx b/src/browser/modules/Carousel/Slide.tsx index 29485e4dee1..bb115cc18df 100644 --- a/src/browser/modules/Carousel/Slide.tsx +++ b/src/browser/modules/Carousel/Slide.tsx @@ -18,38 +18,47 @@ * along with this program. If not, see . */ import React from 'react' -import styles from './carousel.less' +// The reason we have this file is to add css to +// externally defined guides +import styles from './style.less' import { StyledSlide } from './styled' -const Slide = React.forwardRef(({ children, content, html }, ref) => { - if (children) { - return ( - - {children} - - ) - } +type SlideProps = { + children?: JSX.Element[] + content?: JSX.Element + html?: string +} +const Slide = React.forwardRef( + ({ children, content, html }: SlideProps, ref: React.Ref) => { + if (children) { + return ( + + {children} + + ) + } - if (content) { - return ( - - {content} - - ) - } + if (content) { + return ( + + {content} + + ) + } - if (html) { - return ( - - ) - } + if (html) { + return ( + + ) + } - return null -}) + return null + } +) Slide.displayName = 'Slide' diff --git a/src/browser/modules/Carousel/carousel.less b/src/browser/modules/Carousel/style.less similarity index 100% rename from src/browser/modules/Carousel/carousel.less rename to src/browser/modules/Carousel/style.less diff --git a/src/browser/modules/Carousel/styled.tsx b/src/browser/modules/Carousel/styled.tsx index 7937f228a40..122d6123f0e 100644 --- a/src/browser/modules/Carousel/styled.tsx +++ b/src/browser/modules/Carousel/styled.tsx @@ -20,6 +20,7 @@ import styled from 'styled-components' import { bounceRight } from 'browser-styles/animations' +import { dark } from 'browser-styles/themes' export const StyledCarousel: any = styled.div` padding-bottom: 20px; @@ -302,3 +303,117 @@ export const StyledSlide = styled.div` background-color: ${props => props.theme.teaserCardBackground} !important; } ` + +export const SidebarSlide = styled.div` + color: ${dark.primaryText}; + & p.lead, + .title, + .subtitle, + .content > p, + .table-help { + color: ${dark.primaryText} !important; + line-height: 1.3; + + th { + padding-right: 20px; + text-align: left; + } + + &--header { + th { + border-bottom: ${dark.topicBorder}; + font-size: 2rem; + padding: 15px 0 0 0; + } + + &:first-child { + th { + padding-top: 0; + } + } + } + + &--subheader { + th { + border-bottom: ${dark.topicBorder}; + font-size: 1.5rem; + padding: 10px 0 0 0; + } + + &:first-child { + th { + padding-top: 0; + } + } + } + + &--commands { + margin-top: 2rem; + + td { + padding: 3px 10px 3px 0; + } + } + + &--keys { + th { + border-bottom: ${dark.topicBorder}; + } + td { + padding: 3px 10px 3px 0; + } + } + } + & a { + color: ${dark.link}; + text-decoration: ${props => + props.theme.name === 'dark' ? 'underline' : 'none'}; + } + & kbd { + color: ${dark.primaryBackground} !important; /* inverted */ + background-color: ${dark.primaryText} !important; + } + & .content > pre { + background-color: ${dark.secondaryBackground}; + color: ${dark.preText}; + } + & pre.runnable { + background-color: ${dark.preBackground}; + color: ${dark.preText}; + } + & pre.content { + background-color: ${dark.secondaryBackground}; + color: ${dark.preText}; + } + & a[help-topic], + a[play-topic], + a[server-topic], + a[exec-topic] { + background-color: ${dark.topicBackground} !important; + color: #5ca6d9; + } + & button [help-topic], + button [play-topic], + button [server-topic], + button [exec-topic] { + background-color: ${dark.primaryButtonBackground}; + color: ${dark.primaryButtonText}; + } + &.slide .code { + background-color: transparent; + } + &.slide .key { + background-color: ${dark.preBackground}; + border-radius: 3px; + font-size: 12px; + display: inline-block; + padding: 0 6px; + } + + .has-carousel & { + overflow: visible; + } + &.slide .teaser { + background-color: ${dark.teaserCardBackground} !important; + } +` diff --git a/src/browser/modules/Docs/MDX/MdxSlide.tsx b/src/browser/modules/Docs/MDX/MdxSlide.tsx index 739b0ed27ac..1cb741ce0cb 100644 --- a/src/browser/modules/Docs/MDX/MdxSlide.tsx +++ b/src/browser/modules/Docs/MDX/MdxSlide.tsx @@ -56,13 +56,15 @@ const MdxRow = ({ row = '' }) => ( ) const MdxSlide = ({ mdx = '' }) => ( - - - {splitMdxRows(mdx).map((row, index) => ( - - ))} - - + + {splitMdxRows(mdx).map((row, index) => ( + + ))} + + } + /> ) export default MdxSlide diff --git a/src/browser/modules/GuideCarousel/GuideSlide.tsx b/src/browser/modules/GuideCarousel/GuideSlide.tsx deleted file mode 100644 index 269a7963460..00000000000 --- a/src/browser/modules/GuideCarousel/GuideSlide.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -import React from 'react' -import styles from './guide.less' -import { StyledGuideSlide } from './styled' - -type SlideProps = { - children: JSX.Element[] - content?: JSX.Element - html?: string -} -const Slide = React.forwardRef( - ({ children, content, html }: SlideProps, ref: React.Ref) => { - if (children) { - return ( - - {children} - - ) - } - - if (content) { - return ( - - {content} - - ) - } - - if (html) { - return ( - - ) - } - - return null - } -) - -Slide.displayName = 'Slide' - -export default Slide diff --git a/src/browser/modules/GuideCarousel/guide.less b/src/browser/modules/GuideCarousel/guide.less deleted file mode 100644 index e2da404999c..00000000000 --- a/src/browser/modules/GuideCarousel/guide.less +++ /dev/null @@ -1,389 +0,0 @@ -.guideSlide { - overflow-y: auto; - font-family: 'Open Sans', 'HelveticaNeue-Light', 'Helvetica Neue Light', - 'Helvetica Neue', Helvetica, Arial, sans-serif; - font-size: 14px; - font-weight: 400; - - & code, - & .code, - & kbd, - & pre, - & samp, - & figure pre { - font-family: 'Fira Code', 'Monaco', 'Lucida Console', Courier, monospace; - background: #f3f3f3; - color: #333; - padding-left: 0.25em; - padding-right: 0.25em; - white-space: pre-wrap; - word-break: break-all; - word-wrap: break-word; - - .disable-font-ligatures & { - font-variant-ligatures: none !important; - } - } - & th { - min-width: 50px; - } - & pre.code { - padding: 0.5em; - margin-top: 0.5em; - margin-bottom: 0.5em; - - &.pre-scrollable { - max-height: 230px; - } - &.runnable { - cursor: pointer; - border: transparent; - border-radius: 2px; - background-color: rgba(0, 0, 0, 0.1); - } - &.clicked { - border: 2px solid #8dd465; - opacity: 0.5; - } - } - & .text-right { - text-align: right; - } - & .nobreak { - word-break: keep-all; - white-space: nowrap; - } - - & .italic { - font-style: italic; - } - - & .light { - font-weight: 300; - } - - & .semi-bold { - font-weight: 600; - } - - & .bold { - font-weight: 700; - } - - & .extra-bold { - font-weight: 800; - } - - & small, - & .small, - & .btn--s { - font-size: 0.8em; - } - - & .caps { - font-variant: small-caps; - } - - & .muted { - opacity: 0.7; - } - - & h1, - & h2, - & h3, - & h4, - & h5, - & h6 { - line-height: 1.5em; - margin: 0 0 0.25em; - font-weight: 400; - } - - & h1, - & .h1 { - font-size: 2.441em; - } - & h2, - & .h2 { - font-size: 1.953em; - &.vtop { - margin-top: 0.2em; - margin-bottom: 0.35em; - } - } - & h3, - & .h3 { - font-size: 1.563em; - font-weight: 600; - &.vtop { - margin-top: 0.48em; - margin-bottom: 0.4em; - } - } - & h4, - & .h4 { - font-size: 1.25em; - &.vtop { - margin-top: 0.8em; - margin-bottom: 0.55em; - } - } - & h5, - & .h5 { - font-size: 1em; - &.vtop { - margin-top: 1.2em; - margin-bottom: 0.7em; - } - } - & h6, - & .h6 { - font-size: 0.8em; - &.vtop { - margin-top: 1.75em; - margin-bottom: 0.9em; - } - } - - & p { - margin-bottom: 1em; - &.lead { - color: #666; - } - } - - & ul, - & ol { - list-style-position: outside; - padding-left: 1em; - margin-top: 0.5em; - } - - & ul.big { - line-height: 2em; - } - - & ul.vtop { - margin-top: 0; - } - & .icon.icon-sm { - font-size: 0.5em; - } - & .icon.icon-2x { - font-size: 2em; - } - & .icon.icon-3x { - font-size: 3em; - } - & .icon.icon-4x { - font-size: 4em; - } - - & article.help { - font-size: 15px; - color: #666; - display: block; - width: 100%; - padding: 30px; - & .main { - display: flex; - flex: 0 0 25%; - } - } - & .headings { - flex: 0 0 25%; - & .title { - font-size: 24px; - font-weight: 400; - color: #333; - } - } - & .content { - p { - margin-bottom: 10px; - } - img { - max-width: 900px; - max-height: 300px; - } - } - & .example { - margin-top: 1rem; - } - & .links { - display: table; - } - & .runnable { - & pre { - cursor: pointer; - padding-left: 1em; - padding-right: 1em; - } - } - & .links { - display: table; - & .link { - display: table-row; - & .title, - & .content { - display: table-cell; - padding: 5px; - font-size: 13px; - } - & .title { - text-align: right; - font-weight: bold; - } - } - } - - & a[help-topic], - [play-topic], - [server-topic], - [exec-topic] { - background-color: #f8f8f8; - border-radius: 3px; - border: 0px; - display: inline-block; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, - Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', - sans-serif /*"Fira Code",Monaco,"Courier New",Terminal,monospace*/; - font-size: 12px; - line-height: 18px; - margin-bottom: 5px; - margin-right: 5px; - padding: 0 4px; - color: #428bca; - cursor: pointer; - text-decoration: none; - - .disable-font-ligatures & { - font-variant-ligatures: none !important; - } - } - & pre { - word-break: break-all; - word-wrap: break-word; - background-color: #f5f5f5; - padding: 10px !important; - } - & code { - color: #fd766e; - border-radius: 2px; - - a { - color: #fd766e !important; - } - } - & figcaption { - font-style: italic; - font-size: 13px; - text-align: center; - } - & footer { - text-align: center; - } - .btn-cta { - padding: 1em; - background-color: #008cc1; - color: #fff; - border-radius: 4px; - border: 0; - display: inline-block; - } - .teasers { - display: flex; - justify-content: center; - align-items: stretch; - flex-wrap: wrap; - } - .teaser { - margin: 0.5em; - padding: 10px; - border: 0; - border-radius: 4px; - height: 270px; - float: left; - position: relative; - box-shadow: 0px 0px 2px rgba(52, 58, 67, 0.1), - 0px 1px 2px rgba(52, 58, 67, 0.08), 0px 1px 4px rgba(52, 58, 67, 0.08); - &.teaser-2 { - width: 45%; - min-width: 285px; - height: 320px; - overflow: hidden; - .icon-holder { - max-height: 160px; - overflow-y: auto; - } - .icon { - width: 12%; - } - .topic-bullets { - max-width: 80%; - } - } - &.teaser-3 { - width: 30%; - min-width: 215px; - overflow: hidden; - } - button { - font-weight: 600; - position: absolute; - bottom: 10px; - width: 90%; - left: 5%; - outline: none; - } - .icon { - float: left; - max-width: 60px; - width: 18%; - } - .icon.sl { - padding-left: 0; - font-size: 48px; - } - .icon.sl.green { - color: #65b144; - } - .icon.sl.yellow { - color: #fdcc59; - } - .icon.sl.red { - color: #ff5641; - } - .topic-bullets { - font-size: 13px; - line-height: 1.3em; - float: left; - word-wrap: break-word; - min-width: 100px; - max-width: 75%; - list-style: none; - padding-left: 10px; - margin-top: 0; - overflow: hidden; - } - .topic-bullets :first-child { - margin-top: 0; - } - .topic-bullets li { - margin-top: 5px; - } - .icon-holder { - margin: 6px 0 20px 0; - overflow: hidden; - } - } - .padding5 { - padding: 5px; - } - .padding30 { - padding-top: 30px; - } - .table-padding { - padding-top: 10px; - } -} diff --git a/src/browser/modules/GuideCarousel/styled.tsx b/src/browser/modules/GuideCarousel/styled.tsx deleted file mode 100644 index 42f28b2d801..00000000000 --- a/src/browser/modules/GuideCarousel/styled.tsx +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import styled from 'styled-components' -import { bounceRight } from 'browser-styles/animations' -import { dark } from 'browser-styles/themes' - -export const StyledCarousel: any = styled.div` - padding-bottom: 20px; - min-height: 100%; - width: 100%; - outline: none; - - .row { - margin-left: 0; - margin-right: 0; - } -` - -export const SlideContainer = styled.div` - padding: 0; - width: 100%; - display: inline-block; - max-height: 420px; - overflow-y: auto; -` - -export const StyledCarouselButtonContainer = styled.div` - color: ${dark.secondaryButtonText}; - display: flex; - align-items: center; - justify-content: center; - position: absolute; - bottom: 0; - z-index: 10; - border-top: ${dark.inFrameBorder}; - margin-left: -40px; - height: 39px; - width: 100%; - - .is-fullscreen & { - bottom: 39px; - } -` -export const StyledCarouselButtonContainerInner = styled.div` - display: flex; - align-items: center; - justify-content: center; - border-radius: 3px; - position: relative; -` - -export const StyledCarouselCount = styled.div` - display: flex; - align-items: center; - font-size: 10px; - font-weight: bold; - justify-content: flex-end; - border-radius: 3px; - min-width: 44px; - position: absolute; - right: 100%; - padding: 0; - margin-right: 10px; -` - -const CarouselIndicator = styled.li` - margin: 0; - cursor: pointer; - border-radius: 50%; - border: 3px solid transparent; - position: relative; - z-index: 1; - - > span { - background-color: ${dark.secondaryButtonText}; - display: block; - border-radius: 3px; - width: 6px; - height: 6px; - opacity: 0.4; - transition: opacity 0.1s ease-in-out; - } - - &::before { - border-radius: 2px; - content: attr(aria-label); - color: ${dark.primaryBackground}; - background-color: ${dark.primaryText}; - position: absolute; - font-size: 12px; - font-weight: bold; - left: 50%; - min-width: 24px; - bottom: calc(100% + 5px); - pointer-events: none; - transform: translateX(-50%); - padding: 5px; - line-height: 1; - text-align: center; - z-index: 100; - visibility: hidden; - } - - &::after { - border: solid; - border-color: ${dark.primaryText} transparent; - border-width: 6px 6px 0 6px; - bottom: 5px; - content: ''; - left: 50%; - pointer-events: none; - position: absolute; - transform: translateX(-50%); - z-index: 100; - visibility: hidden; - } - - &:hover::before, - &:hover::after { - visibility: visible; - } -` -export const CarouselIndicatorInactive = styled(CarouselIndicator)` - &:hover > span { - opacity: 1; - } -` -export const CarouselIndicatorActive = styled(CarouselIndicator)` - > span { - opacity: 1; - } -` - -export const StyledCarouselIntroAnimated = styled.div` - align-items: center; - animation: ${bounceRight} 4s ease-in-out infinite; - animation-fill-mode: forwards; - color: #222; - display: flex; - opacity: 0.8; - position: absolute; - right: calc(100% + 50px); - pointer-events: none; - transition: opacity 0.2s ease-in-out; -` - -export const StyledCarouselIntro = styled.div` - align-items: center; - background-color: #f6e58d; - border-radius: 20px; - color: #222; - display: flex; - font-family: 'Fira Code', 'Monaco', 'Lucida Console', Courier, monospace; - font-size: 10px; - font-weight: 500; - padding: 3px 10px; - user-select: none; - white-space: nowrap; - - span:first-child { - min-width: 140px; - margin-right: 5px; - } -` - -export const StyledUl = styled.ul` - list-style: none; - display: flex; - align-items: center; - justify-content: center; - margin: 0 !important; - padding-left: 0 !important; -` -export const StyledGuideSlide = styled.div` - color: ${dark.primaryText}; - & p.lead, - .title, - .subtitle, - .content > p, - .table-help { - color: ${dark.primaryText} !important; - line-height: 1.3; - - th { - padding-right: 20px; - text-align: left; - } - - &--header { - th { - border-bottom: ${dark.topicBorder}; - font-size: 2rem; - padding: 15px 0 0 0; - } - - &:first-child { - th { - padding-top: 0; - } - } - } - - &--subheader { - th { - border-bottom: ${dark.topicBorder}; - font-size: 1.5rem; - padding: 10px 0 0 0; - } - - &:first-child { - th { - padding-top: 0; - } - } - } - - &--commands { - margin-top: 2rem; - - td { - padding: 3px 10px 3px 0; - } - } - - &--keys { - th { - border-bottom: ${dark.topicBorder}; - } - td { - padding: 3px 10px 3px 0; - } - } - } - & a { - color: ${dark.link}; - text-decoration: ${props => - props.theme.name === 'dark' ? 'underline' : 'none'}; - } - & kbd { - color: ${dark.primaryBackground} !important; /* inverted */ - background-color: ${dark.primaryText} !important; - } - & .content > pre { - background-color: ${dark.secondaryBackground}; - color: ${dark.preText}; - } - & pre.runnable { - background-color: ${dark.preBackground}; - color: ${dark.preText}; - } - & pre.content { - background-color: ${dark.secondaryBackground}; - color: ${dark.preText}; - } - & a[help-topic], - a[play-topic], - a[server-topic], - a[exec-topic] { - background-color: ${dark.topicBackground} !important; - color: #5ca6d9; - } - & button [help-topic], - button [play-topic], - button [server-topic], - button [exec-topic] { - background-color: ${dark.primaryButtonBackground}; - color: ${dark.primaryButtonText}; - } - &.slide .code { - background-color: transparent; - } - &.slide .key { - background-color: ${dark.preBackground}; - border-radius: 3px; - font-size: 12px; - display: inline-block; - padding: 0 6px; - } - - .has-carousel & { - overflow: visible; - } - &.slide .teaser { - background-color: ${dark.teaserCardBackground} !important; - } -` diff --git a/src/shared/modules/guides/guidesDuck.ts b/src/shared/modules/guides/guidesDuck.ts index 9c33696f7e0..73be7df394a 100644 --- a/src/shared/modules/guides/guidesDuck.ts +++ b/src/shared/modules/guides/guidesDuck.ts @@ -32,9 +32,8 @@ export type Guide = { } const defaultGuide: Guide = { - title: 'allGuides', - initialSlide: 0, - slides: docs.play.chapters.allGuides.slides || [] + ...docs.guide.chapters.index, + initialSlide: 0 } export interface GuideState { guide: Guide diff --git a/src/shared/services/exceptions.ts b/src/shared/services/exceptions.ts index 7d03eaa19b2..9ed68c99445 100644 --- a/src/shared/services/exceptions.ts +++ b/src/shared/services/exceptions.ts @@ -140,7 +140,7 @@ export function DatabaseUnavailableError({ return { type, code: type, - message: `Database "${dbName}" is unavailable, its status is "${dbMeta.status}."` + message: `Database "${dbName}" is unavailable, its status is "${dbMeta.status}".` } } diff --git a/src/shared/services/guideResolverHelper.tsx b/src/shared/services/guideResolverHelper.tsx index 14b72eff0d2..de86e8cd7c9 100644 --- a/src/shared/services/guideResolverHelper.tsx +++ b/src/shared/services/guideResolverHelper.tsx @@ -2,7 +2,7 @@ import React from 'react' import MdxSlide from 'browser/modules/Docs/MDX/MdxSlide' import Slide from 'browser/modules/Carousel/Slide' import docs, { isGuideChapter } from 'browser/documentation' -import guideUnfound from 'browser/documentation/guides/unfound' +import guideUnfound from 'browser/documentation/play-guides/unfound' import { ErrorView } from 'browser/modules/Stream/ErrorFrame' import { addProtocolsToUrlList, From f613e1731f567c2c2e8a2acb4bc77134176aebe7 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Mon, 12 Apr 2021 17:01:25 +0200 Subject: [PATCH 16/64] Directives on index page --- src/browser/components/Directives.tsx | 19 +++++++++++++++++- src/browser/components/drawer/Drawer.tsx | 1 + src/browser/documentation/index.ts | 7 +++++-- .../sidebar-guides/guideIndex.tsx | 20 ++++++++++++------- src/browser/modules/Carousel/Slide.tsx | 4 ++-- src/browser/modules/Carousel/styled.tsx | 4 +++- src/browser/modules/Stream/PlayFrame.tsx | 4 ++-- src/shared/services/guideResolverHelper.tsx | 13 ++---------- 8 files changed, 46 insertions(+), 26 deletions(-) diff --git a/src/browser/components/Directives.tsx b/src/browser/components/Directives.tsx index 41e0651ae69..dce5518a973 100644 --- a/src/browser/components/Directives.tsx +++ b/src/browser/components/Directives.tsx @@ -28,6 +28,20 @@ import * as editor from 'shared/modules/editor/editorDuck' import { addClass, prependIcon } from 'shared/services/dom-helpers' const directives = [ + { + selector: '[data-exec]', + valueExtractor: (elem: any) => { + return `${elem.getAttribute('data-exec')}` + }, + autoExec: true + }, + { + selector: '[data-populate]', + valueExtractor: (elem: any) => { + return `${elem.getAttribute('data-populate')}` + }, + autoExec: false + }, { selector: '[exec-topic]', valueExtractor: (elem: any) => { @@ -104,7 +118,10 @@ export const Directives = (props: any) => { directives.forEach(directive => { const elems = elem.querySelectorAll(directive.selector) Array.from(elems).forEach((e: any) => { - if (e.firstChild.nodeName !== 'I') { + if ( + e.firstChild.nodeName !== 'I' && + !directive.selector.startsWith('[data-') + ) { prependPlayIcon(e) } diff --git a/src/browser/components/drawer/Drawer.tsx b/src/browser/components/drawer/Drawer.tsx index c2b679c32b2..0362e652c48 100644 --- a/src/browser/components/drawer/Drawer.tsx +++ b/src/browser/components/drawer/Drawer.tsx @@ -108,4 +108,5 @@ export const DrawerBrowserCommand = styled.span` overflow: hidden; white-space: nowrap; text-overflow: ellipsis; + cursor: pointer; ` diff --git a/src/browser/documentation/index.ts b/src/browser/documentation/index.ts index a2cba13ca71..9a2732d6a2a 100644 --- a/src/browser/documentation/index.ts +++ b/src/browser/documentation/index.ts @@ -132,6 +132,10 @@ type GuideDocs = { chapters: Record } type GuideChapter = 'index' | 'movieGraph' | 'movies' | 'unfound' +// TypeGuard function to ts to understand that a string is a valid key +export function isGuideChapter(name: string): name is GuideChapter { + return name in docs.guide.chapters +} type PlayDocs = { title: 'Guides & Examples' @@ -153,8 +157,7 @@ export type PlayChapter = | 'unfound' | 'writeCode' -// TypeGuard function to ts to understand that a string is a valid key -export function isGuideChapter(name: string): name is PlayChapter { +export function isPlayChapter(name: string): name is PlayChapter { return name in docs.play.chapters } diff --git a/src/browser/documentation/sidebar-guides/guideIndex.tsx b/src/browser/documentation/sidebar-guides/guideIndex.tsx index e8ab346679f..31f6d1ee622 100644 --- a/src/browser/documentation/sidebar-guides/guideIndex.tsx +++ b/src/browser/documentation/sidebar-guides/guideIndex.tsx @@ -1,29 +1,35 @@ import React from 'react' import { DrawerBrowserCommand } from 'browser-components/drawer' -import { SidebarSlide } from 'browser/modules/Carousel/styled' +import { StyledSidebarSlide } from 'browser/modules/Carousel/styled' const title = 'all guides' const slides = [ - +

Guides in Neo4j Browser

+ You can start guides by running: + + :guide [guide name] + -
+ ] export default { title, slides } diff --git a/src/browser/modules/Carousel/Slide.tsx b/src/browser/modules/Carousel/Slide.tsx index bb115cc18df..9184d99916b 100644 --- a/src/browser/modules/Carousel/Slide.tsx +++ b/src/browser/modules/Carousel/Slide.tsx @@ -18,8 +18,8 @@ * along with this program. If not, see . */ import React from 'react' -// The reason we have this file is to add css to -// externally defined guides +// The reason we have this file is to define classnames +// used in our templates and externally defined guides import styles from './style.less' import { StyledSlide } from './styled' diff --git a/src/browser/modules/Carousel/styled.tsx b/src/browser/modules/Carousel/styled.tsx index 122d6123f0e..a126bcd4377 100644 --- a/src/browser/modules/Carousel/styled.tsx +++ b/src/browser/modules/Carousel/styled.tsx @@ -304,7 +304,9 @@ export const StyledSlide = styled.div` } ` -export const SidebarSlide = styled.div` +export const StyledSidebarSlide = styled.div.attrs({ + className: 'slide' /* added to get styling from less.css */ +})` color: ${dark.primaryText}; & p.lead, .title, diff --git a/src/browser/modules/Stream/PlayFrame.tsx b/src/browser/modules/Stream/PlayFrame.tsx index 6c5780584fd..2bb42f2f2cf 100644 --- a/src/browser/modules/Stream/PlayFrame.tsx +++ b/src/browser/modules/Stream/PlayFrame.tsx @@ -23,7 +23,7 @@ import { withBus } from 'react-suber' import { fetchGuideFromAllowlistAction } from 'shared/modules/commands/commandsDuck' import Docs from '../Docs/Docs' -import docs, { isGuideChapter } from '../../documentation' +import docs, { isPlayChapter } from '../../documentation' import FrameTemplate from '../Frame/FrameTemplate' import FrameAside from '../Frame/FrameAside' import { @@ -240,7 +240,7 @@ function generateContent( ) // Check if content exists locally - if (isGuideChapter(guideName)) { + if (isPlayChapter(guideName)) { const { content, title, subtitle, slides = null } = chapters[guideName] return { guide: ( diff --git a/src/shared/services/guideResolverHelper.tsx b/src/shared/services/guideResolverHelper.tsx index de86e8cd7c9..4c7d5716306 100644 --- a/src/shared/services/guideResolverHelper.tsx +++ b/src/shared/services/guideResolverHelper.tsx @@ -16,7 +16,7 @@ import { } from 'shared/modules/dbMeta/dbMetaDuck' import { splitMdxSlides } from 'browser/modules/Docs/MDX/splitMdx' -const { chapters } = docs.play +const { chapters } = docs.guide const unfound = { slides: [guideUnfound.content], title: guideUnfound.title } export async function resolveGuide( @@ -29,16 +29,7 @@ export async function resolveGuide( } if (isGuideChapter(guideName)) { - // TODO Fix so all guides have slides, to avoid this dance - const guide = chapters[guideName] - const title = guide.title - if (guide.slides) { - return { slides: guide.slides, title } - } - if (guide.content) { - return { slides: [guide.content], title } - } - return { slides: [], title } + return chapters[guideName] } try { From 02325dd5a1d79f9e6e99dd26a80e39d520bccd3f Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 09:08:52 +0200 Subject: [PATCH 17/64] Icons --- src/browser/components/icons/Icons.tsx | 7 +++++++ src/browser/modules/DBMSInfo/DBMSInfo.tsx | 2 +- src/browser/modules/Sidebar/GuidesDrawer.tsx | 19 ++++++++++++------- 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/browser/components/icons/Icons.tsx b/src/browser/components/icons/Icons.tsx index b8066f5c944..7b5eed997e6 100644 --- a/src/browser/components/icons/Icons.tsx +++ b/src/browser/components/icons/Icons.tsx @@ -351,6 +351,13 @@ export const MinusIcon = (): JSX.Element => ( className="sl-minus-circle" /> ) +export const LeftArrowIcon = (): JSX.Element => ( + +) export const RightArrowIcon = (): JSX.Element => ( void } function GuidesDrawer({ @@ -34,12 +35,16 @@ function GuidesDrawer({ }: GuidesDrawerProps): JSX.Element { return ( - - {guide.title !== 'allGuides' && ( -
back to all guides
- )} - {guide.title} Guides{' '} -
+ + + {guide.title !== 'allGuides' && ( + + + + )} + {guide.title} Guides{' '} + + From 47e3a9d19f598072e8c0642fcb626227ed8d4990 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 11:44:39 +0200 Subject: [PATCH 18/64] Index skeleton looks --- src/browser/components/drawer/Drawer.tsx | 8 ++- src/browser/components/icons/Icons.tsx | 5 ++ .../sidebar-guides/guideIndex.tsx | 49 +++++++++++++++---- src/browser/icons/back-arrow.svg | 10 ++++ src/browser/modules/Sidebar/DocumentItems.tsx | 4 +- src/browser/modules/Sidebar/GuidesDrawer.tsx | 27 +++++----- src/shared/modules/guides/guidesDuck.ts | 2 +- 7 files changed, 78 insertions(+), 27 deletions(-) create mode 100644 src/browser/icons/back-arrow.svg diff --git a/src/browser/components/drawer/Drawer.tsx b/src/browser/components/drawer/Drawer.tsx index 0362e652c48..e0ea8d65a06 100644 --- a/src/browser/components/drawer/Drawer.tsx +++ b/src/browser/components/drawer/Drawer.tsx @@ -44,6 +44,9 @@ export const DrawerHeader = styled.h4` export const DrawerToppedHeader = styled(DrawerHeader)` padding-top: 8px; ` +export const DrawerSeparator = styled.div` + border-bottom: 1px solid #424650; +` export const DrawerSubHeader = styled.h5` color: ${props => props.theme.primaryHeaderText}; @@ -78,7 +81,10 @@ export const DrawerFooter = styled.div` text-align: center; ` -export const DrawerExternalLink = styled.a` +export const DrawerExternalLink = styled.a.attrs({ + target: '_blank', + rel: 'noreferrer' +})` cursor: pointer; text-decoration: none; color: #68bdf4; diff --git a/src/browser/components/icons/Icons.tsx b/src/browser/components/icons/Icons.tsx index 7b5eed997e6..8268f753302 100644 --- a/src/browser/components/icons/Icons.tsx +++ b/src/browser/components/icons/Icons.tsx @@ -26,6 +26,7 @@ import addCircle from 'icons/add-circle.svg' import appWindowCode from 'icons/app-window-code.svg' import arrowLeft from 'icons/arrow-left.svg' import arrowRight from 'icons/arrow-right.svg' +import backArrow from 'icons/back-arrow.svg' import buttonRefreshArrow from 'icons/button-refresh-arrow.svg' import cannyFeedback from 'icons/canny-feedback.svg' import cannyNotifications from 'icons/canny-notifications.svg' @@ -474,3 +475,7 @@ export const CannyFeedbackIcon = (): JSX.Element => ( export const CannyNotificationsIcon = (): JSX.Element => ( ) + +export const BackIcon = ({ width }: { width: number }): JSX.Element => ( + +) diff --git a/src/browser/documentation/sidebar-guides/guideIndex.tsx b/src/browser/documentation/sidebar-guides/guideIndex.tsx index 31f6d1ee622..dc6e922e7f3 100644 --- a/src/browser/documentation/sidebar-guides/guideIndex.tsx +++ b/src/browser/documentation/sidebar-guides/guideIndex.tsx @@ -1,26 +1,52 @@ import React from 'react' -import { DrawerBrowserCommand } from 'browser-components/drawer' +import { + DrawerBrowserCommand, + DrawerExternalLink, + DrawerSubHeader +} from 'browser-components/drawer' import { StyledSidebarSlide } from 'browser/modules/Carousel/styled' const title = 'all guides' const slides = [ -

Guides in Neo4j Browser

- You can start guides by running: + You can also access Browser guides by running :guide [guide name] + in the code editor. + Built-in guides
    - - :guide movies -
  • - a guide about getting started - :guide intro + + :guide intro + + Navigating Neo4j Browser
  • - a guide about movies - :guide movies + + :guide concepts + + Property graph model concepts +
  • +
  • + + :guide cypher + + Cypher basics - create, match, delete +
  • + +
  • + + :guide movieGraph + + Queries and recommendations with Cypher - movie use case +
  • + +
  • + + :guide northwindGraph + + Translate and import relation data into graph
  • @@ -29,6 +55,9 @@ const slides = [
+ + More guides +
] diff --git a/src/browser/icons/back-arrow.svg b/src/browser/icons/back-arrow.svg new file mode 100644 index 00000000000..5bebe3a6386 --- /dev/null +++ b/src/browser/icons/back-arrow.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/src/browser/modules/Sidebar/DocumentItems.tsx b/src/browser/modules/Sidebar/DocumentItems.tsx index b33d3930a1d..56be6003899 100644 --- a/src/browser/modules/Sidebar/DocumentItems.tsx +++ b/src/browser/modules/Sidebar/DocumentItems.tsx @@ -78,9 +78,7 @@ export const DocumentItems = ({ const listOfItems = items.map(item => 'url' in item ? ( - - {item.name} - + {item.name} ) : ( void } function GuidesDrawer({ @@ -35,16 +40,14 @@ function GuidesDrawer({ }: GuidesDrawerProps): JSX.Element { return ( - - - {guide.title !== 'allGuides' && ( - - - - )} - {guide.title} Guides{' '} - - + + {guide.title !== defaultGuide.title && ( + + + + )} + Neo4j Browser Guides + diff --git a/src/shared/modules/guides/guidesDuck.ts b/src/shared/modules/guides/guidesDuck.ts index 73be7df394a..a194ebc50b3 100644 --- a/src/shared/modules/guides/guidesDuck.ts +++ b/src/shared/modules/guides/guidesDuck.ts @@ -31,7 +31,7 @@ export type Guide = { slides: JSX.Element[] } -const defaultGuide: Guide = { +export const defaultGuide: Guide = { ...docs.guide.chapters.index, initialSlide: 0 } From e9e4b5a01d795551f0a19fedee7f68f1f2efb9da Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 13:28:58 +0200 Subject: [PATCH 19/64] Fix darkmode and scroll into view --- .../SavedScripts/SavedScriptsFolder.tsx | 2 +- .../sidebar-guides/guideIndex.tsx | 6 ++-- .../sidebar-guides/movie-graph.tsx | 2 +- src/browser/modules/Carousel/Slide.tsx | 23 +++++++++------ src/browser/modules/Docs/MDX/MdxSlide.tsx | 18 ++++++------ .../modules/GuideCarousel/GuideCarousel.tsx | 28 ++++++++++--------- src/browser/modules/Sidebar/GuidesDrawer.tsx | 12 ++++++-- src/shared/services/guideResolverHelper.tsx | 6 ++-- 8 files changed, 55 insertions(+), 42 deletions(-) diff --git a/src/browser/components/SavedScripts/SavedScriptsFolder.tsx b/src/browser/components/SavedScripts/SavedScriptsFolder.tsx index 4fd7930f9c5..46bea3267c7 100644 --- a/src/browser/components/SavedScripts/SavedScriptsFolder.tsx +++ b/src/browser/components/SavedScripts/SavedScriptsFolder.tsx @@ -51,7 +51,7 @@ interface SavedScriptsFolderProps { forceEdit: boolean onDoneEditing: () => void selectedScriptIds: string[] - children: JSX.Element[] + children: React.ReactNode } function SavedScriptsFolder({ diff --git a/src/browser/documentation/sidebar-guides/guideIndex.tsx b/src/browser/documentation/sidebar-guides/guideIndex.tsx index dc6e922e7f3..6bfe4542034 100644 --- a/src/browser/documentation/sidebar-guides/guideIndex.tsx +++ b/src/browser/documentation/sidebar-guides/guideIndex.tsx @@ -4,11 +4,11 @@ import { DrawerExternalLink, DrawerSubHeader } from 'browser-components/drawer' -import { StyledSidebarSlide } from 'browser/modules/Carousel/styled' +import Slide from 'browser/modules/Carousel/Slide' const title = 'all guides' const slides = [ - + You can also access Browser guides by running :guide [guide name] @@ -58,7 +58,7 @@ const slides = [ More guides - + ] export default { title, slides } diff --git a/src/browser/documentation/sidebar-guides/movie-graph.tsx b/src/browser/documentation/sidebar-guides/movie-graph.tsx index 46cd38d1b52..6120198dfd1 100644 --- a/src/browser/documentation/sidebar-guides/movie-graph.tsx +++ b/src/browser/documentation/sidebar-guides/movie-graph.tsx @@ -25,7 +25,7 @@ import Slide from '../../modules/Carousel/Slide' const title = 'Movie Graph' const category = 'graphExamples' const slides = [ - +

Movie Graph

Pop-cultural connections between actors and movies

diff --git a/src/browser/modules/Carousel/Slide.tsx b/src/browser/modules/Carousel/Slide.tsx index 9184d99916b..8ec88557cd4 100644 --- a/src/browser/modules/Carousel/Slide.tsx +++ b/src/browser/modules/Carousel/Slide.tsx @@ -21,34 +21,41 @@ import React from 'react' // The reason we have this file is to define classnames // used in our templates and externally defined guides import styles from './style.less' -import { StyledSlide } from './styled' +import { StyledSidebarSlide, StyledSlide } from './styled' type SlideProps = { - children?: JSX.Element[] + children?: React.ReactNode content?: JSX.Element html?: string + forceDarkMode?: boolean } + const Slide = React.forwardRef( - ({ children, content, html }: SlideProps, ref: React.Ref) => { + ( + { children, content, html, forceDarkMode }: SlideProps, + ref: React.Ref + ) => { + const SlideComponent = forceDarkMode ? StyledSidebarSlide : StyledSlide + if (children) { return ( - + {children} - + ) } if (content) { return ( - + {content} - + ) } if (html) { return ( - ( ) -const MdxSlide = ({ mdx = '' }) => ( - - {splitMdxRows(mdx).map((row, index) => ( - - ))} - - } - /> +const MdxSlide = ({ mdx = '', forceDarkMode = false }) => ( + + + {splitMdxRows(mdx).map((row, index) => ( + + ))} + + ) export default MdxSlide diff --git a/src/browser/modules/GuideCarousel/GuideCarousel.tsx b/src/browser/modules/GuideCarousel/GuideCarousel.tsx index f6f7f65fd1f..2af5a7aa3ee 100644 --- a/src/browser/modules/GuideCarousel/GuideCarousel.tsx +++ b/src/browser/modules/GuideCarousel/GuideCarousel.tsx @@ -5,7 +5,6 @@ import { SlidePreviousIcon, SlideNextIcon } from 'browser-components/icons/Icons' -import { useRef } from 'react' import { CarouselIndicatorActive, CarouselIndicatorInactive, @@ -17,32 +16,29 @@ import { StyledUl } from '../Sidebar/styled' -type GuideCarouselProps = { slides?: JSX.Element[]; initialSlide?: number } +type GuideCarouselProps = { + slides?: JSX.Element[] + initialSlide?: number + scrollToTop?: () => void +} function GuidesCarousel({ slides = [], - initialSlide = 0 + initialSlide = 0, + scrollToTop = () => undefined }: GuideCarouselProps): JSX.Element { const [currentSlideIndex, gotoSlide] = useState(initialSlide) const currentSlide = slides[currentSlideIndex] const onFirstSlide = currentSlideIndex === 0 const onLastSlide = currentSlideIndex === slides.length - 1 - const scrollRef = useRef(null) - - function scrollToTop() { - if (scrollRef.current) scrollRef.current.scrollTop = 0 - } - function nextSlide() { if (!onLastSlide) { gotoSlide(index => index + 1) - scrollToTop() } } function prevSlide() { if (!onFirstSlide) { gotoSlide(index => index - 1) - scrollToTop() } } @@ -56,14 +52,20 @@ function GuidesCarousel({ } useEffect(() => { - // As slides change, switch to initial Slide + // If we switch the guide, jump to initial Slide gotoSlide(initialSlide) }, [initialSlide, slides]) + + useEffect(() => { + // As we progress in the slides, scroll to top + scrollToTop() + }, [scrollToTop, currentSlideIndex]) + const moreThanOneSlide = slides.length > 1 return ( - + {moreThanOneSlide && ( diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index c3bb29cd989..674c8b9808a 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -18,7 +18,7 @@ * along with this program. If not, see . */ -import React from 'react' +import React, { useRef } from 'react' import { connect } from 'react-redux' import { DrawerBody, DrawerHeader } from 'browser-components/drawer' @@ -38,8 +38,9 @@ function GuidesDrawer({ guide, backToAllGuides }: GuidesDrawerProps): JSX.Element { + const scrollRef = useRef(null) return ( - + {guide.title !== defaultGuide.title && ( @@ -50,7 +51,12 @@ function GuidesDrawer({ - + + scrollRef.current?.scrollIntoView({ block: 'start' }) + } + /> diff --git a/src/shared/services/guideResolverHelper.tsx b/src/shared/services/guideResolverHelper.tsx index 4c7d5716306..81fef5801e1 100644 --- a/src/shared/services/guideResolverHelper.tsx +++ b/src/shared/services/guideResolverHelper.tsx @@ -42,7 +42,7 @@ export async function resolveGuide( function mdxTextToSlides(mdx: string): JSX.Element[] { return splitMdxSlides(mdx).map((slide, index) => ( // index is fine since we'll never move or delete slides - + )) } @@ -52,10 +52,10 @@ function htmlTextToSlides(html: string): JSX.Element[] { const htmlSlides = tmpDiv.getElementsByTagName('slide') if (htmlSlides && htmlSlides.length) { return Array.from(htmlSlides).map((slide, index) => ( - + )) } - return [] + return [] } async function resolveRemoteGuideFromURL( From 7cd46727507ad3a1a3dc337aea3a5cacb3333757 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 15:02:46 +0200 Subject: [PATCH 20/64] Keep carousel at bottom --- src/browser/modules/Carousel/styled.tsx | 2 +- .../modules/GuideCarousel/GuideCarousel.tsx | 12 ++++---- src/browser/modules/Sidebar/GuidesDrawer.tsx | 28 +++++++++---------- src/browser/modules/Sidebar/styled.tsx | 18 ++---------- 4 files changed, 25 insertions(+), 35 deletions(-) diff --git a/src/browser/modules/Carousel/styled.tsx b/src/browser/modules/Carousel/styled.tsx index a126bcd4377..97a8c5b8975 100644 --- a/src/browser/modules/Carousel/styled.tsx +++ b/src/browser/modules/Carousel/styled.tsx @@ -49,9 +49,9 @@ export const StyledCarouselButtonContainer = styled.div` justify-content: center; position: absolute; bottom: 0; + left: 0; z-index: 10; border-top: ${props => props.theme.inFrameBorder}; - margin-left: -40px; height: 39px; width: 100%; diff --git a/src/browser/modules/GuideCarousel/GuideCarousel.tsx b/src/browser/modules/GuideCarousel/GuideCarousel.tsx index 2af5a7aa3ee..b3557df0bbc 100644 --- a/src/browser/modules/GuideCarousel/GuideCarousel.tsx +++ b/src/browser/modules/GuideCarousel/GuideCarousel.tsx @@ -64,10 +64,12 @@ function GuidesCarousel({ const moreThanOneSlide = slides.length > 1 return ( - - - - +
+ + + + + {moreThanOneSlide && ( @@ -112,7 +114,7 @@ function GuidesCarousel({ )} - +
) } export default GuidesCarousel diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index 674c8b9808a..bff57c62e4b 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -21,7 +21,7 @@ import React, { useRef } from 'react' import { connect } from 'react-redux' -import { DrawerBody, DrawerHeader } from 'browser-components/drawer' +import { DrawerHeader } from 'browser-components/drawer' import { getGuide, startGuide, @@ -29,7 +29,6 @@ import { defaultGuide } from 'shared/modules/guides/guidesDuck' import { GlobalState } from 'shared/globalState' -import { GuideContent, StyledHeaderContainer, WideDrawer } from './styled' import GuidesCarousel from '../GuideCarousel/GuideCarousel' import { BackIcon } from '../../components/icons/Icons' @@ -40,7 +39,11 @@ function GuidesDrawer({ }: GuidesDrawerProps): JSX.Element { const scrollRef = useRef(null) return ( - +
{guide.title !== defaultGuide.title && ( @@ -49,19 +52,16 @@ function GuidesDrawer({ )} Neo4j Browser Guides - - - - scrollRef.current?.scrollIntoView({ block: 'start' }) - } - /> - - - + + scrollRef.current?.scrollIntoView({ block: 'start' }) + } + /> +
) } + const mapStateToProps = (state: GlobalState) => ({ guide: getGuide(state) }) const mapDispatchToProps = (dispatch: any) => ({ backToAllGuides: () => dispatch(startGuide()) diff --git a/src/browser/modules/Sidebar/styled.tsx b/src/browser/modules/Sidebar/styled.tsx index bd1130e1b8b..e76562bb8c3 100644 --- a/src/browser/modules/Sidebar/styled.tsx +++ b/src/browser/modules/Sidebar/styled.tsx @@ -19,11 +19,7 @@ */ import { Button } from 'semantic-ui-react' import styled from 'styled-components' -import { - Drawer, - DrawerBody, - DrawerBrowserCommand -} from 'browser-components/drawer' +import { DrawerBody, DrawerBrowserCommand } from 'browser-components/drawer' export const StyledSetting = styled.div` padding-bottom: 15px; @@ -126,6 +122,7 @@ export const StyledCommand = styled(DrawerBrowserCommand)` ` export const StyledCarousel = styled.div` + height: 100%; padding-bottom: 20px; min-height: 100%; width: 100%; @@ -150,9 +147,9 @@ export const StyledCarouselButtonContainer = styled.div` justify-content: center; position: absolute; bottom: 0; + left: 0; z-index: 10; border-top: ${props => props.theme.inFrameBorder}; - margin-left: -40px; height: 39px; width: 100%; @@ -258,12 +255,3 @@ export const StyledUl = styled.ul` margin: 0 !important; padding-left: 0 !important; ` - -export const WideDrawer = styled(Drawer)` - width: 500px; - position: relative; -` - -export const GuideContent = styled.div` - padding-bottom: 40px; -` From bbac60701459cbddeb9abd3d53b7b03d495ed280 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 15:15:57 +0200 Subject: [PATCH 21/64] Wider sidebar --- src/browser/components/TabNavigation/Navigation.tsx | 11 +++++++---- src/browser/components/TabNavigation/styled.tsx | 4 ++-- src/shared/modules/sidebar/sidebarDuck.ts | 4 +++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/browser/components/TabNavigation/Navigation.tsx b/src/browser/components/TabNavigation/Navigation.tsx index f330a12a28f..9d5f5e4ac70 100644 --- a/src/browser/components/TabNavigation/Navigation.tsx +++ b/src/browser/components/TabNavigation/Navigation.tsx @@ -32,6 +32,7 @@ import { StyledTopNav, StyledBottomNav } from './styled' +import { GUIDE_DRAWER_ID } from 'shared/modules/sidebar/sidebarDuck' const Closing = 'CLOSING' const Closed = 'CLOSED' @@ -169,6 +170,11 @@ class Navigation extends Component { this.state.drawerContent ) + const drawerWidth = this.props.openDrawer === GUIDE_DRAWER_ID ? 500 : 300 + const useFullWidth = + this.state.transitionState === Open || + this.state.transitionState === Opening + const width = useFullWidth ? drawerWidth : 0 return ( @@ -176,10 +182,7 @@ class Navigation extends Component { {bottomNavItemsList} { if (ref) { // Remove old listeners so we don't get multiple callbacks. diff --git a/src/browser/components/TabNavigation/styled.tsx b/src/browser/components/TabNavigation/styled.tsx index 321a655c999..915b1876adf 100644 --- a/src/browser/components/TabNavigation/styled.tsx +++ b/src/browser/components/TabNavigation/styled.tsx @@ -28,12 +28,12 @@ export const StyledSidebar = styled.div` color: #fff; ` -export const StyledDrawer = styled.div<{ open: boolean }>` +export const StyledDrawer = styled.div<{ width: number }>` flex: 0 0 auto; background-color: #31333b; overflow-x: hidden; overflow-y: auto; - width: ${props => (props.open ? 'auto' : '0px')}; + width: ${props => props.width}px; transition: 0.2s ease-out; z-index: 1; ` diff --git a/src/shared/modules/sidebar/sidebarDuck.ts b/src/shared/modules/sidebar/sidebarDuck.ts index 876a6f3746e..7d982881164 100644 --- a/src/shared/modules/sidebar/sidebarDuck.ts +++ b/src/shared/modules/sidebar/sidebarDuck.ts @@ -41,6 +41,8 @@ export function getScriptDraftId(state: GlobalState): string | null { return state[NAME].scriptId || null } +export const GUIDE_DRAWER_ID = 'guides' + // SIDEBAR type DrawerId = | 'dbms' @@ -48,10 +50,10 @@ type DrawerId = | 'documents' | 'sync' | 'favorites' - | 'guides' | 'about' | 'project files' | 'settings' + | typeof GUIDE_DRAWER_ID | null export interface SidebarState { drawer: DrawerId | null From a35193a843c67fbc44090dc46f709e5a137a9934 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 15:43:00 +0200 Subject: [PATCH 22/64] Add network error --- src/browser/documentation/index.ts | 12 +++++++- src/shared/services/guideResolverHelper.tsx | 32 +++++++++++++++------ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/browser/documentation/index.ts b/src/browser/documentation/index.ts index 9a2732d6a2a..26a74c5d5bb 100644 --- a/src/browser/documentation/index.ts +++ b/src/browser/documentation/index.ts @@ -131,7 +131,12 @@ type GuideDocs = { title: 'Built-in Browser guides' chapters: Record } -type GuideChapter = 'index' | 'movieGraph' | 'movies' | 'unfound' +type GuideChapter = + | 'index' + | 'movieGraph' + | 'movie-graph' + | 'movies' + | 'unfound' // TypeGuard function to ts to understand that a string is a valid key export function isGuideChapter(name: string): name is GuideChapter { return name in docs.guide.chapters @@ -148,9 +153,11 @@ export type PlayChapter = | 'iconography' | 'intro' | 'learn' + | 'movie-graph' | 'movieGraph' | 'movies' | 'northwind' + | 'northwind-graph' | 'northwindGraph' | 'start' | 'typography' @@ -326,8 +333,10 @@ const docs: AllDocumentation = { intro: playIntro, learn: playLearn, movieGraph: playMovieGraph, + 'movie-graph': playMovieGraph, movies: playMovieGraph, northwind: playNorthwindGraph, + 'northwind-graph': playNorthwindGraph, northwindGraph: playNorthwindGraph, start: playStart, typography: playTypography, @@ -342,6 +351,7 @@ const docs: AllDocumentation = { index: guideIndex, movies: guideMovieGraph, movieGraph: guideMovieGraph, + 'movie-graph': guideMovieGraph, unfound: guideUnfound } } diff --git a/src/shared/services/guideResolverHelper.tsx b/src/shared/services/guideResolverHelper.tsx index 81fef5801e1..bac8d46685c 100644 --- a/src/shared/services/guideResolverHelper.tsx +++ b/src/shared/services/guideResolverHelper.tsx @@ -15,6 +15,15 @@ import { getRemoteContentHostnameAllowlist } from 'shared/modules/dbMeta/dbMetaDuck' import { splitMdxSlides } from 'browser/modules/Docs/MDX/splitMdx' +import { + StyledCypherErrorMessage, + StyledDiv, + StyledErrorH4, + StyledHelpContent, + StyledHelpDescription, + StyledHelpFrame, + StyledPreformattedArea +} from 'browser/modules/Stream/styled' const { chapters } = docs.guide const unfound = { slides: [guideUnfound.content], title: guideUnfound.title } @@ -92,14 +101,21 @@ async function resolveRemoteGuideFromURL( return { title: 'Error', slides: [ - + + + + + ERROR + Remote guide error + + + + {e.name}: {e.message} + + + + + ] } } From 34f4fcdf8a793f915bb6294b8d101fd7491ec0fa Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 16:02:12 +0200 Subject: [PATCH 23/64] Prevent title overflow --- src/browser/modules/Sidebar/GuidesDrawer.tsx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index bff57c62e4b..0d88bbc337d 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -40,7 +40,7 @@ function GuidesDrawer({ const scrollRef = useRef(null) return (
@@ -52,6 +52,15 @@ function GuidesDrawer({ )} Neo4j Browser Guides +

+ {guide.title} +

From 375706afd1210fb7609b7b7eb6d53eece96b71b6 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Tue, 13 Apr 2021 16:18:25 +0200 Subject: [PATCH 24/64] Start formatting movies guide --- .../sidebar-guides/movie-graph.tsx | 553 ++++++++---------- src/browser/modules/Sidebar/GuidesDrawer.tsx | 1 + 2 files changed, 252 insertions(+), 302 deletions(-) diff --git a/src/browser/documentation/sidebar-guides/movie-graph.tsx b/src/browser/documentation/sidebar-guides/movie-graph.tsx index 6120198dfd1..3aef2934b41 100644 --- a/src/browser/documentation/sidebar-guides/movie-graph.tsx +++ b/src/browser/documentation/sidebar-guides/movie-graph.tsx @@ -24,61 +24,45 @@ import Slide from '../../modules/Carousel/Slide' const title = 'Movie Graph' const category = 'graphExamples' +// TODO not happy with the force dark mode const slides = [ -
-

Movie Graph

-

Pop-cultural connections between actors and movies

-
-
-

- The Movie Graph is a mini graph application containing actors - and directors that are related through the movies they've collaborated - on. -

-

This guide will show you how to:

-
    -
  1. Create: insert movie data into the graph
  2. -
  3. Find: retrieve individual movies and actors
  4. -
  5. Query: discover related actors and directors
  6. -
  7. Solve: the Bacon Path
  8. -
-

-

- WARNING: This guide will modify the data in the currently active - database.{' '} -

-
+

+ The Movie Graph is a mini graph application containing actors and + directors that are related through the movies they've collaborated on. +

+

This guide will show you how to:

+
    +
  1. Create: insert movie data into the graph
  2. +
  3. Find: retrieve individual movies and actors
  4. +
  5. Query: discover related actors and directors
  6. +
  7. Solve: the Bacon Path
  8. +
+

+

+ WARNING: This guide will modify the data in the currently active + database.{' '} +

, - -
-
The Movie Graph
-
-

Create

-

- To the right is a giant code block containing a single Cypher query - statement composed of multiple CREATE clauses. This will create the - movie graph. -

-
    -
  1. Click on the code block
  2. -
  3. Notice it gets copied to the editor above ↑
  4. -
  5. Click the editor's play button to execute
  6. -
  7. Wait for the query to finish
  8. -
-

- WARNING: This adds data to the current database, each time it is run! -

-
-

- :help cypher{' '} - CREATE -

-
-
-
-
-          {`CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
+  
+    

Create

+

+ To the right is a giant code block containing a single Cypher query + statement composed of multiple CREATE clauses. This will create the movie + graph. +

+
    +
  1. Click on the code block
  2. +
  3. Notice it gets copied to the editor above ↑
  4. +
  5. Click the editor's play button to execute
  6. +
  7. Wait for the query to finish
  8. +
+

+ WARNING: This adds data to the current database, each time it is run! +

+
+
+        {`CREATE (TheMatrix:Movie {title:'The Matrix', released:1999, tagline:'Welcome to the Real World'})
 CREATE (Keanu:Person {name:'Keanu Reeves', born:1964})
 CREATE (Carrie:Person {name:'Carrie-Anne Moss', born:1967})
 CREATE (Laurence:Person {name:'Laurence Fishburne', born:1961})
@@ -586,271 +570,236 @@ CREATE
 
 WITH TomH as a
 MATCH (a)-[:ACTED_IN]->(m)<-[:DIRECTED]-(d) RETURN a,m,d LIMIT 10;`}
-        
-
-
+ + +

+ :help cypher{' '} + CREATE +

, - -
-
The Movie Graph
-
-

Find

-

Example queries for finding individual nodes.

-
    -
  1. Click on any query example
  2. -
  3. Run the query from the editor
  4. -
  5. Notice the syntax pattern
  6. -
  7. Try looking for other movies or actors
  8. -
-
-

- :help MATCH{' '} - WHERE RETURN -

-
-
-

Find the actor named "Tom Hanks"...

-
-
-          {'MATCH (tom {name: "Tom Hanks"}) RETURN tom'}
-        
-
-

Find the movie with title "Cloud Atlas"...

-
-
-          {'MATCH (cloudAtlas {title: "Cloud Atlas"}) RETURN cloudAtlas'}
-        
-
-

Find 10 people...

-
-
-          MATCH (people:Person) RETURN people.name LIMIT 10
-        
-
-

Find movies released in the 1990s...

-
-
-          {
-            'MATCH (nineties:Movie) WHERE nineties.released >= 1990 AND nineties.released < 2000 RETURN nineties.title'
-          }
-        
-
-
+ +

Find

+

Example queries for finding individual nodes.

+
    +
  1. Click on any query example
  2. +
  3. Run the query from the editor
  4. +
  5. Notice the syntax pattern
  6. +
  7. Try looking for other movies or actors
  8. +
+
+

+ :help MATCH{' '} + WHERE RETURN +

+

Find the actor named "Tom Hanks"...

+
+
+        {'MATCH (tom {name: "Tom Hanks"}) RETURN tom'}
+      
+
+

Find the movie with title "Cloud Atlas"...

+
+
+        {'MATCH (cloudAtlas {title: "Cloud Atlas"}) RETURN cloudAtlas'}
+      
+
+

Find 10 people...

+
+
+        MATCH (people:Person) RETURN people.name LIMIT 10
+      
+
+

Find movies released in the 1990s...

+
+
+        {
+          'MATCH (nineties:Movie) WHERE nineties.released >= 1990 AND nineties.released < 2000 RETURN nineties.title'
+        }
+      
+
, - -
-
The Movie Graph
-
-

Query

-

Finding patterns within the graph.

-
    -
  1. Actors are people who acted in movies
  2. -
  3. Directors are people who directed a movie
  4. -
  5. What other relationships exist?
  6. -
-
-

- :help MATCH -

-
-
-

List all Tom Hanks movies...

-
-
-          {
-            'MATCH (tom:Person {name: "Tom Hanks"})-[:ACTED_IN]->(tomHanksMovies) RETURN tom,tomHanksMovies'
-          }
-        
-
-

Who directed "Cloud Atlas"?

-
-
-          {
-            'MATCH (cloudAtlas {title: "Cloud Atlas"})<-[:DIRECTED]-(directors) RETURN directors.name'
-          }
-        
-
-

Tom Hanks' co-actors...

-
-
-          {
-            'MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors) RETURN coActors.name'
-          }
-        
-
-

How people are related to "Cloud Atlas"...

-
-
-          {
-            'MATCH (people:Person)-[relatedTo]-(:Movie {title: "Cloud Atlas"}) RETURN people.name, Type(relatedTo), relatedTo'
-          }
-        
-
-
+ +

Query

+

Finding patterns within the graph.

+
    +
  1. Actors are people who acted in movies
  2. +
  3. Directors are people who directed a movie
  4. +
  5. What other relationships exist?
  6. +
+
+

List all Tom Hanks movies...

+
+
+        {
+          'MATCH (tom:Person {name: "Tom Hanks"})-[:ACTED_IN]->(tomHanksMovies) RETURN tom,tomHanksMovies'
+        }
+      
+
+

Who directed "Cloud Atlas"?

+
+
+        {
+          'MATCH (cloudAtlas {title: "Cloud Atlas"})<-[:DIRECTED]-(directors) RETURN directors.name'
+        }
+      
+
+

Tom Hanks' co-actors...

+
+
+        {
+          'MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors) RETURN coActors.name'
+        }
+      
+
+

How people are related to "Cloud Atlas"...

+
+
+        {
+          'MATCH (people:Person)-[relatedTo]-(:Movie {title: "Cloud Atlas"}) RETURN people.name, Type(relatedTo), relatedTo'
+        }
+      
+
+

+ :help MATCH +

, - -
-
The Movie Graph
-
-

Solve

-

- You've heard of the classic "Six Degrees of Kevin Bacon"? That is simply - a shortest path query called the "Bacon Path". -

-
    -
  1. Variable length patterns
  2. -
  3. Built-in shortestPath() algorithm
  4. -
-
-
-

- Movies and actors up to 4 "hops" away from Kevin Bacon -

-
-
-          {`MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..4]-(hollywood)
+  
+    

Solve

+

+ You've heard of the classic "Six Degrees of Kevin Bacon"? That is simply a + shortest path query called the "Bacon Path". +

+
    +
  1. Variable length patterns
  2. +
  3. Built-in shortestPath() algorithm
  4. +
+

+ Movies and actors up to 4 "hops" away from Kevin Bacon +

+
+
+        {`MATCH (bacon:Person {name:"Kevin Bacon"})-[*1..4]-(hollywood)
 RETURN DISTINCT hollywood`}
-        
-
-

- Bacon path, the shortest path of any relationships to Meg Ryan -

-
-
-          {`MATCH p=shortestPath(
+      
+
+

+ Bacon path, the shortest path of any relationships to Meg Ryan +

+
+
+        {`MATCH p=shortestPath(
 (bacon:Person {name:"Kevin Bacon"})-[*]-(meg:Person {name:"Meg Ryan"})
 )
 RETURN p`}
-        
- -
-
+ + +
, - -
-
The Movie Graph
-
-

Recommend

-

- Let's recommend new co-actors for Tom Hanks. A basic recommendation - approach is to find connections past an immediate neighborhood which are - themselves well connected. -

-

For Tom Hanks, that means:

-
    -
  1. - Find actors that Tom Hanks hasn't yet worked with, but his co-actors - have. -
  2. -
  3. Find someone who can introduce Tom to his potential co-actor.
  4. -
-
-
-

- Extend Tom Hanks co-actors, to find co-co-actors who haven't worked with - Tom Hanks... -

-
-
-          {`MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
+  
+    

Recommend

+

+ Let's recommend new co-actors for Tom Hanks. A basic recommendation + approach is to find connections past an immediate neighborhood which are + themselves well connected. +

+

For Tom Hanks, that means:

+
    +
  1. + Find actors that Tom Hanks hasn't yet worked with, but his co-actors + have. +
  2. +
  3. Find someone who can introduce Tom to his potential co-actor.
  4. +
+

+ Extend Tom Hanks co-actors, to find co-co-actors who haven't worked with + Tom Hanks... +

+
+
+        {`MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
   (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cocoActors)
 WHERE NOT (tom)-[:ACTED_IN]->()<-[:ACTED_IN]-(cocoActors) AND tom <> cocoActors
 RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC`}
-        
-
-

Find someone to introduce Tom Hanks to Tom Cruise

-
-
-          {`MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
+      
+
+

Find someone to introduce Tom Hanks to Tom Cruise

+
+
+        {`MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors),
   (coActors)-[:ACTED_IN]->(m2)<-[:ACTED_IN]-(cruise:Person {name:"Tom Cruise"})
 RETURN tom, m, coActors, m2, cruise`}
-        
-
-
+ +
, - -
-
The Movie Graph
-
-

Clean up

-

When you're done experimenting, you can remove the movie data set.

-

Note:

-
    -
  1. Nodes can't be deleted if relationships exist
  2. -
  3. Delete both nodes and relationships together
  4. -
-

- WARNING: This will remove all Person and Movie nodes! -

-
-

- :help DELETE -

-
-
-

- Delete all Movie and Person nodes, and their relationships -

-
-
-          MATCH (n) DETACH DELETE n
-        
- -
-

Prove that the Movie Graph is gone

-
-
MATCH (n) RETURN n
-
-
+ +

Clean up

+

When you're done experimenting, you can remove the movie data set.

+

Note:

+
    +
  1. Nodes can't be deleted if relationships exist
  2. +
  3. Delete both nodes and relationships together
  4. +
+

+ WARNING: This will remove all Person and Movie nodes! +

+
+

+ Delete all Movie and Person nodes, and their relationships +

+
+
+        MATCH (n) DETACH DELETE n
+      
+ +
+

Prove that the Movie Graph is gone

+
+
MATCH (n) RETURN n
+
+

+ :help DELETE +

, - -
-

Next steps

- - -
-
-

Documentation

- -
+ +

Next steps

+ + +

Documentation

+
] diff --git a/src/browser/modules/Sidebar/GuidesDrawer.tsx b/src/browser/modules/Sidebar/GuidesDrawer.tsx index 0d88bbc337d..a95d180b70b 100644 --- a/src/browser/modules/Sidebar/GuidesDrawer.tsx +++ b/src/browser/modules/Sidebar/GuidesDrawer.tsx @@ -61,6 +61,7 @@ function GuidesDrawer({ > {guide.title} +
From 69c5471cdfb5ccb4bc3c72ceecf40af7e9f0d250 Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Wed, 14 Apr 2021 12:54:46 +0200 Subject: [PATCH 25/64] Update snapshots --- .../__snapshots__/index.test.tsx.snap | 6 +- .../__snapshots__/MetaItems.test.tsx.snap | 52 ++++++------- .../__snapshots__/Settings.test.tsx.snap | 16 ++-- .../__snapshots__/ErrorsView.test.tsx.snap | 38 ++++----- .../VisualizationView.test.tsx.snap | 4 +- .../__snapshots__/SchemaFrame.test.tsx.snap | 78 +++++++++---------- 6 files changed, 97 insertions(+), 97 deletions(-) diff --git a/src/browser/modules/ClickToCode/__snapshots__/index.test.tsx.snap b/src/browser/modules/ClickToCode/__snapshots__/index.test.tsx.snap index 00f13dac53b..4f7e8d8bc5a 100644 --- a/src/browser/modules/ClickToCode/__snapshots__/index.test.tsx.snap +++ b/src/browser/modules/ClickToCode/__snapshots__/index.test.tsx.snap @@ -5,7 +5,7 @@ exports[`ClickToCode does not render if no children 1`] = `
`; exports[`ClickToCode renders all children 1`] = `
@@ -20,7 +20,7 @@ exports[`ClickToCode renders all children 1`] = ` exports[`ClickToCode renders children as code if no code is provided 1`] = `
hellohi! @@ -30,7 +30,7 @@ exports[`ClickToCode renders children as code if no code is provided 1`] = ` exports[`ClickToCode renders code as the code when code is available 1`] = `
hello diff --git a/src/browser/modules/DBMSInfo/__snapshots__/MetaItems.test.tsx.snap b/src/browser/modules/DBMSInfo/__snapshots__/MetaItems.test.tsx.snap index bd4bdb789cd..6a30703065f 100644 --- a/src/browser/modules/DBMSInfo/__snapshots__/MetaItems.test.tsx.snap +++ b/src/browser/modules/DBMSInfo/__snapshots__/MetaItems.test.tsx.snap @@ -3,15 +3,15 @@ exports[`LabelItems renders empty 1`] = `
Node Labels

There are no labels in database @@ -24,30 +24,30 @@ exports[`LabelItems renders empty 1`] = ` exports[`LabelItems renders labels 1`] = `

Node Labels
*
MyLabel
MyLabel2 @@ -60,15 +60,15 @@ exports[`LabelItems renders labels 1`] = ` exports[`PropertyItems renders empty 1`] = `
Property Keys

There are no properties in database @@ -81,24 +81,24 @@ exports[`PropertyItems renders empty 1`] = ` exports[`PropertyItems renders properties 1`] = `

Property Keys
prop1
prop2 @@ -111,15 +111,15 @@ exports[`PropertyItems renders properties 1`] = ` exports[`RelationshipItems renders empty 1`] = `
Relationship Types

No relationships in database @@ -132,30 +132,30 @@ exports[`RelationshipItems renders empty 1`] = ` exports[`RelationshipItems renders relationshipTypes 1`] = `

Relationship Types
*
MY_TYPE
MY_TYPE2 diff --git a/src/browser/modules/Sidebar/__snapshots__/Settings.test.tsx.snap b/src/browser/modules/Sidebar/__snapshots__/Settings.test.tsx.snap index 6c305d2bfab..8876f0c574d 100644 --- a/src/browser/modules/Sidebar/__snapshots__/Settings.test.tsx.snap +++ b/src/browser/modules/Sidebar/__snapshots__/Settings.test.tsx.snap @@ -12,29 +12,29 @@ exports[`Settings renders with strange characters in display name 1`] = ` Browser Settings
Test åäö settings
diff --git a/src/browser/modules/Stream/CypherFrame/__snapshots__/ErrorsView.test.tsx.snap b/src/browser/modules/Stream/CypherFrame/__snapshots__/ErrorsView.test.tsx.snap index e0292585916..0558b77ca70 100644 --- a/src/browser/modules/Stream/CypherFrame/__snapshots__/ErrorsView.test.tsx.snap +++ b/src/browser/modules/Stream/CypherFrame/__snapshots__/ErrorsView.test.tsx.snap @@ -6,11 +6,11 @@ exports[`ErrorsViews ErrorsStatusbar displays error 1`] = ` class="sc-bdVaJa kwzqrW" > Test.Error: Test error description @@ -26,42 +26,42 @@ exports[`ErrorsViews ErrorsView displays nothing if no errors 1`] = `
`; exports[`ErrorsViews ErrorsView displays procedure link if unknown procedure 1`] = `
ERROR

Neo.ClientError.Procedure.ProcedureNotFound

           not found
         
 List available procedures @@ -74,30 +74,30 @@ exports[`ErrorsViews ErrorsView displays procedure link if unknown procedure 1`] exports[`ErrorsViews ErrorsView does displays an error 1`] = `
ERROR

Test.Error

           Test error description
         
diff --git a/src/browser/modules/Stream/CypherFrame/__snapshots__/VisualizationView.test.tsx.snap b/src/browser/modules/Stream/CypherFrame/__snapshots__/VisualizationView.test.tsx.snap index bfb2316fbee..a54089db3f2 100644 --- a/src/browser/modules/Stream/CypherFrame/__snapshots__/VisualizationView.test.tsx.snap +++ b/src/browser/modules/Stream/CypherFrame/__snapshots__/VisualizationView.test.tsx.snap @@ -137,7 +137,7 @@ exports[`Visualization renders with result and escapes any HTML 1`] = ` class="sc-cvbbAY ZoyPP faded zoom-in" > @@ -145,7 +145,7 @@ exports[`Visualization renders with result and escapes any HTML 1`] = ` class="sc-cvbbAY ZoyPP zoom-out" > diff --git a/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap b/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap index 5163bbf91f0..fa91ce2d21d 100644 --- a/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap +++ b/src/browser/modules/Stream/__snapshots__/SchemaFrame.test.tsx.snap @@ -7,16 +7,16 @@ exports[`SchemaFrame renders empty 1`] = ` >
@@ -24,10 +24,10 @@ exports[`SchemaFrame renders empty 1`] = ` @@ -35,13 +35,13 @@ exports[`SchemaFrame renders empty 1`] = `
Indexes
None
@@ -49,10 +49,10 @@ exports[`SchemaFrame renders empty 1`] = ` @@ -89,46 +89,46 @@ exports[`SchemaFrame renders empty for Neo4j >= 4.0 1`] = ` >
Constraints
None
@@ -136,42 +136,42 @@ exports[`SchemaFrame renders empty for Neo4j >= 4.0 1`] = `
Index Name Type Uniqueness EntityType LabelsOrTypes Properties State
None
@@ -179,10 +179,10 @@ exports[`SchemaFrame renders empty for Neo4j >= 4.0 1`] = ` @@ -219,16 +219,16 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = ` >
Constraints
None
@@ -236,10 +236,10 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = ` @@ -247,13 +247,13 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = `
Indexes
ON :Movie(released) ONLINE
@@ -261,10 +261,10 @@ exports[`SchemaFrame renders results for Neo4j < 4.0 1`] = ` From 197adbb82c68c068b9109bf26e0112e0ef44d2cb Mon Sep 17 00:00:00 2001 From: Oskar Damkjaer Date: Wed, 14 Apr 2021 19:09:03 +0200 Subject: [PATCH 26/64] Self review --- src/browser/components/ConfirmationDialog.tsx | 63 ------------------- src/browser/documentation/index.ts | 1 + src/browser/modules/Carousel/Carousel.tsx | 7 +-- src/browser/modules/Docs/Docs.tsx | 5 +- src/browser/modules/Frame/FrameTitlebar.tsx | 28 +-------- .../modules/GuideCarousel/GuideCarousel.tsx | 20 ++++++ src/browser/modules/Sidebar/DocumentItems.tsx | 1 + .../services/commandInterpreterHelper.ts | 5 +- src/shared/services/guideResolverHelper.tsx | 21 ++++++- 9 files changed, 50 insertions(+), 101 deletions(-) delete mode 100644 src/browser/components/ConfirmationDialog.tsx diff --git a/src/browser/components/ConfirmationDialog.tsx b/src/browser/components/ConfirmationDialog.tsx deleted file mode 100644 index 3195d9a56d6..00000000000 --- a/src/browser/components/ConfirmationDialog.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) "Neo4j" - * Neo4j Sweden AB [http://neo4j.com] - * - * This file is part of Neo4j. - * - * Neo4j is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -import React from 'react' -import { Button, Modal } from 'semantic-ui-react' - -interface ConfirmationDialogProps { - cancelLabel?: string - confirmLabel?: string - onClose: () => void - onConfirm: () => void - open: boolean -} - -function ConfirmationDialog({ - cancelLabel = 'Cancel', - children, - confirmLabel = 'OK', - onClose, - onConfirm, - open -}: React.PropsWithChildren): JSX.Element { - return ( - onClose()} - open={open} - size="tiny" - style={{ fontSize: '16px', lineHeight: '1.5' }} - > - {children} - - -
Constraints
ON ( book:Book ) ASSERT book.isbn IS UNIQUE