diff --git a/app/components/CopyPathButton.tsx b/app/components/CopyPathButton.tsx new file mode 100644 index 000000000..57950c5b7 --- /dev/null +++ b/app/components/CopyPathButton.tsx @@ -0,0 +1,74 @@ +import { ClipboardIcon, CogIcon } from "@heroicons/react/outline"; +import { useCallback, useState } from "react"; +import { CopyText } from "./CopyText"; +import { Body } from "./Primitives/Body"; +import { Dialog, DialogTrigger, DialogContent } from "./UI/Dialog"; +import { PathComponent } from '@jsonhero/path' +import { getPathValue, languages } from "~/utilities/programmingLanguages"; +import classnames from "~/utilities/classnames"; +import { CopyPathPreferences } from "./CopyPathPreferences"; +import { usePreferences } from "./PreferencesProvider"; + +export type CopyPathButtonProps = { + heroPathComponents: PathComponent[]; + className?: string; +}; + +export function CopyPathButton({ heroPathComponents, className }: CopyPathButtonProps) { + const [copied, setCopied] = useState(false); + const [settingsOpen, setSettingsOpen] = useState(false) + const [preferences] = usePreferences() + const [variableName, setVariableName] = useState(""); + const [useOptChaining, setUseOptionalChaining] = useState(false); + + const onCopied = useCallback(() => { + setCopied(true); + const timeout = setTimeout(() => { + setCopied(false); + }, 1500); + }, [heroPathComponents]); + return ( + <> + + {copied ? ( + Copied! + ) : ( +
+ + Copy Path +
+ )} +
+ + setSettingsOpen(true)} + > +
+
+ +   +
+
+
+ setSettingsOpen(false)} + className={classnames( + "fixed z-50", + "w-[95vw] max-w-2xl rounded-lg", + "top-0 left-[50%] -translate-x-[50%]", + "mt-[60px]", + "bg-white border-[1px] border-slate-500 dark:border-slate-700 dark:bg-slate-800" + )} + > + + +
+ + ); +} diff --git a/app/components/CopyPathPreferences.tsx b/app/components/CopyPathPreferences.tsx new file mode 100644 index 000000000..fb303fcfe --- /dev/null +++ b/app/components/CopyPathPreferences.tsx @@ -0,0 +1,76 @@ +import { languages, canUseOptChaining } from "~/utilities/programmingLanguages"; +import { usePreferences } from "./PreferencesProvider"; +import { useState } from "react"; + +export type CopyPathPreferencesProps = { + variableName: string; + onVariableNameChange: (value: string) => void; + useOptChaining: boolean; + onUseOptionalChainingChange: (value: boolean) => void; +}; + +export function CopyPathPreferences({ + variableName, + onVariableNameChange, + useOptChaining, + onUseOptionalChainingChange, +}: CopyPathPreferencesProps) { + const [preferences, setPreferences] = usePreferences(); + + return ( +
+

+ Copy Path Preferences +

+
+ + + + onVariableNameChange(e.target.value)} + /> + {canUseOptChaining[preferences.language || languages.javascript] && ( + <> + + onUseOptionalChainingChange(e.target.checked)} + /> + + )} +
+
+ ); +} diff --git a/app/components/InfoHeader.tsx b/app/components/InfoHeader.tsx index d7bbebb50..0130b42eb 100644 --- a/app/components/InfoHeader.tsx +++ b/app/components/InfoHeader.tsx @@ -10,6 +10,7 @@ import { concatenated, getHierarchicalTypes } from "~/utilities/dataType"; import { formatRawValue } from "~/utilities/formatter"; import { isNullable } from "~/utilities/nullable"; import { CopyTextButton } from "./CopyTextButton"; +import { CopyPathButton } from "./CopyPathButton"; import { Body } from "./Primitives/Body"; import { LargeMono } from "./Primitives/LargeMono"; import { Title } from "./Primitives/Title"; @@ -45,7 +46,8 @@ export function InfoHeader({ relatedPaths }: InfoHeaderProps) { return isNullable(relatedPaths, json); }, [relatedPaths, json]); - const [hovering, setHovering] = useState(false); + const [hoveringKey, setHoveringKey] = useState(false); + const [hoveringValue, setHoveringValue] = useState(false); console.warn(selectedInfo); const newPath = formattedSelectedInfo.replace(/^#/, "$").replace(/\//g, "."); @@ -56,10 +58,24 @@ export function InfoHeader({ relatedPaths }: InfoHeaderProps) { return (
-
- + <div + className="relative flex items-center" + onMouseEnter={() => setHoveringKey(true)} + onMouseLeave={() => setHoveringKey(false)} + > + <Title className={`flex-1 mr-2 overflow-hidden overflow-ellipsis break-words text-slate-700 transition dark:text-slate-200 ${ + hoveringKey ? "bg-slate-100 dark:bg-slate-700" : "bg-transparent" + }`}> { selectedName ?? "nothing" } +
+ +
setHovering(true)} - onMouseLeave={() => setHovering(false)} + onMouseEnter={() => setHoveringValue(true)} + onMouseLeave={() => setHoveringValue(false)} > {isSelectedLeafNode && ( {selectedNode.name === "$ref" && checkPathExists(json, newPath) ? ( @@ -90,7 +106,7 @@ export function InfoHeader({ relatedPaths }: InfoHeaderProps) { )}
; + type PreferencesContextType = [ - Preferences | undefined, - Dispatch> + PartialPreferences, + Dispatch> ]; const PreferencesContext = createContext(undefined); @@ -30,7 +35,7 @@ export function PreferencesProvider({ }: { children: ReactNode; }) { - const [preferences, setPreferences] = useState(); + const [preferences, setPreferences] = useState(PreferencesDefaults); useEffect(() => { const preferences = loadPreferences(); @@ -39,7 +44,7 @@ export function PreferencesProvider({ useEffect(() => { if (preferences === undefined) return; - savePreferences(preferences); + savePreferences(preferences as Preferences); }, [preferences]); return ( diff --git a/app/utilities/programmingLanguages.ts b/app/utilities/programmingLanguages.ts new file mode 100644 index 000000000..d9e0de337 --- /dev/null +++ b/app/utilities/programmingLanguages.ts @@ -0,0 +1,25 @@ +import { PathComponent } from "@jsonhero/path" + +export enum languages { + javascript = "javascript", + python = "python" +} + +export const canUseOptChaining = { + [languages.javascript]: true, + [languages.python]: false, +} + +export const defaultLangauge = languages.javascript + +export const getPathValue = (language: languages, variableName: string, useOptChaining: boolean, paths: PathComponent[]): string => { + const path_base = variableName + switch(language) { + case languages.python: + return `${path_base}${paths.slice(1).map(p => p.isArray ? `[${p.toString()}]` : `["${p.toString()}"]`).join("")}` + case languages.javascript: + default: + return `${path_base}${paths.slice(1).map(p => p.isArray ? `${useOptChaining ? "?[" : "["}${p.toString()}]` : `${useOptChaining ? "?." : "."}${p.toString()}`).join("")}` + } +} +