diff --git a/apps/web/package.json b/apps/web/package.json index 71a76d8b0e453eb8d6e6bfbe644ab9669fdc53cf..8ed2832f69ec0b3e8ed758a4c0aa465d49021b89 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -22,6 +22,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-grid-layout": "^1.3.4", + "react-joyride": "^2.6.1", "react-redux": "^8.0.5", "react-router-dom": "^6.8.1", "reactflow": "11.4.0-next.1", diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 4f787f5b96c25f096829a7f3b2ca4a2038e85932..67b3237ba76305ccc559d7291af12009e0319802 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -37,6 +37,7 @@ import { LinkPredictionInstance, setMLResult, allMLTypes } from '@graphpolaris/s import { Resizable } from '@graphpolaris/shared/lib/components/Resizable'; import { DashboardAlerts } from '@graphpolaris/shared/lib/data-access/authorization/dashboardAlerts'; import { addError } from '@graphpolaris/shared/lib/data-access/store/configSlice'; +import Onboarding from '../components/onboarding/onboarding'; export interface App {} @@ -119,6 +120,7 @@ export function App(props: App) { return ( <div className="h-screen w-screen overflow-clip"> + <Onboarding /> <DashboardAlerts /> <div className={'h-screen w-screen ' + (!auth.authorized ? 'blur-sm pointer-events-none ' : '')}> <div className="flex flex-col h-screen max-h-screen relative"> diff --git a/apps/web/src/app/panels/Visualization.tsx b/apps/web/src/app/panels/Visualization.tsx index c13d286dacfaa3bf7994c327120db1d473017313..b41d5d8813182fddc383ceb9b166fc149cab5c19 100644 --- a/apps/web/src/app/panels/Visualization.tsx +++ b/apps/web/src/app/panels/Visualization.tsx @@ -5,7 +5,7 @@ import { useVisualizationCache } from '@graphpolaris/shared/lib/data-access'; export const VisualizationPanel = () => { const vis = useVisualizationCache() return ( - <div className="h-full w-full overflow-y-auto"> + <div className="vis-panel h-full w-full overflow-y-auto"> <h1>Visualization Panel</h1> <p className='text-sm'>{vis.visual}</p> {/* <div> diff --git a/apps/web/src/components/navbar/navbar.tsx b/apps/web/src/components/navbar/navbar.tsx index 76534f6016626d78ef7c77199b9347da8296b0c8..ce43ad98fb32c0c80d772b80468c80217405cd25 100644 --- a/apps/web/src/components/navbar/navbar.tsx +++ b/apps/web/src/components/navbar/navbar.tsx @@ -190,7 +190,7 @@ export const Navbar = (props: NavbarComponentProps) => { </div> <div className="w-fit"> - <div className=""> + <div className="menu-walkthrough"> <button tabIndex={0} className="btn btn-circle btn-ghost hover:bg-gray-200" diff --git a/apps/web/src/components/navbar/search/SearchBar.tsx b/apps/web/src/components/navbar/search/SearchBar.tsx index a92202f379c490459446cfc8bde610c0d43ae3f2..969774e74c80b254ea2273b27632ff634918c592 100644 --- a/apps/web/src/components/navbar/search/SearchBar.tsx +++ b/apps/web/src/components/navbar/search/SearchBar.tsx @@ -108,7 +108,7 @@ export function SearchBar({}) { }, []); return ( - <div className={`relative form-control mr-4 ${inputActive || search !== '' ? 'w-96' : 'w-56'} transition-width duration-300`}> + <div className={`searchbar relative form-control mr-4 ${inputActive || search !== '' ? 'w-96' : 'w-56'} transition-width duration-300`}> <div className="relative rounded-md shadow-sm w-full"> <input ref={inputRef} diff --git a/apps/web/src/components/onboarding/onboarding.tsx b/apps/web/src/components/onboarding/onboarding.tsx new file mode 100644 index 0000000000000000000000000000000000000000..534379ac7164b3c014e5a7f55b708599ce01167b --- /dev/null +++ b/apps/web/src/components/onboarding/onboarding.tsx @@ -0,0 +1,89 @@ +import React, { useState, useEffect } from 'react'; +import Joyride, { ACTIONS, EVENTS, STATUS } from 'react-joyride'; +import { useLocation } from 'react-router-dom'; +import CloseIcon from '@mui/icons-material/Close'; +import { useCases } from './use-cases'; +import { useAuthorizationCache } from '@graphpolaris/shared/lib/data-access'; + +interface OnboardingState { + run?: boolean; + stepIndex?: number; +} + +export default function Onboarding({}) { + const location = useLocation(); + const auth = useAuthorizationCache(); + const [showWalkthrough, setShowWalkthrough] = useState<boolean>(true); + const [onboarding, setOnboarding] = useState<OnboardingState>({ + run: false, + stepIndex: 0, + }); + + useEffect(() => { + // Check whether walkthrough cookie exists and if user is logged in + const isWalkthroughCompleted = document.cookie.includes('walkthroughCompleted=true'); + if (isWalkthroughCompleted || auth.userID) { + setShowWalkthrough(false); + } + }, []); + + const handleJoyrideCallback = (data: { action: any; index: any; status: any; type: any }) => { + const { action, index, status, type } = data; + if ([EVENTS.STEP_AFTER, EVENTS.TARGET_NOT_FOUND].includes(type)) { + setOnboarding({ stepIndex: index + (action === ACTIONS.PREV ? -1 : 1) }); + } else if ([STATUS.FINISHED, STATUS.SKIPPED].includes(status)) { + setOnboarding({ run: false }); + addWalkthroughCookie(); + } + }; + + const startWalkThrough = () => { + setOnboarding({ ...onboarding, run: true }); + setShowWalkthrough(false); + }; + + const addWalkthroughCookie = () => { + setShowWalkthrough(false); + const date = new Date(); + date.setTime(date.getTime() + 24 * 60 * 60 * 1000 * 365); + document.cookie = `walkthroughCompleted=true; expires=${date.toUTCString()};`; + }; + + const script = useCases[location.pathname as keyof typeof useCases] || useCases.general; + + return ( + <div> + {showWalkthrough && ( + <div className="alert absolute bottom-5 left-5 w-fit cursor-pointer z-50 bg-entity-400"> + <span className="btn btn-ghost" onClick={startWalkThrough}> + Start a Tour + </span> + <button className="btn btn-circle btn-sm btn-ghost" onClick={() => addWalkthroughCookie()}> + <CloseIcon fontSize="small" /> + </button> + </div> + )} + <Joyride + run={onboarding.run} + continuous={true} + stepIndex={onboarding.stepIndex} + steps={script} + showProgress={true} + showSkipButton={true} + hideCloseButton={true} + callback={handleJoyrideCallback} + styles={{ + options: { + primaryColor: '#FF7D00', + }, + tooltip: { + borderRadius: 0, + }, + buttonNext: { + borderRadius: 0, + }, + }} + /> + </div> + ); +} diff --git a/apps/web/src/components/onboarding/use-cases/fraud.tsx b/apps/web/src/components/onboarding/use-cases/fraud.tsx new file mode 100644 index 0000000000000000000000000000000000000000..048c55455cbea861ded99c1a41432e5230dc3d61 --- /dev/null +++ b/apps/web/src/components/onboarding/use-cases/fraud.tsx @@ -0,0 +1,11 @@ +export const fraudScript = [ + // TODO: make script for fraud detection + + { + target: '.schema-panel', + title: 'Schema Panel', + content: 'The Schema Panel displays the structure of your database. You can drag pills from here to the query builder', + placement: 'right', + disableBeacon: true, + }, +]; diff --git a/apps/web/src/components/onboarding/use-cases/general.tsx b/apps/web/src/components/onboarding/use-cases/general.tsx new file mode 100644 index 0000000000000000000000000000000000000000..2f8f61fe65c805c6030cc7ecb064cf429523ca00 --- /dev/null +++ b/apps/web/src/components/onboarding/use-cases/general.tsx @@ -0,0 +1,55 @@ +export const generalScript = [ + { + target: '.schema-panel', + title: 'Schema Panel', + content: + 'The schema panel displays the structure of your database. The orange pills are nodes in your database, and the blue pills are edges. You can drag pills from here to the query builder to construct queries.', + placement: 'right', + disableBeacon: true, + }, + { + target: '.schema-settings', + title: 'Schema Settings', + content: 'Access schema settings to customize the style, structure and layout of the schema panel.', + placement: 'right', + disableBeacon: true, + }, + { + target: '.query-panel', + title: 'Query Builder', + content: + 'Use the query builder to create queries for your graph database. You can drag pills from the schema panel into here, and connect pills based on your needs.', + placement: 'top', + disableBeacon: true, + }, + { + target: '.query-settings', + title: 'Query Builder Settings', + content: + 'Adjust query builder settings to fine-tune your query-building experience. You can also incorporate machine-learning options into your query.', + placement: 'left', + disableBeacon: true, + }, + { + target: '.vis-panel', + title: 'Visualization Panel', + content: "The visualization panel is where you'll see your data visualizations, with the data from the executed query.", + placement: 'left', + disableBeacon: true, + }, + { + target: '.searchbar', + title: 'Searching', + content: + 'Utilize the search bar to search through GraphPolaris efficiently. You can use keywords like @schema, @data or @querybuilder to refine search scope.', + placement: 'bottom', + disableBeacon: true, + }, + { + target: '.menu-walkthrough', + title: 'Menu', + content: 'In the menu you can manage databases, switch between them, and perform other actions.', + placement: 'bottom', + disableBeacon: true, + }, +]; diff --git a/apps/web/src/components/onboarding/use-cases/index.tsx b/apps/web/src/components/onboarding/use-cases/index.tsx new file mode 100644 index 0000000000000000000000000000000000000000..1805d31db015192c318b79c8017053750a2beae2 --- /dev/null +++ b/apps/web/src/components/onboarding/use-cases/index.tsx @@ -0,0 +1,7 @@ +import { generalScript } from './general'; +import { fraudScript } from './fraud'; + +export const useCases = { + general: generalScript, + fraud: fraudScript, +}; diff --git a/libs/shared/lib/querybuilder/panel/querybuilder.tsx b/libs/shared/lib/querybuilder/panel/querybuilder.tsx index 215be6b727045db682e20878554f73cf790ab99a..4063edd4f1665b1707ef1e85d73e61d2f698c04d 100644 --- a/libs/shared/lib/querybuilder/panel/querybuilder.tsx +++ b/libs/shared/lib/querybuilder/panel/querybuilder.tsx @@ -485,7 +485,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { > <Background gap={10} size={0.7} /> - <Controls showZoom={false} showInteractive={false} className={styles.controls}> + <Controls showZoom={false} showInteractive={false} className={`${styles.controls} query-settings`}> <ControlButton className={styles.buttons} title={'Remove all elements'} onClick={() => clearAllNodes()}> <DeleteIcon /> </ControlButton> @@ -559,7 +559,7 @@ export const QueryBuilderInner = (props: QueryBuilderProps) => { export const QueryBuilder = (props: QueryBuilderProps) => { return ( - <div className="flex w-full h-full"> + <div className="query-panel flex w-full h-full"> <ReactFlowProvider> <QueryBuilderInner {...props} /> </ReactFlowProvider> diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx index d4c678d2cfc965ed03b22d86efeece706ff39244..91743e06a46b05d7d18df6908eac075d5a1b7df7 100644 --- a/libs/shared/lib/schema/panel/schema.tsx +++ b/libs/shared/lib/schema/panel/schema.tsx @@ -153,9 +153,14 @@ export const Schema = (props: Props) => { }, [settingsIconRef, dialogRef, toggleSchemaSettings]); return ( - <div className="w-full h-full"> + <div className="schema-panel w-full h-full"> <SchemaDialog open={toggleSchemaSettings} onClose={() => setToggleSchemaSettings(false)} ref={dialogRef} /> - <h1 className="h-[1rem]">Schema</h1> + <div className="flex justify-between h-[1rem]"> + <h1>Schema</h1> + <p className="text-slate-400" title={JSON.stringify(session)}> + Connected to: {session.currentDatabase} + </p> + </div> {nodes.length === 0 && <p>No Elements</p>} <ReactFlowProvider> <div className="h-[calc(100%-.8rem)] w-full"> @@ -174,7 +179,7 @@ export const Schema = (props: Props) => { onInit={onInit} proOptions={{ hideAttribution: true }} > - <Controls showInteractive={false} showZoom={false} showFitView={true} className={styles.controls}> + <Controls showInteractive={false} showZoom={false} showFitView={true} className={`${styles.controls} schema-settings`}> {/* <ControlButton className={styles.exportButton} title={'Export graph schema'} diff --git a/libs/shared/package.json b/libs/shared/package.json index f654e515f26e5652a13126c8e6c4f238acf217dd..bb16b0fab93f6d0af3a5df53fab2c77845cb4c02 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -56,6 +56,7 @@ "react-cookie": "^4.1.1", "react-draggable": "^4.4.5", "react-grid-layout": "^1.3.4", + "react-joyride": "^2.6.1", "react-json-view": "^1.21.3", "react-palm": "^3.3.8", "react-router-dom": "^6.8.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3449e4b30f71f5a687c787f1a1875b3e05e307d7..edb6c7e1fa0ff4fcb3010c59df7a6dfbbd93146a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,6 +99,9 @@ importers: react-grid-layout: specifier: ^1.3.4 version: 1.3.4(react-dom@18.2.0)(react@18.2.0) + react-joyride: + specifier: ^2.6.1 + version: 2.6.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) react-redux: specifier: ^8.0.5 version: 8.0.5(@types/react-dom@18.0.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0)(redux@4.2.1) @@ -319,6 +322,9 @@ importers: react-grid-layout: specifier: ^1.3.4 version: 1.3.4(react-dom@18.2.0)(react@18.2.0) + react-joyride: + specifier: ^2.6.1 + version: 2.6.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) react-json-view: specifier: ^1.21.3 version: 1.21.3(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) @@ -3546,6 +3552,27 @@ packages: deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package dev: false + /@gilbarbara/deep-equal@0.1.2: + resolution: {integrity: sha512-jk+qzItoEb0D0xSSmrKDDzf9sheQj/BAPxlgNxgmOaA3mxpUa6ndJLYGZKsJnIVEQSD8zcTbyILz7I0HcnBCRA==} + dev: false + + /@gilbarbara/deep-equal@0.2.0: + resolution: {integrity: sha512-dkjEAjjsoPUthQHYENjmgd453IBWLNGqFPolcmbbyKrHrGWj3AayQz7CYGN45OljDOTaFSmyb0sWgDtzpaxWjw==} + dev: false + + /@gilbarbara/helpers@0.8.7: + resolution: {integrity: sha512-DL3btZpWnS3ZMkGdQ9sVQgVj/WlabUFbRoP6sg2iOjEFImq+QDqFgEDZn4Uf8LF3thGuNgj9EtsWlNCbvJYTqg==} + dependencies: + '@gilbarbara/types': 0.2.2 + is-lite: 0.9.3 + dev: false + + /@gilbarbara/types@0.2.2: + resolution: {integrity: sha512-QuQDBRRcm1Q8AbSac2W1YElurOhprj3Iko/o+P1fJxUWS4rOGKMVli98OXS7uo4z+cKAif6a+L9bcZFSyauQpQ==} + dependencies: + type-fest: 4.5.0 + dev: false + /@humanwhocodes/config-array@0.5.0: resolution: {integrity: sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==} engines: {node: '>=10.10.0'} @@ -11327,6 +11354,10 @@ packages: - react-dom dev: false + /deep-diff@1.0.2: + resolution: {integrity: sha512-aWS3UIVH+NPGCD1kki+DCU9Dua032iSsO43LqQpcs4R3+dVv7tX0qBGjiVHJHjplsoUM2XRO/KB92glqc68awg==} + dev: false + /deep-eql@4.1.3: resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} engines: {node: '>=6'} @@ -11379,7 +11410,6 @@ packages: /deepmerge@4.3.1: resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} engines: {node: '>=0.10.0'} - dev: true /default-browser-id@3.0.0: resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} @@ -13962,6 +13992,14 @@ packages: engines: {node: '>=8'} dev: true + /is-lite@0.8.2: + resolution: {integrity: sha512-JZfH47qTsslwaAsqbMI3Q6HNNjUuq6Cmzzww50TdP5Esb6e1y2sK2UAaZZuzfAzpoI2AkxoPQapZdlDuP6Vlsw==} + dev: false + + /is-lite@0.9.3: + resolution: {integrity: sha512-lbyynwsRRUMh1fHEinXkde/thdjj8OpW/okyGAVgmW4r/FkCEP966oSEg0B8ON5+mm73MJjFXB4ZViuaAldw4g==} + dev: false + /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} @@ -16158,6 +16196,11 @@ packages: splaytree: 3.1.2 dev: false + /popper.js@1.16.1: + resolution: {integrity: sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==} + deprecated: You can find the new Popper v2 at @popperjs/core, this package is dedicated to the legacy v1 + dev: false + /postcss-import@14.1.0(postcss@8.4.21): resolution: {integrity: sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw==} engines: {node: '>=10.0.0'} @@ -16216,7 +16259,7 @@ packages: dependencies: lilconfig: 2.1.0 postcss: 8.4.21 - ts-node: 10.9.1(@types/node@18.13.0)(typescript@4.9.5) + ts-node: 10.9.1(@types/node@17.0.12)(typescript@4.9.5) yaml: 1.10.2 dev: true @@ -16874,6 +16917,23 @@ packages: react: 18.2.0 dev: true + /react-floater@0.7.6(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-tt/15k/HpaShbtvWCwsQYLR+ebfUuYbl+oAUJ3DcEDkgYKeUcSkDey2PdAIERdVwzdFZANz47HbwoET2/Rduxg==} + peerDependencies: + react: 15 - 18 + react-dom: 15 - 18 + dependencies: + deepmerge: 4.3.1 + exenv: 1.2.2 + is-lite: 0.8.2 + popper.js: 1.16.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-proptype-conditional-require: 1.0.4 + tree-changes: 0.9.3 + dev: false + /react-grid-layout@1.3.4(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-sB3rNhorW77HUdOjB4JkelZTdJGQKuXLl3gNg+BI8gJkTScspL1myfZzW/EM0dLEn+1eH+xW+wNqk0oIM9o7cw==} peerDependencies: @@ -16889,6 +16949,16 @@ packages: react-resizable: 3.0.4(react-dom@18.2.0)(react@18.2.0) dev: false + /react-innertext@1.1.5(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-PWAqdqhxhHIv80dT9znP2KvS+hfkbRovFp4zFYHFFlOoQLRiawIic81gKb3U1wEyJZgMwgs3JoLtwryASRWP3Q==} + peerDependencies: + '@types/react': '>=0.0.0 <=99' + react: '>=0.0.0 <=99' + dependencies: + '@types/react': 18.0.28 + react: 18.2.0 + dev: false + /react-inspector@6.0.1(react@18.2.0): resolution: {integrity: sha512-cxKSeFTf7jpSSVddm66sKdolG90qURAX3g1roTeaN6x0YEbtWc8JpmFN9+yIqLNH2uEkYerWLtJZIXRIFuBKrg==} peerDependencies: @@ -16930,6 +17000,29 @@ packages: /react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + /react-joyride@2.6.1(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-JgGxWcg3fhQx3DrSTfC7nBmexH1N1UTy8hKi+U5QAObo+ZqACi3/X62RWbauZDxloshCNX2c6Zpui14+6NYeow==} + peerDependencies: + react: 15 - 18 + react-dom: 15 - 18 + dependencies: + '@gilbarbara/deep-equal': 0.2.0 + '@gilbarbara/helpers': 0.8.7 + deep-diff: 1.0.2 + deepmerge: 4.3.1 + is-lite: 0.9.3 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-floater: 0.7.6(react-dom@18.2.0)(react@18.2.0) + react-innertext: 1.1.5(@types/react@18.0.28)(react@18.2.0) + react-is: 16.13.1 + scroll: 3.0.1 + scrollparent: 2.1.0 + tree-changes: 0.10.0 + transitivePeerDependencies: + - '@types/react' + dev: false + /react-json-pretty@2.2.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-3UMzlAXkJ4R8S4vmkRKtvJHTewG4/rn1Q18n0zqdu/ipZbUPLVZD+QwC7uVcD/IAY3s8iNVHlgR2dMzIUS0n1A==} peerDependencies: @@ -17072,6 +17165,10 @@ packages: react-test-renderer: 18.2.0(react@18.2.0) dev: false + /react-proptype-conditional-require@1.0.4: + resolution: {integrity: sha512-nopsRn7KnGgazBe2c3H2+Kf+Csp6PGDRLiBkYEDMKY8o/EIgft/WnIm/OnAKTawZiLnJXHAqhpFBddvs6NiXlw==} + dev: false + /react-reconciler@0.12.0(react@18.2.0): resolution: {integrity: sha512-BBaE+asD1HdzS35GLhvOEUGFwFKBNN/Jj9b+VlCt9JjF+jDnmIij4SbulNpqccYxPE/Eeup3/ciouo9YmhSgbg==} engines: {node: '>=0.10.0'} @@ -18009,6 +18106,14 @@ packages: ajv: 6.12.6 ajv-keywords: 3.5.2(ajv@6.12.6) + /scroll@3.0.1: + resolution: {integrity: sha512-pz7y517OVls1maEzlirKO5nPYle9AXsFzTMNJrRGmT951mzpIBy7sNHOg5o/0MQd/NqliCiWnAi0kZneMPFLcg==} + dev: false + + /scrollparent@2.1.0: + resolution: {integrity: sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==} + dev: false + /scss@0.2.4: resolution: {integrity: sha512-4u8V87F+Q/upVhUmhPnB4C1R11xojkRkWjExL2v0CX2EXTg18VrKd+9JWoeyCp2VEMdSpJsyAvVU+rVjogh51A==} engines: {node: '>= 0.2.0'} @@ -18993,6 +19098,20 @@ packages: punycode: 2.3.0 dev: true + /tree-changes@0.10.0: + resolution: {integrity: sha512-Hu1ElozbPrc8/zvDfazlnbOQxepXVpy0IRrNrZkUB1aDyyJ+yColKKzGmvO8KE5AH8xvW6z9aChFQfDJGlDdKA==} + dependencies: + '@gilbarbara/deep-equal': 0.1.2 + is-lite: 0.9.3 + dev: false + + /tree-changes@0.9.3: + resolution: {integrity: sha512-vvvS+O6kEeGRzMglTKbc19ltLWNtmNt1cpBoSYLj/iEcPVvpJasemKOlxBrmZaCtDJoF+4bwv3m01UKYi8mukQ==} + dependencies: + '@gilbarbara/deep-equal': 0.1.2 + is-lite: 0.8.2 + dev: false + /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -19254,6 +19373,11 @@ packages: engines: {node: '>=12.20'} dev: true + /type-fest@4.5.0: + resolution: {integrity: sha512-diLQivFzddJl4ylL3jxSkEc39Tpw7o1QeEHIPxVwryDK2lpB7Nqhzhuo6v5/Ls08Z0yPSAhsyAWlv1/H0ciNmw==} + engines: {node: '>=16'} + dev: false + /type-is@1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -20243,8 +20367,8 @@ packages: tinybench: 2.4.0 tinypool: 0.4.0 tinyspy: 1.1.1 - vite: 4.2.1(@types/node@17.0.12)(sass@1.64.2) - vite-node: 0.29.4(@types/node@17.0.12)(sass@1.59.3) + vite: 4.2.1(@types/node@17.0.12)(sass@1.59.3) + vite-node: 0.29.4(@types/node@17.0.12)(sass@1.64.2) why-is-node-running: 2.2.2 transitivePeerDependencies: - less