Skip to content

Commit

Permalink
feat: Sketch embeds
Browse files Browse the repository at this point in the history
close #154

While Sketch provide neither their oEmbed endpoint nor developer
document about embedding, it seems their URL schema is simple and easy
to generate embed URL from it.
  • Loading branch information
pocka committed Jun 18, 2022
1 parent 7da8506 commit 4e75ddf
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 2 deletions.
36 changes: 34 additions & 2 deletions packages/examples/stories/tests/placeholder.stories.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,47 @@ export default {
title: "Tests/Placeholder",
};

export const showPlaceholder = () => <Button>Button</Button>;
const Template = () => <Button>Button</Button>;

export const showPlaceholder = Template.bind({});

showPlaceholder.storyName = "Show placeholder when no `design` parameter";

export const showError = () => <Button>Button</Button>;
export const showError = Template.bind({});

showError.storyName = "Show error message when `type` is not supported";
showError.parameters = {
design: {
type: "foo",
},
};

export const showSketchError1 = Template.bind({});
showSketchError1.storyName =
"Show error message when unsupported Sketch link is passed";
showSketchError1.parameters = {
design: {
type: "sketch",
url: "https://www.sketch.com/s/foo",
},
};

export const showSketchError2 = Template.bind({});
showSketchError2.storyName =
"Show error message when the hostname of Sketch link is invalid";
showSketchError2.parameters = {
design: {
type: "sketch",
url: "https://sketch.com/s/foo/a/bar",
},
};

export const showSketchError3 = Template.bind({});
showSketchError3.storyName =
"Show error message when the Sketch link is not HTTPS";
showSketchError3.parameters = {
design: {
type: "sketch",
url: "http://www.sketch.com/s/foo/a/bar",
},
};
8 changes: 8 additions & 0 deletions packages/storybook-addon-designs/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export type Config =
| IFrameConfig
| FigmaConfig
| FigspecConfig
| SketchConfig
| ImageConfig
| LinkConfig;

Expand Down Expand Up @@ -53,6 +54,13 @@ export interface FigmaConfig extends IFrameConfigBase {
embedHost?: string;
}

/**
* Render Sketch artboard. (experimental)
*/
export interface SketchConfig extends IFrameConfigBase {
type: "sketch";
}

/**
* Render Figma files or frames via [figspec](https://github.com/pocka/figspec).
*/
Expand Down
120 changes: 120 additions & 0 deletions packages/storybook-addon-designs/src/register/components/Sketch.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/** @jsx jsx */
import { Link, Placeholder } from "@storybook/components";
import { jsx } from "@storybook/theming";
import { FC, Fragment, ReactNode, useMemo } from "react";

import { SketchConfig, IFrameConfigBase } from "../../config";

import { IFrame } from "./IFrame";

interface ParseSucceeded<T> {
valid: true;

data: T;
}

interface ParseFailed {
valid: false;

error: ReactNode;
}

type Parser<T> = (url: URL) => ParseSucceeded<T> | ParseFailed;

export const v1UrlParser: Parser<IFrameConfigBase> = (url) => {
if (url.protocol !== "https:") {
return {
valid: false,
error: (
<Fragment>
Expected HTTPS link, received <code>{url.protocol}</code>.
</Fragment>
),
};
}

if (url.hostname !== "www.sketch.com") {
return {
valid: false,
error: (
<Fragment>
Expected a hostname <code>www.sketch.com</code>, received{" "}
<code>{url.hostname}</code>
</Fragment>
),
};
}

const malformedUrlErrorMessage = (
<Fragment>
Expected pathname <code>{"/s/<string>/a/<string>"}</code>, received{" "}
<code>{url.pathname}</code>.
</Fragment>
);

const pathSegments = url.pathname.split("/").slice(1);
if (pathSegments.length < 4) {
return {
valid: false,
error: malformedUrlErrorMessage,
};
}

if (pathSegments[0] === "embed") {
return {
valid: true,
data: {
url: url.href,
offscreen: false,
},
};
}

const [s, sid, a, aid] = pathSegments;
if (s !== "s" || !sid || a !== "a" || !aid) {
return {
valid: false,
error: malformedUrlErrorMessage,
};
}

return {
valid: true,
data: {
url: `https://www.sketch.com/embed/s/${sid}/a/${aid}`,
offscreen: false,
},
};
};

interface Props {
config: SketchConfig;
}

export const Sketch: FC<Props> = ({ config }) => {
const result = useMemo(() => {
const parsed = v1UrlParser(new URL(config.url));
if (!parsed.valid) {
return parsed;
}

return {
...parsed,
data: {
...config,
...parsed.data,
},
};
}, [config]);

if (!result.valid) {
return (
<Placeholder>
<Fragment>Invalid Sketch URL</Fragment>
<Fragment>{result.error}</Fragment>
</Placeholder>
);
}

return <IFrame defer config={result.data} />;
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Figma } from "./Figma";
import { IFrame } from "./IFrame";
import { ImagePreview } from "./Image";
import { LinkPanel } from "./LinkPanel";
import { Sketch } from "./Sketch";
import { Tab, Tabs } from "./Tabs";

const Figspec = lazy(() => import("./Figspec"));
Expand Down Expand Up @@ -59,6 +60,11 @@ export const Wrapper: FC<Props> = ({ config }) => {
content: <Figma config={cfg} />,
offscreen: false,
};
case "sketch":
return {
...meta,
content: <Sketch config={cfg} />,
};
case "figspec":
case "experimental-figspec":
if (cfg.type === "experimental-figspec") {
Expand Down

0 comments on commit 4e75ddf

Please sign in to comment.