From 68991abba7a2b32047ec7cc6402c3a8b5cc6ec52 Mon Sep 17 00:00:00 2001 From: Aman Harwara Date: Tue, 15 Nov 2022 18:58:13 +0530 Subject: [PATCH] feat: add smart view with custom json (#2000) --- .../Components/Icon/IconNameToSvgMapping.tsx | 1 + .../Components/Shared/CopyableCodeBlock.tsx | 63 ++++++++ .../SmartViewBuilder/AddSmartViewModal.tsx | 143 +++++++++++++++++- .../AddSmartViewModalController.ts | 48 +++++- .../CompoundPredicateBuilder.tsx | 2 +- .../src/javascripts/Components/Tabs/Tab.tsx | 35 +++++ .../javascripts/Components/Tabs/TabList.tsx | 25 +++ .../javascripts/Components/Tabs/TabPanel.tsx | 22 +++ .../Components/Tabs/TabsContainer.tsx | 29 ++++ .../Components/Tabs/useTabState.ts | 29 ++++ 10 files changed, 387 insertions(+), 10 deletions(-) create mode 100644 packages/web/src/javascripts/Components/Shared/CopyableCodeBlock.tsx create mode 100644 packages/web/src/javascripts/Components/Tabs/Tab.tsx create mode 100644 packages/web/src/javascripts/Components/Tabs/TabList.tsx create mode 100644 packages/web/src/javascripts/Components/Tabs/TabPanel.tsx create mode 100644 packages/web/src/javascripts/Components/Tabs/TabsContainer.tsx create mode 100644 packages/web/src/javascripts/Components/Tabs/useTabState.ts diff --git a/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx b/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx index 98b666280..028ab6322 100644 --- a/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx +++ b/packages/web/src/javascripts/Components/Icon/IconNameToSvgMapping.tsx @@ -9,6 +9,7 @@ export const IconNameToSvgMapping = { 'attachment-file': icons.AttachmentFileIcon, 'check-bold': icons.CheckBoldIcon, 'check-circle': icons.CheckCircleIcon, + 'chevron-up': icons.ChevronUpIcon, 'chevron-down': icons.ChevronDownIcon, 'chevron-left': icons.ChevronLeftIcon, 'chevron-right': icons.ChevronRightIcon, diff --git a/packages/web/src/javascripts/Components/Shared/CopyableCodeBlock.tsx b/packages/web/src/javascripts/Components/Shared/CopyableCodeBlock.tsx new file mode 100644 index 000000000..2417cb1e9 --- /dev/null +++ b/packages/web/src/javascripts/Components/Shared/CopyableCodeBlock.tsx @@ -0,0 +1,63 @@ +import { classNames } from '@/Utils/ConcatenateClassNames' +import { addToast, ToastType } from '@standardnotes/toast' +import { useRef, useState } from 'react' +import Icon from '../Icon/Icon' + +type Props = { + code: string +} + +const CopyableCodeBlock = ({ code }: Props) => { + const buttonRef = useRef(null) + const [didCopy, setDidCopy] = useState(false) + const [isCopyButtonVisible, setIsCopyButtonVisible] = useState(false) + + return ( +
setIsCopyButtonVisible(true)} + onMouseLeave={() => setIsCopyButtonVisible(false)} + > +
{code}
+
+ +
+ {didCopy ? 'Copied!' : 'Copy example to clipboard'} +
+
+
+ ) +} + +export default CopyableCodeBlock diff --git a/packages/web/src/javascripts/Components/SmartViewBuilder/AddSmartViewModal.tsx b/packages/web/src/javascripts/Components/SmartViewBuilder/AddSmartViewModal.tsx index fe9607219..d4e073843 100644 --- a/packages/web/src/javascripts/Components/SmartViewBuilder/AddSmartViewModal.tsx +++ b/packages/web/src/javascripts/Components/SmartViewBuilder/AddSmartViewModal.tsx @@ -10,35 +10,113 @@ import ModalDialogLabel from '@/Components/Shared/ModalDialogLabel' import Spinner from '@/Components/Spinner/Spinner' import { Platform } from '@standardnotes/snjs' import { observer } from 'mobx-react-lite' -import { useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { AddSmartViewModalController } from './AddSmartViewModalController' +import TabPanel from '../Tabs/TabPanel' +import { useTabState } from '../Tabs/useTabState' +import TabsContainer from '../Tabs/TabsContainer' +import CopyableCodeBlock from '../Shared/CopyableCodeBlock' +import { Disclosure, DisclosureButton, DisclosurePanel } from '@reach/disclosure' +import { classNames } from '@/Utils/ConcatenateClassNames' type Props = { controller: AddSmartViewModalController platform: Platform } +const ConflictedNotesExampleCode = `{ + "keypath": "content.conflict_of.length", + "operator": ">", + "value": 0 +}` + +const ComplexCompoundExampleCode = `{ + "operator": "and", + "value": [ + { + "operator": "not", + "value": { + "keypath": "tags", + "operator": "includes", + "value": { + "keypath": "title", + "operator": "=", + "value": "completed" + } + } + }, + { + "keypath": "tags", + "operator": "includes", + "value": { + "keypath": "title", + "operator": "=", + "value": "todo" + } + } + ] +} +` + const AddSmartViewModal = ({ controller, platform }: Props) => { - const { isSaving, title, setTitle, icon, setIcon, closeModal, saveCurrentSmartView, predicateController } = controller + const { + isSaving, + title, + setTitle, + icon, + setIcon, + closeModal, + saveCurrentSmartView, + predicateController, + customPredicateJson, + setCustomPredicateJson, + isCustomJsonValidPredicate, + setIsCustomJsonValidPredicate, + validateAndPrettifyCustomPredicate, + } = controller const titleInputRef = useRef(null) + const customJsonInputRef = useRef(null) const [shouldShowIconPicker, setShouldShowIconPicker] = useState(false) const iconPickerButtonRef = useRef(null) + const [shouldShowJsonExamples, setShouldShowJsonExamples] = useState(false) + const toggleIconPicker = () => { setShouldShowIconPicker((shouldShow) => !shouldShow) } + const tabState = useTabState({ + defaultTab: 'builder', + }) + const save = () => { if (!title.length) { titleInputRef.current?.focus() return } + if (tabState.activeTab === 'custom' && !isCustomJsonValidPredicate) { + validateAndPrettifyCustomPredicate() + return + } + void saveCurrentSmartView() } + const canSave = tabState.activeTab === 'builder' || isCustomJsonValidPredicate + + useEffect(() => { + if (!customJsonInputRef.current) { + return + } + + if (tabState.activeTab === 'custom' && isCustomJsonValidPredicate === false) { + customJsonInputRef.current.focus() + } + }, [isCustomJsonValidPredicate, tabState.activeTab]) + return ( Add Smart View @@ -88,17 +166,68 @@ const AddSmartViewModal = ({ controller, platform }: Props) => {
Predicate:
- + + + + + +