Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c57d313b6d | ||
|
|
c504b0c8c6 | ||
|
|
dc3bb5d2ab | ||
|
|
4c810901c3 | ||
|
|
25e1b20d9c | ||
|
|
52b7d1962d | ||
|
|
d4c03f38d2 | ||
|
|
e37607b377 | ||
|
|
abd804ca4e | ||
|
|
ac31a5a1cf | ||
|
|
a53506564d | ||
|
|
2cb9bcd991 | ||
|
|
d7225b201f | ||
|
|
0358dbd5e2 | ||
|
|
bc5e5561c7 | ||
|
|
6df767080a | ||
|
|
2fb902ee01 | ||
|
|
fa7010da0b | ||
|
|
1b42269739 | ||
|
|
c9d309c16d | ||
|
|
3acf393009 | ||
|
|
7508a96d3f | ||
|
|
49e0c57a33 | ||
|
|
d898d820bb |
|
Before Width: | Height: | Size: 620 KiB After Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 279 B After Width: | Height: | Size: 279 B |
|
Before Width: | Height: | Size: 336 B After Width: | Height: | Size: 336 B |
|
Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 486 B |
@@ -6,6 +6,7 @@ import {
|
||||
ViewUpdate,
|
||||
} from "@uiw/react-codemirror";
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useJsonDoc } from "~/hooks/useJsonDoc";
|
||||
import { getEditorSetup } from "~/utilities/codeMirrorSetup";
|
||||
import { darkTheme, lightTheme } from "~/utilities/codeMirrorTheme";
|
||||
import { useTheme } from "./ThemeProvider";
|
||||
@@ -92,10 +93,14 @@ export function CodeEditor(opts: CodeEditorProps) {
|
||||
}
|
||||
}, [selection, view, setSelectionRef.current]);
|
||||
|
||||
const { minimal } = useJsonDoc();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
className="h-jsonViewerHeight overflow-y-auto no-scrollbar"
|
||||
className={`${
|
||||
minimal ? "h-jsonViewerHeightMinimal" : "h-jsonViewerHeight"
|
||||
} overflow-y-auto no-scrollbar`}
|
||||
ref={editor}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -3,6 +3,7 @@ import { colorForItemAtPath } from "~/utilities/colors";
|
||||
import { IconComponent } from "~/useColumnView";
|
||||
import { useJson } from "../hooks/useJson";
|
||||
import { memo, useMemo } from "react";
|
||||
import { useJsonDoc } from "~/hooks/useJsonDoc";
|
||||
|
||||
export type ColumnProps = {
|
||||
id: string;
|
||||
@@ -15,6 +16,7 @@ export type ColumnProps = {
|
||||
function ColumnElement(column: ColumnProps) {
|
||||
const { id, title, children } = column;
|
||||
const [json] = useJson();
|
||||
const { minimal } = useJsonDoc();
|
||||
const iconColor = useMemo(() => colorForItemAtPath(id, json), [id, json]);
|
||||
|
||||
return (
|
||||
@@ -25,9 +27,13 @@ function ColumnElement(column: ColumnProps) {
|
||||
>
|
||||
<div className="flex items-center text-slate-800 bg-slate-50 mb-[3px] p-2 pb-0 transition dark:bg-slate-900 dark:text-slate-300">
|
||||
{column.icon && <column.icon className="h-6 w-6 mr-1" />}
|
||||
<Title className="">{title}</Title>
|
||||
<Title className="text-ellipsis overflow-hidden">{title}</Title>
|
||||
</div>
|
||||
<div className="overflow-y-auto h-viewerHeight no-scrollbar">
|
||||
<div
|
||||
className={`overflow-y-auto ${
|
||||
minimal ? "h-viewerHeightMinimal" : "h-viewerHeight"
|
||||
} no-scrollbar`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
import { useJsonDoc } from "~/hooks/useJsonDoc";
|
||||
import { ArrowKeysIcon } from "./Icons/ArrowKeysIcon";
|
||||
import { CopyShortcutIcon } from "./Icons/CopyShortcutIcon";
|
||||
import { EscapeKeyIcon } from "./Icons/EscapeKeyIcon";
|
||||
import { SquareBracketsIcon } from "./Icons/SquareBracketsIcon";
|
||||
import { Body } from "./Primitives/Body";
|
||||
import { ThemeModeToggler } from "./ThemeModeToggle";
|
||||
import { GithubStarSmall } from "./UI/GithubStarSmall";
|
||||
|
||||
export function Footer() {
|
||||
const { minimal } = useJsonDoc();
|
||||
|
||||
return (
|
||||
<footer className="flex items-center justify-between w-screen h-[30px] bg-slate-200 dark:bg-slate-800 border-t-[1px] border-slate-400 transition dark:border-slate-600">
|
||||
<ol className="flex pl-3">
|
||||
@@ -34,7 +38,16 @@ export function Footer() {
|
||||
</Body>
|
||||
</li>
|
||||
</ol>
|
||||
<ThemeModeToggler />
|
||||
<ol className="flex items-center">
|
||||
{minimal && (
|
||||
<li>
|
||||
<GithubStarSmall />
|
||||
</li>
|
||||
)}
|
||||
<li>
|
||||
<ThemeModeToggler />
|
||||
</li>
|
||||
</ol>
|
||||
</footer>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
|
||||
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
|
||||
import { HomeSection } from "./HomeSection";
|
||||
|
||||
import shareVideo from "~/assets/home/JsonHeroShare.mp4";
|
||||
|
||||
export function HomeCollaborateSection() {
|
||||
return (
|
||||
<HomeSection
|
||||
@@ -20,7 +22,7 @@ export function HomeCollaborateSection() {
|
||||
</SmallSubtitle>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<AutoplayVideo src="/home/JsonHeroShare.mp4" />
|
||||
<AutoplayVideo src={shareVideo} />
|
||||
</div>
|
||||
</HomeSection>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
|
||||
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
|
||||
import { HomeSection } from "./HomeSection";
|
||||
|
||||
import edgeCasesVideo from "~/assets/home/UncoverEdgeCases.mp4";
|
||||
|
||||
export function HomeEdgeCasesSection() {
|
||||
return (
|
||||
<HomeSection
|
||||
@@ -21,7 +23,7 @@ export function HomeEdgeCasesSection() {
|
||||
</SmallSubtitle>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<AutoplayVideo src="/home/UncoverEdgeCases.mp4" />
|
||||
<AutoplayVideo src={edgeCasesVideo} />
|
||||
</div>
|
||||
</HomeSection>
|
||||
);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { Link } from "remix";
|
||||
import { DiscordIcon } from "../Icons/DiscordIcon";
|
||||
import { EmailIcon } from "../Icons/EmailIcon";
|
||||
import { GithubIcon } from "../Icons/GithubIcon";
|
||||
@@ -42,6 +43,10 @@ export function HomeFooter({ maxWidth = "1150px" }: HomeFooterProps) {
|
||||
<TwitterIcon />
|
||||
</a>
|
||||
</li>
|
||||
|
||||
<li className="ml-2 hover:cursor-pointer text-indigo-700">
|
||||
<Link to="/privacy">Privacy Policy</Link>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -11,9 +11,13 @@ import {
|
||||
PopoverTrigger,
|
||||
} from "../UI/Popover";
|
||||
|
||||
export function HomeHeader() {
|
||||
export function HomeHeader({ fixed }: { fixed?: boolean }) {
|
||||
return (
|
||||
<header className="fixed z-20 flex items-center justify-between w-screen h-[82px] px-4 bg-indigo-700">
|
||||
<header
|
||||
className={`${
|
||||
fixed ? "fixed" : ""
|
||||
} z-20 flex items-center justify-between w-screen h-[82px] px-4 bg-indigo-700`}
|
||||
>
|
||||
<div className="flex w-36 sm:w-44 mr-3">
|
||||
<Logo />
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,8 @@ import { AutoplayVideo } from "../AutoplayVideo";
|
||||
import { NewFile } from "../NewFile";
|
||||
import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
|
||||
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
|
||||
import { HomeSection } from "./HomeSection";
|
||||
|
||||
import heroVideo from "~/assets/home/JsonHero2.mp4";
|
||||
|
||||
const jsonHeroTitle = "JSON sucks.";
|
||||
const jsonHeroSlogan = "But we're making it better.";
|
||||
@@ -14,7 +15,7 @@ export function HomeHeroSection() {
|
||||
>
|
||||
<div className="self-center md:w-1/2 md:pr-10 flex justify-end">
|
||||
<div className=" max-w-3xl">
|
||||
<AutoplayVideo src="/home/JsonHero2.mp4" />
|
||||
<AutoplayVideo src={heroVideo} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="self-center flex align-center md:w-1/2 px-6 pb-8 mt-8 lg:mt-0">
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ExtraLargeTitle } from "../Primitives/ExtraLargeTitle";
|
||||
import { SmallSubtitle } from "../Primitives/SmallSubtitle";
|
||||
import { HomeSection } from "./HomeSection";
|
||||
|
||||
import searchVideo from "~/assets/home/JsonHeroSearch.mp4";
|
||||
|
||||
export function HomeSearchSection() {
|
||||
return (
|
||||
<HomeSection containerClassName="py-10 px-6 bg-black md:py-36 lg:py-20">
|
||||
@@ -17,7 +19,7 @@ export function HomeSearchSection() {
|
||||
</SmallSubtitle>
|
||||
</div>
|
||||
<div className="w-full md:w-1/2">
|
||||
<AutoplayVideo src="/home/JsonHeroSearch.mp4" />
|
||||
<AutoplayVideo src={searchVideo} />
|
||||
</div>
|
||||
</HomeSection>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export function GithubIconSimple(props: React.SVGProps<SVGSVGElement>) {
|
||||
export type IconProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function GithubIconSimple({ className }: IconProps) {
|
||||
return (
|
||||
<svg
|
||||
className={props.className}
|
||||
className={className}
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
fill="currentColor"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_571_3822)">
|
||||
@@ -13,7 +17,7 @@ export function GithubIconSimple(props: React.SVGProps<SVGSVGElement>) {
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M12 0C5.37017 0 0 5.50708 0 12.306C0 17.745 3.44015 22.3532 8.20626 23.9849C8.80295 24.0982 9.02394 23.7205 9.02394 23.3881C9.02394 23.0935 9.01657 22.3229 9.00921 21.2956C5.67219 22.0359 4.96501 19.6487 4.96501 19.6487C4.41989 18.2285 3.63168 17.8508 3.63168 17.8508C2.54144 17.0878 3.71271 17.1029 3.71271 17.1029C4.91344 17.1936 5.55433 18.372 5.55433 18.372C6.62247 20.2531 8.36096 19.7092 9.04604 19.3919C9.15654 18.5987 9.46593 18.0548 9.80479 17.745C7.13812 17.4353 4.33886 16.3777 4.33886 11.6638C4.33886 10.3192 4.80295 9.2238 5.57643 8.36261C5.4512 8.05288 5.03867 6.79887 5.69429 5.1067C5.69429 5.1067 6.7035 4.77432 8.99447 6.36827C9.95212 6.09632 10.9761 5.96034 12 5.95279C13.0166 5.95279 14.0479 6.09632 15.0055 6.36827C17.2965 4.77432 18.3057 5.1067 18.3057 5.1067C18.9613 6.79887 18.5488 8.05288 18.4236 8.36261C19.1897 9.2238 19.6538 10.3192 19.6538 11.6638C19.6538 16.3928 16.8471 17.4278 14.1731 17.7375C14.6004 18.1152 14.9908 18.8706 14.9908 20.0189C14.9908 21.6657 14.9761 22.9877 14.9761 23.3957C14.9761 23.728 15.1897 24.1058 15.8011 23.9849C20.5672 22.3532 24 17.745 24 12.3135C24 5.50708 18.6298 0 12 0Z"
|
||||
fill="#231E1B"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
|
||||
@@ -6,11 +6,11 @@ import { useJsonColumnViewState } from "~/hooks/useJsonColumnView";
|
||||
import { concatenated, getHierarchicalTypes } from "~/utilities/dataType";
|
||||
import { formatRawValue } from "~/utilities/formatter";
|
||||
import { isNullable } from "~/utilities/nullable";
|
||||
import { CopyTextButton } from "./CopyTextButton";
|
||||
import { Body } from "./Primitives/Body";
|
||||
import { LargeMono } from "./Primitives/LargeMono";
|
||||
import { Title } from "./Primitives/Title";
|
||||
import { ValueIcon, ValueIconSize } from "./ValueIcon";
|
||||
import { CopyTextButton } from "./CopyTextButton";
|
||||
|
||||
export type InfoHeaderProps = {
|
||||
relatedPaths: string[];
|
||||
@@ -41,12 +41,13 @@ export function InfoHeader({ relatedPaths }: InfoHeaderProps) {
|
||||
}, [relatedPaths, json]);
|
||||
|
||||
const [hovering, setHovering] = useState(false);
|
||||
console.warn(selectedInfo);
|
||||
|
||||
return (
|
||||
<div className="mb-4 pb-4">
|
||||
<div className="flex items-center">
|
||||
<Title className="flex-1 mr-2 text-slate-700 transition dark:text-slate-200">
|
||||
{selectedName ?? "nothing"}
|
||||
<Title className="flex-1 mr-2 overflow-hidden overflow-ellipsis break-words text-slate-700 transition dark:text-slate-200">
|
||||
{ selectedName ?? "nothing" }
|
||||
</Title>
|
||||
<div>
|
||||
<ValueIcon
|
||||
|
||||
@@ -5,8 +5,10 @@ import { InfoHeader } from "./InfoHeader";
|
||||
import { ContainerInfo } from "./ContainerInfo";
|
||||
import { useSelectedInfo } from "~/hooks/useSelectedInfo";
|
||||
import { useRelatedPaths } from "~/hooks/useRelatedPaths";
|
||||
import { useJsonDoc } from "~/hooks/useJsonDoc";
|
||||
|
||||
export function InfoPanel() {
|
||||
const { minimal } = useJsonDoc();
|
||||
const selectedInfo = useSelectedInfo();
|
||||
const relatedPaths = useRelatedPaths();
|
||||
|
||||
@@ -16,7 +18,11 @@ export function InfoPanel() {
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="h-inspectorHeight p-4 bg-white border-l-[1px] border-slate-300 overflow-y-auto no-scrollbar transition dark:bg-slate-800 dark:border-slate-600">
|
||||
<div
|
||||
className={`${
|
||||
minimal ? "h-inspectorHeightMinimal" : "h-inspectorHeight"
|
||||
} p-4 bg-white border-l-[1px] border-slate-300 overflow-y-auto no-scrollbar transition dark:bg-slate-800 dark:border-slate-600`}
|
||||
>
|
||||
<InfoHeader relatedPaths={relatedPaths} />
|
||||
|
||||
<div className="mb-4">
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
useJsonColumnViewAPI,
|
||||
useJsonColumnViewState,
|
||||
} from "~/hooks/useJsonColumnView";
|
||||
import { useJsonDoc } from "~/hooks/useJsonDoc";
|
||||
import { JsonTreeViewNode, useJsonTreeViewContext } from "~/hooks/useJsonTree";
|
||||
import { VirtualNode } from "~/hooks/useVirtualTree";
|
||||
import { CopySelectedNodeShortcut } from "./CopySelectedNode";
|
||||
@@ -78,6 +79,8 @@ export function JsonTreeView() {
|
||||
}
|
||||
}, [treeRef.current]);
|
||||
|
||||
const { minimal } = useJsonDoc();
|
||||
|
||||
return (
|
||||
<>
|
||||
<CopySelectedNodeShortcut />
|
||||
@@ -85,7 +88,7 @@ export function JsonTreeView() {
|
||||
className="text-white w-full"
|
||||
ref={parentRef}
|
||||
style={{
|
||||
height: `calc(100vh - 106px)`,
|
||||
height: `calc(100vh - ${minimal ? "66px" : "106px"})`,
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
}}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { useJsonColumnViewAPI } from "~/hooks/useJsonColumnView";
|
||||
import { ColumnViewNode, IconComponent } from "~/useColumnView";
|
||||
import { Body } from "./Primitives/Body";
|
||||
|
||||
import eyeIcon from "~/assets/svgs/EyeIcon.svg";
|
||||
|
||||
export type PathPreviewProps = {
|
||||
nodes: ColumnViewNode[];
|
||||
maxComponents?: number;
|
||||
@@ -77,7 +79,7 @@ export function PathPreview({
|
||||
<div
|
||||
className={`flex select-none pl-7 ${
|
||||
isEnabled
|
||||
? "relative transition hover:bg-slate-200 hover:cursor-pointer dark:hover:bg-slate-600 after:transition after:absolute after:h-3 after:w-3 after:opacity-0 hover:after:opacity-100 after:top-1 after:left-1 after:content-[''] after:bg-[url('/svgs/EyeIcon.svg')] after:bg-no-repeat"
|
||||
? `relative transition hover:bg-slate-200 hover:cursor-pointer dark:hover:bg-slate-600 after:transition after:absolute after:h-3 after:w-3 after:opacity-0 hover:after:opacity-100 after:top-1 after:left-1 after:content-[''] after:bg-[url('${eyeIcon}')] after:bg-no-repeat`
|
||||
: "disabled"
|
||||
}`}
|
||||
onClick={() =>
|
||||
|
||||
@@ -23,7 +23,15 @@ export function PreviewUri(props: PreviewUriProps) {
|
||||
<div>
|
||||
{previewFetcher.type === "done" ? (
|
||||
<>
|
||||
{"error" in previewFetcher.data ? (
|
||||
{typeof previewFetcher.data == "string" ? (
|
||||
<PreviewBox>
|
||||
<Body>
|
||||
<span
|
||||
dangerouslySetInnerHTML={{ __html: previewFetcher.data }}
|
||||
></span>
|
||||
</Body>
|
||||
</PreviewBox>
|
||||
) : "error" in previewFetcher.data ? (
|
||||
<PreviewBox>
|
||||
<Body>{previewFetcher.data.error}</Body>
|
||||
</PreviewBox>
|
||||
|
||||
@@ -6,6 +6,7 @@ import { useJsonDoc } from "~/hooks/useJsonDoc";
|
||||
import { ToolTip } from "./ToolTip";
|
||||
import { Body } from "./Primitives/Body";
|
||||
import { ShortcutIcon } from "./Icons/ShortcutIcon";
|
||||
import { useTheme } from "./ThemeProvider";
|
||||
|
||||
export function SideBar() {
|
||||
const { doc } = useJsonDoc();
|
||||
@@ -81,6 +82,23 @@ function SidebarLink({
|
||||
|
||||
const isActive = location.pathname === to;
|
||||
|
||||
const { minimal } = useJsonDoc();
|
||||
const [theme] = useTheme();
|
||||
|
||||
const queryParams = new URLSearchParams();
|
||||
|
||||
if (typeof minimal === "boolean") {
|
||||
queryParams.set("minimal", String(minimal));
|
||||
|
||||
if (theme) {
|
||||
queryParams.set("theme", theme);
|
||||
}
|
||||
}
|
||||
|
||||
const href = `${to}${
|
||||
queryParams.toString().length > 0 ? `?${queryParams.toString()}` : ""
|
||||
}`;
|
||||
|
||||
if (hotKey) {
|
||||
const navigate = useNavigate();
|
||||
useHotkeys(
|
||||
@@ -88,7 +106,7 @@ function SidebarLink({
|
||||
(e) => {
|
||||
e.preventDefault();
|
||||
if (!isActive && to) {
|
||||
navigate(to);
|
||||
navigate(href);
|
||||
}
|
||||
},
|
||||
[navigate, isActive, to]
|
||||
@@ -100,7 +118,7 @@ function SidebarLink({
|
||||
: "relative w-10 h-10 mb-1 text-slate-700 hover:bg-slate-300 rounded-sm cursor:pointer transition dark:text-white dark:hover:bg-slate-700";
|
||||
|
||||
return !!to ? (
|
||||
<Link to={to} prefetch={isActive ? "none" : "render"}>
|
||||
<Link to={href} prefetch={isActive ? "none" : "render"}>
|
||||
<li className={classes}>{children}</li>
|
||||
</Link>
|
||||
) : (
|
||||
|
||||
@@ -15,8 +15,10 @@ export function ThemeModeToggler() {
|
||||
|
||||
return (
|
||||
<button
|
||||
className={`flex text-xl items-center pr-2 ${
|
||||
theme === "light" ? "text-slate-800" : "text-white"
|
||||
className={`flex text-xl items-center px-2 py-1.5 transition ${
|
||||
theme === "light"
|
||||
? "text-slate-800 hover:bg-slate-300"
|
||||
: "text-white hover:bg-slate-700"
|
||||
}`}
|
||||
onClick={toggleTheme}
|
||||
>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import { formatStarCount } from "~/utilities/formatStarCount";
|
||||
import { GithubIconSimple } from "../Icons/GithubIconSimple";
|
||||
import { Body } from "../Primitives/Body";
|
||||
import { useStarCount } from "../StarCountProvider";
|
||||
|
||||
export type GithubStarSmallProps = {
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export function GithubStarSmall({ className }: GithubStarSmallProps) {
|
||||
const starCount = useStarCount();
|
||||
|
||||
return (
|
||||
<a
|
||||
href="https://github.com/jsonhero-io/jsonhero-web"
|
||||
target="_blank"
|
||||
className="flex p-1 text-slate-700 dark:text-slate-300 hover:bg-slate-300 dark:hover:bg-slate-700 transition hover:cursor-pointer"
|
||||
>
|
||||
<div className="flex items-center gap-1.5 pl-1 rounded-l-sm">
|
||||
<GithubIconSimple className="w-4 h-4 ml-1 text-slate-700 dark:text-white transition"></GithubIconSimple>
|
||||
<Body className="font-semibold text-slate-800 dark:text-slate-100">
|
||||
Star
|
||||
</Body>
|
||||
</div>
|
||||
{starCount && (
|
||||
<div className="pr-2 pl-1">
|
||||
<Body className="font-bold dark:text-slate-100">
|
||||
{formatStarCount(starCount)}
|
||||
</Body>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { JSONDocument } from "~/jsonDoc.server";
|
||||
type JsonDocType = {
|
||||
doc: JSONDocument;
|
||||
path?: string;
|
||||
minimal?: boolean;
|
||||
};
|
||||
|
||||
const JsonDocContext = createContext<JsonDocType | undefined>(undefined);
|
||||
@@ -13,13 +14,15 @@ export function JsonDocProvider({
|
||||
children,
|
||||
doc,
|
||||
path,
|
||||
minimal,
|
||||
}: {
|
||||
children: ReactNode;
|
||||
doc: JSONDocument;
|
||||
path?: string;
|
||||
minimal?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<JsonDocContext.Provider value={{ doc, path }}>
|
||||
<JsonDocContext.Provider value={{ doc, path, minimal }}>
|
||||
{children}
|
||||
</JsonDocContext.Provider>
|
||||
);
|
||||
|
||||
@@ -18,6 +18,8 @@ import {
|
||||
useTheme,
|
||||
} from "~/components/ThemeProvider";
|
||||
|
||||
import openGraphImage from "~/assets/images/opengraph.png";
|
||||
|
||||
export const meta: MetaFunction = ({ location }) => {
|
||||
const description =
|
||||
"JSON Hero makes reading and understand JSON files easy by giving you a clean and beautiful UI packed with extra features.";
|
||||
@@ -25,11 +27,11 @@ export const meta: MetaFunction = ({ location }) => {
|
||||
title: "JSON Viewer - JSON Hero",
|
||||
viewport: "width=device-width,initial-scale=1",
|
||||
description,
|
||||
"og:image": `https://jsonhero.io/images/opengraph.png`,
|
||||
"og:image": `https://jsonhero.io${openGraphImage}`,
|
||||
"og:url": `https://jsonhero.io${location.pathname}`,
|
||||
"og:title": "JSON Hero - A beautiful JSON viewer",
|
||||
"og:description": description,
|
||||
"twitter:image": "https://jsonhero.io/images/opengraph.png",
|
||||
"twitter:image": `https://jsonhero.io${openGraphImage}`,
|
||||
"twitter:card": "summary_large_image",
|
||||
"twitter:creator": "@json_hero",
|
||||
"twitter:site": "@json_hero",
|
||||
@@ -50,20 +52,32 @@ export function links() {
|
||||
export type LoaderData = {
|
||||
theme?: Theme;
|
||||
starCount?: number;
|
||||
themeOverride?: Theme;
|
||||
};
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
const themeSession = await getThemeSession(request);
|
||||
const starCount = await getStarCount();
|
||||
const themeOverride = getThemeFromRequest(request);
|
||||
|
||||
const data: LoaderData = {
|
||||
theme: themeSession.getTheme(),
|
||||
starCount,
|
||||
themeOverride,
|
||||
};
|
||||
|
||||
return data;
|
||||
};
|
||||
|
||||
function getThemeFromRequest(request: Request): Theme | undefined {
|
||||
const url = new URL(request.url);
|
||||
const theme = url.searchParams.get("theme");
|
||||
if (theme) {
|
||||
return theme as Theme;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function App() {
|
||||
const [theme] = useTheme();
|
||||
|
||||
@@ -86,7 +100,7 @@ function App() {
|
||||
}
|
||||
|
||||
export default function AppWithProviders() {
|
||||
const { theme, starCount } = useLoaderData<LoaderData>();
|
||||
const { theme, starCount, themeOverride } = useLoaderData<LoaderData>();
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
@@ -96,7 +110,7 @@ export default function AppWithProviders() {
|
||||
return (
|
||||
<ThemeProvider
|
||||
specifiedTheme={theme}
|
||||
themeOverride={forceDarkMode ? "dark" : undefined}
|
||||
themeOverride={forceDarkMode ? "dark" : themeOverride}
|
||||
>
|
||||
<StarCountProvider starCount={starCount}>
|
||||
<App />
|
||||
|
||||
@@ -1,8 +1,22 @@
|
||||
import { ActionFunction, json } from "remix";
|
||||
import { ActionFunction, json, LoaderFunction } from "remix";
|
||||
import invariant from "tiny-invariant";
|
||||
import { sendEvent } from "~/graphJSON.server";
|
||||
import { createFromRawJson, CreateJsonOptions } from "~/jsonDoc.server";
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
if (request.method === "OPTIONS") {
|
||||
return new Response(null, {
|
||||
status: 204,
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "POST",
|
||||
"Access-Control-Allow-Headers": "Content-Type",
|
||||
"Access-Control-Max-Age": "86400",
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export const action: ActionFunction = async ({ request, context }) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
@@ -42,5 +56,12 @@ export const action: ActionFunction = async ({ request, context }) => {
|
||||
})
|
||||
);
|
||||
|
||||
return json({ id: doc.id, title, location: url.toString() });
|
||||
return json(
|
||||
{ id: doc.id, title, location: url.toString() },
|
||||
{
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
@@ -11,7 +11,7 @@ import { GithubBanner } from "~/components/Home/HomeGithubBanner";
|
||||
export default function Index() {
|
||||
return (
|
||||
<div className="overflow-x-hidden">
|
||||
<HomeHeader />
|
||||
<HomeHeader fixed={true} />
|
||||
<HomeHeroSection />
|
||||
<GithubBanner />
|
||||
<HomeInfoBoxSection />
|
||||
|
||||
@@ -41,6 +41,7 @@ export const loader: LoaderFunction = async ({ params, request }) => {
|
||||
}
|
||||
|
||||
const path = getPathFromRequest(request);
|
||||
const minimal = getMinimalFromRequest(request);
|
||||
|
||||
if (doc.type == "url") {
|
||||
console.log(`Fetching ${doc.url}...`);
|
||||
@@ -65,12 +66,14 @@ export const loader: LoaderFunction = async ({ params, request }) => {
|
||||
doc,
|
||||
json,
|
||||
path,
|
||||
minimal,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
doc,
|
||||
json: JSON.parse(doc.contents),
|
||||
path,
|
||||
minimal,
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -91,7 +94,24 @@ function getPathFromRequest(request: Request): string | null {
|
||||
return `$.${path}`;
|
||||
}
|
||||
|
||||
type LoaderData = { doc: JSONDocument; json: unknown; path?: string };
|
||||
function getMinimalFromRequest(request: Request): boolean | undefined {
|
||||
const url = new URL(request.url);
|
||||
|
||||
const minimal = url.searchParams.get("minimal");
|
||||
|
||||
if (!minimal) {
|
||||
return;
|
||||
}
|
||||
|
||||
return minimal === "true";
|
||||
}
|
||||
|
||||
type LoaderData = {
|
||||
doc: JSONDocument;
|
||||
json: unknown;
|
||||
path?: string;
|
||||
minimal?: boolean;
|
||||
};
|
||||
|
||||
export const meta: MetaFunction = ({
|
||||
data,
|
||||
@@ -124,6 +144,7 @@ export default function JsonDocumentRoute() {
|
||||
doc={loaderData.doc}
|
||||
path={loaderData.path}
|
||||
key={loaderData.doc.id}
|
||||
minimal={loaderData.minimal}
|
||||
>
|
||||
<JsonProvider initialJson={loaderData.json}>
|
||||
<JsonSchemaProvider>
|
||||
@@ -145,7 +166,7 @@ export default function JsonDocumentRoute() {
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-screen flex flex-col sm:overflow-hidden">
|
||||
<Header />
|
||||
{!loaderData.minimal && <Header />}
|
||||
<div className="bg-slate-50 flex-grow transition dark:bg-slate-900">
|
||||
<div className="main-container flex justify-items-stretch h-full">
|
||||
<SideBar />
|
||||
|
||||
@@ -0,0 +1,310 @@
|
||||
---
|
||||
meta:
|
||||
title: JSON Hero - Privacy
|
||||
---
|
||||
|
||||
import { HomeHeader } from "~/components/Home/HomeHeader";
|
||||
|
||||
<HomeHeader />
|
||||
|
||||
<div style={{width: "800px", marginLeft: "12px", marginTop: "12px", marginBottom: "12px"}}>
|
||||
**PRIVACY NOTICE**
|
||||
|
||||
<br />
|
||||
|
||||
### Last updated June 01, 2022
|
||||
|
||||
<br />
|
||||
|
||||
This privacy notice for Stack Hero Limited ("Company," "we," "us," or "our"), describes how and why we might collect, store, use, and/or share ("process") your information when you use our services ("Services"), such as when you:
|
||||
Visit our website at https://jsonhero.io, or any website of ours that links to this privacy notice
|
||||
Engage with us in other related ways, including any sales, marketing, or events
|
||||
Questions or concerns? Reading this privacy notice will help you understand your privacy rights and choices. If you do not agree with our policies and practices, please do not use our Services. If you still have any questions or concerns, please contact us at hello@jsonhero.io.
|
||||
|
||||
<br />
|
||||
|
||||
**SUMMARY OF KEY POINTS**
|
||||
|
||||
<br />
|
||||
|
||||
This summary provides key points from our privacy notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our table of contents below to find the section you are looking for. You can also click here to go directly to our table of contents.
|
||||
|
||||
<br />
|
||||
|
||||
What personal information do we process? When you visit, use, or navigate our Services, we may process personal information depending on how you interact with Stack Hero Limited and the Services, the choices you make, and the products and features you use. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
Do we process any sensitive personal information? We may process sensitive personal information when necessary with your consent or as otherwise permitted by applicable law. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
Do we receive any information from third parties? We do not receive any information from third parties.
|
||||
|
||||
<br />
|
||||
|
||||
How do we process your information? We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. We process your information only when we have a valid legal reason to do so. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
In what situations and with which parties do we share personal information? We may share information in specific situations and with specific third parties. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
How do we keep your information safe? We have organizational and technical processes and procedures in place to protect your personal information. However, no electronic transmission over the internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorized third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
What are your rights? Depending on where you are located geographically, the applicable privacy law may mean you have certain rights regarding your personal information. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
How do I exercise my rights? The easiest way to exercise your rights is by filling out our data subject request form available here: hello@jsonhero.io, or by contacting us. We will consider and act upon any request in accordance with applicable data protection laws.
|
||||
|
||||
<br />
|
||||
|
||||
Want to learn more about what Stack Hero Limited does with any information we collect? Click here to review the notice in full.
|
||||
|
||||
<br />
|
||||
|
||||
**1. WHAT INFORMATION DO WE COLLECT?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: We collect personal information that you provide to us.
|
||||
|
||||
<br />
|
||||
|
||||
We collect personal information that you voluntarily provide to us when you express an interest in obtaining information about us or our products and Services, when you participate in activities on the Services, or otherwise when you contact us.
|
||||
|
||||
<br />
|
||||
|
||||
Sensitive Information. When necessary, with your consent or as otherwise permitted by applicable law, we process the following categories of sensitive information:
|
||||
All personal information that you provide to us must be true, complete, and accurate, and you must notify us of any changes to such personal information.
|
||||
|
||||
<br />
|
||||
|
||||
Information automatically collected
|
||||
|
||||
<br />
|
||||
|
||||
In Short: Some information — such as your Internet Protocol (IP) address and/or browser and device characteristics — is collected automatically when you visit our Services.
|
||||
|
||||
<br />
|
||||
|
||||
We automatically collect certain information when you visit, use, or navigate the Services. This information does not reveal your specific identity (like your name or contact information) but may include device and usage information, such as your IP address, browser and device characteristics, operating system, language preferences, referring URLs, device name, country, location, information about how and when you use our Services, and other technical information. This information is primarily needed to maintain the security and operation of our Services, and for our internal analytics and reporting purposes.
|
||||
|
||||
<br />
|
||||
|
||||
Like many businesses, we also collect information through cookies and similar technologies.
|
||||
|
||||
<br />
|
||||
|
||||
The information we collect includes:
|
||||
Log and Usage Data. Log and usage data is service-related, diagnostic, usage, and performance information our servers automatically collect when you access or use our Services and which we record in log files. Depending on how you interact with us, this log data may include your IP address, device information, browser type, and settings and information about your activity in the Services (such as the date/time stamps associated with your usage, pages and files viewed, searches, and other actions you take such as which features you use), device event information (such as system activity, error reports (sometimes called "crash dumps"), and hardware settings).
|
||||
|
||||
<br />
|
||||
|
||||
**2. HOW DO WE PROCESS YOUR INFORMATION?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent.
|
||||
|
||||
<br />
|
||||
|
||||
We process your personal information for a variety of reasons, depending on how you interact with our Services, including:
|
||||
|
||||
<br />
|
||||
|
||||
To save or protect an individual's vital interest. We may process your information when necessary to save or protect an individual’s vital interest, such as to prevent harm.
|
||||
|
||||
<br />
|
||||
|
||||
**3. WHAT LEGAL BASES DO WE RELY ON TO PROCESS YOUR INFORMATION?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: We only process your personal information when we believe it is necessary and we have a valid legal reason (i.e., legal basis) to do so under applicable law, like with your consent, to comply with laws, to provide you with services to enter into or fulfill our contractual obligations, to protect your rights, or to fulfill our legitimate business interests.
|
||||
|
||||
<br />
|
||||
|
||||
If you are located in the EU or UK, this section applies to you.
|
||||
|
||||
The General Data Protection Regulation (GDPR) and UK GDPR require us to explain the valid legal bases we rely on in order to process your personal information. As such, we may rely on the following legal bases to process your personal information:
|
||||
Consent. We may process your information if you have given us permission (i.e., consent) to use your personal information for a specific purpose. You can withdraw your consent at any time. Click here to learn more.
|
||||
Legal Obligations. We may process your information where we believe it is necessary for compliance with our legal obligations, such as to cooperate with a law enforcement body or regulatory agency, exercise or defend our legal rights, or disclose your information as evidence in litigation in which we are involved.
|
||||
Vital Interests. We may process your information where we believe it is necessary to protect your vital interests or the vital interests of a third party, such as situations involving potential threats to the safety of any person.
|
||||
|
||||
<br />
|
||||
|
||||
If you are located in Canada, this section applies to you.
|
||||
|
||||
We may process your information if you have given us specific permission (i.e., express consent) to use your personal information for a specific purpose, or in situations where your permission can be inferred (i.e., implied consent). You can withdraw your consent at any time. Click here to learn more.
|
||||
|
||||
<br />
|
||||
|
||||
In some exceptional cases, we may be legally permitted under applicable law to process your information without your consent, including, for example:
|
||||
If collection is clearly in the interests of an individual and consent cannot be obtained in a timely way
|
||||
For investigations and fraud detection and prevention
|
||||
For business transactions provided certain conditions are met
|
||||
If it is contained in a witness statement and the collection is necessary to assess, process, or settle an insurance claim
|
||||
For identifying injured, ill, or deceased persons and communicating with next of kin
|
||||
If we have reasonable grounds to believe an individual has been, is, or may be victim of financial abuse
|
||||
If it is reasonable to expect collection and use with consent would compromise the availability or the accuracy of the information and the collection is reasonable for purposes related to investigating a breach of an agreement or a contravention of the laws of Canada or a province
|
||||
If disclosure is required to comply with a subpoena, warrant, court order, or rules of the court relating to the production of records
|
||||
If it was produced by an individual in the course of their employment, business, or profession and the collection is consistent with the purposes for which the information was produced
|
||||
If the collection is solely for journalistic, artistic, or literary purposes
|
||||
If the information is publicly available and is specified by the regulations
|
||||
|
||||
<br />
|
||||
|
||||
**4. WHEN AND WITH WHOM DO WE SHARE YOUR PERSONAL INFORMATION?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: We may share information in specific situations described in this section and/or with the following third parties.
|
||||
|
||||
<br />
|
||||
|
||||
We may need to share your personal information in the following situations:
|
||||
Business Transfers. We may share or transfer your information in connection with, or during negotiations of, any merger, sale of company assets, financing, or acquisition of all or a portion of our business to another company.
|
||||
|
||||
<br />
|
||||
|
||||
**5. DO WE USE COOKIES AND OTHER TRACKING TECHNOLOGIES?**
|
||||
|
||||
In Short: We may use cookies and other tracking technologies to collect and store your information.
|
||||
|
||||
<br />
|
||||
|
||||
We may use cookies and similar tracking technologies (like web beacons and pixels) to access or store information. Specific information about how we use such technologies and how you can refuse certain cookies is set out in our Cookie Notice.
|
||||
|
||||
<br />
|
||||
|
||||
**6. HOW LONG DO WE KEEP YOUR INFORMATION?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: We keep your information for as long as necessary to fulfill the purposes outlined in this privacy notice unless otherwise required by law.
|
||||
|
||||
<br />
|
||||
|
||||
We will only keep your personal information for as long as it is necessary for the purposes set out in this privacy notice, unless a longer retention period is required or permitted by law (such as tax, accounting, or other legal requirements). No purpose in this notice will require us keeping your personal information for longer than 2 years.
|
||||
|
||||
<br />
|
||||
|
||||
When we have no ongoing legitimate business need to process your personal information, we will either delete or anonymize such information, or, if this is not possible (for example, because your personal information has been stored in backup archives), then we will securely store your personal information and isolate it from any further processing until deletion is possible.
|
||||
|
||||
<br />
|
||||
|
||||
**7. HOW DO WE KEEP YOUR INFORMATION SAFE?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: We aim to protect your personal information through a system of organizational and technical security measures.
|
||||
|
||||
<br />
|
||||
|
||||
We have implemented appropriate and reasonable technical and organizational security measures designed to protect the security of any personal information we process. However, despite our safeguards and efforts to secure your information, no electronic transmission over the Internet or information storage technology can be guaranteed to be 100% secure, so we cannot promise or guarantee that hackers, cybercriminals, or other unauthorized third parties will not be able to defeat our security and improperly collect, access, steal, or modify your information. Although we will do our best to protect your personal information, transmission of personal information to and from our Services is at your own risk. You should only access the Services within a secure environment.
|
||||
|
||||
<br />
|
||||
|
||||
**8. WHAT ARE YOUR PRIVACY RIGHTS?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: In some regions, such as the European Economic Area (EEA), United Kingdom (UK), and Canada, you have rights that allow you greater access to and control over your personal information. You may review, change, or terminate your account at any time.
|
||||
|
||||
<br />
|
||||
|
||||
In some regions (like the EEA, UK, and Canada), you have certain rights under applicable data protection laws. These may include the right (i) to request access and obtain a copy of your personal information, (ii) to request rectification or erasure; (iii) to restrict the processing of your personal information; and (iv) if applicable, to data portability. In certain circumstances, you may also have the right to object to the processing of your personal information. You can make such a request by contacting us by using the contact details provided in the section "HOW CAN YOU CONTACT US ABOUT THIS NOTICE?" below.
|
||||
|
||||
<br />
|
||||
|
||||
We will consider and act upon any request in accordance with applicable data protection laws.
|
||||
|
||||
<br />
|
||||
|
||||
If you are located in the EEA or UK and you believe we are unlawfully processing your personal information, you also have the right to complain to your local data protection supervisory authority. You can find their contact details here: https://ec.europa.eu/justice/data-protection/bodies/authorities/index_en.htm.
|
||||
|
||||
<br />
|
||||
|
||||
If you are located in Switzerland, the contact details for the data protection authorities are available here: https://www.edoeb.admin.ch/edoeb/en/home.html.
|
||||
|
||||
<br />
|
||||
|
||||
Withdrawing your consent: If we are relying on your consent to process your personal information, which may be express and/or implied consent depending on the applicable law, you have the right to withdraw your consent at any time. You can withdraw your consent at any time by contacting us by using the contact details provided in the section "HOW CAN YOU CONTACT US ABOUT THIS NOTICE?" below.
|
||||
|
||||
<br />
|
||||
|
||||
However, please note that this will not affect the lawfulness of the processing before its withdrawal, nor when applicable law allows, will it affect the processing of your personal information conducted in reliance on lawful processing grounds other than consent.
|
||||
|
||||
<br />
|
||||
|
||||
Cookies and similar technologies: Most Web browsers are set to accept cookies by default. If you prefer, you can usually choose to set your browser to remove cookies and to reject cookies. If you choose to remove cookies or reject cookies, this could affect certain features or services of our Services. To opt out of interest-based advertising by advertisers on our Services visit http://www.aboutads.info/choices/.
|
||||
|
||||
<br />
|
||||
|
||||
If you have questions or comments about your privacy rights, you may email us at hello@jsonhero.io.
|
||||
|
||||
<br />
|
||||
|
||||
**9. CONTROLS FOR DO-NOT-TRACK FEATURES**
|
||||
|
||||
<br />
|
||||
|
||||
Most web browsers and some mobile operating systems and mobile applications include a Do-Not-Track ("DNT") feature or setting you can activate to signal your privacy preference not to have data about your online browsing activities monitored and collected. At this stage no uniform technology standard for recognizing and implementing DNT signals has been finalized. As such, we do not currently respond to DNT browser signals or any other mechanism that automatically communicates your choice not to be tracked online. If a standard for online tracking is adopted that we must follow in the future, we will inform you about that practice in a revised version of this privacy notice.
|
||||
|
||||
<br />
|
||||
|
||||
**10. DO CALIFORNIA RESIDENTS HAVE SPECIFIC PRIVACY RIGHTS?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: Yes, if you are a resident of California, you are granted specific rights regarding access to your personal information.
|
||||
|
||||
<br />
|
||||
|
||||
California Civil Code Section 1798.83, also known as the "Shine The Light" law, permits our users who are California residents to request and obtain from us, once a year and free of charge, information about categories of personal information (if any) we disclosed to third parties for direct marketing purposes and the names and addresses of all third parties with which we shared personal information in the immediately preceding calendar year. If you are a California resident and would like to make such a request, please submit your request in writing to us using the contact information provided below.
|
||||
|
||||
<br />
|
||||
|
||||
If you are under 18 years of age, reside in California, and have a registered account with Services, you have the right to request removal of unwanted data that you publicly post on the Services. To request removal of such data, please contact us using the contact information provided below and include the email address associated with your account and a statement that you reside in California. We will make sure the data is not publicly displayed on the Services, but please be aware that the data may not be completely or comprehensively removed from all our systems (e.g., backups, etc.).
|
||||
|
||||
<br />
|
||||
|
||||
**11. DO WE MAKE UPDATES TO THIS NOTICE?**
|
||||
|
||||
<br />
|
||||
|
||||
In Short: Yes, we will update this notice as necessary to stay compliant with relevant laws.
|
||||
|
||||
<br />
|
||||
|
||||
We may update this privacy notice from time to time. The updated version will be indicated by an updated "Revised" date and the updated version will be effective as soon as it is accessible. If we make material changes to this privacy notice, we may notify you either by prominently posting a notice of such changes or by directly sending you a notification. We encourage you to review this privacy notice frequently to be informed of how we are protecting your information.
|
||||
|
||||
<br />
|
||||
|
||||
**12. HOW CAN YOU CONTACT US ABOUT THIS NOTICE?**
|
||||
|
||||
<br />
|
||||
|
||||
If you have questions or comments about this notice, you may email us at hello@jsonhero.io or by post to:
|
||||
|
||||
<br />
|
||||
|
||||
Stack Hero Limited
|
||||
68 Hanbury St
|
||||
London, England E1 5JL
|
||||
United Kingdom
|
||||
|
||||
<br />
|
||||
|
||||
**13. HOW CAN YOU REVIEW, UPDATE, OR DELETE THE DATA WE COLLECT FROM YOU?**
|
||||
|
||||
<br />
|
||||
|
||||
Based on the applicable laws of your country, you may have the right to request access to the personal information we collect from you, change that information, or delete it in some circumstances. To request to review, update, or delete your personal information, please visit: hello@jsonhero.io.
|
||||
|
||||
</div>
|
||||
@@ -37,7 +37,11 @@ async function getPeekalink(link: string): Promise<PreviewResult> {
|
||||
}
|
||||
|
||||
export async function getUriPreview(uri: string): Promise<PreviewResult> {
|
||||
const head = await headUri(uri);
|
||||
console.log(`[getPreview][getUriPreview] ${uri}`);
|
||||
|
||||
const url = rewriteUrl(uri);
|
||||
|
||||
const head = await headUri(url.href);
|
||||
|
||||
// If the url is an image content type, return a preview image
|
||||
if (
|
||||
@@ -46,14 +50,14 @@ export async function getUriPreview(uri: string): Promise<PreviewResult> {
|
||||
contentType.includes(head.contentType)
|
||||
)
|
||||
) {
|
||||
const previewImage = createPreviewImage(uri, head);
|
||||
const previewImage = createPreviewImage(url.href, head);
|
||||
|
||||
return previewImage;
|
||||
}
|
||||
|
||||
// If the url is a json content type, attempt to request the json and return a preview json
|
||||
if (head?.contentType.includes("application/json")) {
|
||||
const response = await safeFetch(uri, {
|
||||
const response = await safeFetch(url.href, {
|
||||
headers: {
|
||||
accept: "application/json",
|
||||
},
|
||||
@@ -65,10 +69,10 @@ export async function getUriPreview(uri: string): Promise<PreviewResult> {
|
||||
|
||||
const jsonBody = await response.json();
|
||||
|
||||
return createPreviewJson(uri, jsonBody);
|
||||
return createPreviewJson(url.href, jsonBody);
|
||||
}
|
||||
|
||||
const peekalinkResult = await getPeekalink(uri);
|
||||
const peekalinkResult = await getPeekalink(url.href);
|
||||
|
||||
return peekalinkResult;
|
||||
}
|
||||
@@ -149,3 +153,33 @@ function createPreviewImage(uri: string, head: HeadInfo): PreviewImage {
|
||||
size: head.contentLength,
|
||||
};
|
||||
}
|
||||
|
||||
// Rewrites the URL to convert an ipfs: url to use https://ipfs.io/ipfs/
|
||||
function rewriteUrl(url: string): URL {
|
||||
const unmodifiedUrl = new URL(url);
|
||||
|
||||
console.log(
|
||||
`[getPreview][rewriteUrl] ${unmodifiedUrl.href}, protocol: ${unmodifiedUrl.protocol}, hostname: ${unmodifiedUrl.hostname}, pathname: ${unmodifiedUrl.pathname}, search: ${unmodifiedUrl.search}, hash: ${unmodifiedUrl.hash}`
|
||||
);
|
||||
|
||||
// Rewrite the URL if it is a relative URL
|
||||
if (unmodifiedUrl.protocol === "ipfs:") {
|
||||
if (unmodifiedUrl.hostname === "") {
|
||||
return new URL(
|
||||
`https://ipfs.io/ipfs/${unmodifiedUrl.pathname.substring(2)}`
|
||||
);
|
||||
} else {
|
||||
// Parse out the "hostname" from the raw url because hostnames are case-insensitive and automatically lowercased
|
||||
const urlMatches = url.match(/^ipfs:\/\/([A-Za-z0-9]+)(\/.*)?/i);
|
||||
const hostname = urlMatches?.[1];
|
||||
|
||||
return new URL(
|
||||
`https://ipfs.io/ipfs/${hostname ?? unmodifiedUrl.hostname}${
|
||||
unmodifiedUrl.pathname.length > 0 ? `/${unmodifiedUrl.pathname}` : ""
|
||||
}${unmodifiedUrl.search}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return unmodifiedUrl;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/react": "^17.0.24",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"concurrently": "^7.0.0",
|
||||
"esbuild-visualizer": "^0.3.1",
|
||||
"jest": "^27.4.7",
|
||||
@@ -3050,6 +3051,12 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/ua-parser-js": {
|
||||
"version": "0.7.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz",
|
||||
"integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/unist": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
@@ -16018,6 +16025,12 @@
|
||||
"integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/ua-parser-js": {
|
||||
"version": "0.7.36",
|
||||
"resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.36.tgz",
|
||||
"integrity": "sha512-N1rW+njavs70y2cApeIw1vLMYXRwfBy+7trgavGuuTfOd7j1Yh7QTRc/yqsPl6ncokt72ZXuxEU0PiCp9bSwNQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/unist": {
|
||||
"version": "2.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
|
||||
|
||||
@@ -73,6 +73,7 @@
|
||||
"@types/lodash-es": "^4.17.6",
|
||||
"@types/react": "^17.0.24",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"@types/ua-parser-js": "^0.7.36",
|
||||
"concurrently": "^7.0.0",
|
||||
"esbuild-visualizer": "^0.3.1",
|
||||
"jest": "^27.4.7",
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;700;900&display=swap");
|
||||
@font-face {
|
||||
font-family: "MonoLisa";
|
||||
src: url("/fonts/MonoLisa/woff2/MonoLisa-Regular.woff2") format("woff2"),
|
||||
url("/fonts/MonoLisa/woff/MonoLisa-Regular.woff") format("woff");
|
||||
}
|
||||
@import url("https://fonts.googleapis.com/css2?family=Source+Sans+Pro:wght@400;700;900&family=Roboto+Mono&display=swap");
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
|
||||
@@ -16,17 +16,19 @@ module.exports = {
|
||||
"6xl": ["2.625rem", "3rem"], // 42px
|
||||
"7xl": "4rem", // 64px
|
||||
"8xl": "8rem", // 128px
|
||||
|
||||
},
|
||||
extend: {
|
||||
height: {
|
||||
viewerHeight: "calc(100vh - 146px)",
|
||||
inspectorHeight: "calc(100vh - 70px)",
|
||||
jsonViewerHeight: "calc(100vh - 106px)",
|
||||
viewerHeightMinimal: "calc(100vh - 106px)",
|
||||
inspectorHeightMinimal: "calc(100vh - 30px)",
|
||||
jsonViewerHeightMinimal: "calc(100vh - 66px)",
|
||||
},
|
||||
fontFamily: {
|
||||
sans: ["Source Sans Pro", "sans-serif"],
|
||||
mono: ["MonoLisa", "monospace"],
|
||||
mono: ["Roboto Mono", "monospace"],
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||