Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DRAFT] New Perseus Preview component #1304

Open
wants to merge 45 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
b6318da
IframeContentRenderer: move to 'preview' subfolder
jeremywiebe Aug 12, 2024
08f346c
Add vite aliases for `strings` exports
jeremywiebe Aug 12, 2024
e662bdc
ContentRenderer: Initial basic content preview (no iframe)
jeremywiebe May 18, 2024
0193db8
DeviceFramer: Tweak comment
jeremywiebe May 22, 2024
1263fbc
ContentRenderer: Get linting working somewhat
jeremywiebe May 24, 2024
2c9f10b
Lint: Use phosphor icon
jeremywiebe May 24, 2024
39231e5
ContentRenderer: Fix keypad connection for 'mobile' in previews
jeremywiebe Jul 26, 2024
2ef1dee
ContentRenderer: Implement nochrome
jeremywiebe Jul 26, 2024
1acd8e1
ContentRenderer: add linter props
jeremywiebe Jul 30, 2024
4a76f5e
ContentRenderer: Fix import of mock strings
jeremywiebe Jul 30, 2024
79b19d3
ContentRenderer: set framework-perseus and, if relevant, perseus-mobi…
jeremywiebe Aug 1, 2024
4ee30b8
ContentRenderer: add story to demo the new preview system
jeremywiebe Aug 7, 2024
5d191b0
ContentRenderer: move article sample data to .testdata.ts file
jeremywiebe Aug 7, 2024
642f930
ContentRenderer: add story showing lint support
jeremywiebe Aug 7, 2024
53d0e40
ContentRenderer: Extend linting story
jeremywiebe Aug 8, 2024
8b46b8b
Lint: revert updates for now
jeremywiebe Aug 8, 2024
f9ae119
ItemEditor: Remove imperative API for preview updates
jeremywiebe Jul 17, 2024
ad99a25
ItemEditor: provide linter context to new ContentRenderer (preview)
jeremywiebe Jul 30, 2024
ee27578
Remove Storybook-specific preview for EditorPage - real preview now w…
jeremywiebe Aug 7, 2024
84b23db
HintEditor: migrate to iframe-less preview (ContentRenderer)
jeremywiebe Jul 30, 2024
2823360
HintEditor: propogate preview device type to ContentRenderer
jeremywiebe Aug 6, 2024
67b9ee4
ArticleEditor: migrate to new preview (ContentRenderer)
jeremywiebe Jul 30, 2024
c5b3986
ArticleEditor: restore section border to preview
jeremywiebe Aug 1, 2024
4e52482
ArticleEditor: handle rendering one or all sections
jeremywiebe Aug 1, 2024
9e6c8d6
ArticleEditor: build onChange handler safely (bind this as we pass a …
jeremywiebe Aug 1, 2024
dc9fff2
ArticleEditor: add example article to article editor story
jeremywiebe Aug 1, 2024
f8b21ef
ArticleEditor: fix more types related to refs
jeremywiebe Aug 6, 2024
e891cca
Remove part of a dependencies error message that makes no sense anymore
jeremywiebe Jul 30, 2024
090b701
Spike on iframe dynamic rendering - failure as styles are very hard t…
jeremywiebe Jul 31, 2024
2229388
ArticleEditor: remove 'replace' prop passed to Editor - articles aren…
jeremywiebe Aug 9, 2024
f5f19db
PerseusEditor: clarify types around serialize() and getSaveWarnings()
jeremywiebe Aug 9, 2024
e260df8
Fonts: fix path to Lato-Regular.woff2 in Storybook
jeremywiebe Aug 9, 2024
64826e7
Lint: modernize stories
jeremywiebe Aug 9, 2024
011f727
Lint: Use <span> for lint content as <div> is not supported
jeremywiebe Aug 12, 2024
9e87fc8
Lint: Hide children prop in stories - it's not useful
jeremywiebe Aug 12, 2024
3cdd246
Lint: Provide 'severity' dropdown in storybook
jeremywiebe Aug 12, 2024
05fc776
InteractiveGraphEditor: fix readonly typedness in story
jeremywiebe Aug 12, 2024
b2ea199
ViewportResizer: Use Phosphor icons
jeremywiebe Aug 12, 2024
e8acd31
EditorPage: tiny WB improvements for JSON mode and viewport resizer a…
jeremywiebe Aug 12, 2024
dbd968e
GroupEditor: Handle ref being null - should not happen so using invar…
jeremywiebe Aug 12, 2024
a80c35b
EditorPage: Preview only desktop, but note phone and tablet checking …
jeremywiebe Aug 13, 2024
9920bc7
ArticleEditor: remove passing 'screen' to editor
jeremywiebe Aug 13, 2024
760fdcb
EditorPage: Add note about preview being desktop-only now
jeremywiebe Aug 13, 2024
05b5521
EditorPage: Don't change ApiOptions based on previewDevice
jeremywiebe Aug 13, 2024
143f88c
Changeset
jeremywiebe Aug 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/hungry-seals-refuse.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@khanacademy/perseus-dev-ui": patch
"@khanacademy/perseus": patch
"@khanacademy/perseus-editor": patch
---

Migrate Perseus preview to use non-iframe approach
2 changes: 1 addition & 1 deletion .storybook/preview-head.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<!-- Preload WB fonts to prevent layout shifting due to font rendering -->
<link
rel="preload"
href="fonts/pre/Lato-Regular.woff2"
href="fonts/lato/Lato-Regular.woff2"
as="font"
type="font/woff2"
crossorigin
Expand Down
36 changes: 35 additions & 1 deletion dev/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,41 @@ const packageAliases = {};
glob.sync(resolve(__dirname, "../packages/*/package.json")).forEach(
(packageJsonPath) => {
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
packageAliases[pkg.name] = join(dirname(packageJsonPath), pkg.source);

// "exports" is the more modern way to declare package exports. Some,
// but not all, Perseus packages delcare "exports".
if ("exports" in pkg) {
// Not all packages export strings, but for those that do we need
// to set up an alias so Vite properly resolves them.
// Eg `import {strings, mockStrings} from "@khanacademy/perseus/strings";`
// And MOST IMPORTANTLY, this alias _must_ precede the main
// import, otherwise Vite will just use the main export and tack
// `/strings` onto the end, resulting in a path like this:
// `packages/perseus/src/index.ts/strings`
const stringsSource = pkg.exports["./strings"]?.source;
if (stringsSource != null) {
packageAliases[`${pkg.name}/strings`] = join(
dirname(packageJsonPath),
stringsSource,
);
}

const mainSource = pkg.exports["."]?.source;
if (mainSource == null) {
throw new Error(
`Package declares 'exports', but not provide a main export (exports["."])`,
);
}
packageAliases[pkg.name] = join(
dirname(packageJsonPath),
mainSource,
);
} else {
packageAliases[pkg.name] = join(
dirname(packageJsonPath),
pkg.source,
);
}
},
);

Expand Down
590 changes: 589 additions & 1 deletion packages/perseus-editor/src/__stories__/article-editor.stories.tsx

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,24 +1,15 @@
import {
Renderer,
type APIOptions,
type DeviceType,
type Hint,
type PerseusAnswerArea,
type PerseusRenderer,
} from "@khanacademy/perseus";
import Button from "@khanacademy/wonder-blocks-button";
import {View} from "@khanacademy/wonder-blocks-core";
import IconButton from "@khanacademy/wonder-blocks-icon-button";
import {Strut} from "@khanacademy/wonder-blocks-layout";
import {color, spacing} from "@khanacademy/wonder-blocks-tokens";
import {LabelLarge} from "@khanacademy/wonder-blocks-typography";
import xIcon from "@phosphor-icons/core/regular/x.svg";
import {action} from "@storybook/addon-actions";
import {StyleSheet} from "aphrodite";
import * as React from "react";

// eslint-disable-next-line import/no-relative-packages
import {mockStrings} from "../../../perseus/src/strings";
import EditorPage from "../editor-page";

import {flags} from "./flags-for-api-options";
Expand All @@ -27,13 +18,14 @@ type Props = {
apiOptions?: APIOptions;
question?: PerseusRenderer;
hints?: ReadonlyArray<Hint>;
developerMode?: boolean;
};

const onChangeAction = action("onChange");

function EditorPageWithStorybookPreview(props: Props) {
const [previewDevice, setPreviewDevice] =
React.useState<DeviceType>("phone");
React.useState<DeviceType>("desktop");
const [jsonMode, setJsonMode] = React.useState<boolean | undefined>(false);
const [answerArea, setAnswerArea] = React.useState<
PerseusAnswerArea | undefined | null
Expand All @@ -45,8 +37,6 @@ function EditorPageWithStorybookPreview(props: Props) {
props.hints,
);

const [panelOpen, setPanelOpen] = React.useState<boolean>(true);

const apiOptions = props.apiOptions ?? {
isMobile: false,
flags,
Expand All @@ -60,7 +50,7 @@ function EditorPageWithStorybookPreview(props: Props) {
onPreviewDeviceChange={(newDevice) =>
setPreviewDevice(newDevice)
}
developerMode={true}
developerMode={props.developerMode}
jsonMode={jsonMode}
answerArea={answerArea}
question={question}
Expand All @@ -85,85 +75,8 @@ function EditorPageWithStorybookPreview(props: Props) {
}
}}
/>

{/* Button to open panel */}
{!panelOpen && (
<Button
onClick={() => setPanelOpen(!panelOpen)}
style={styles.openPanelButton}
>
Open preview (storybook only)
</Button>
)}

{/* Panel to show the question/hint previews */}
{panelOpen && (
<View style={styles.panel}>
{/* Close button */}
<IconButton
icon={xIcon}
onClick={() => setPanelOpen(!panelOpen)}
style={styles.closeButton}
/>

<View style={styles.panelInner}>
{/* Question preview */}
<Renderer
strings={mockStrings}
apiOptions={apiOptions}
{...question}
/>
</View>

{/* Hints preview */}
{hints?.map((hint, index) => (
<View key={index} style={styles.panelInner}>
<Strut size={spacing.medium_16} />
<LabelLarge>{`Hint ${index + 1}`}</LabelLarge>
<Renderer
strings={mockStrings}
apiOptions={apiOptions}
hintMode={true}
{...hint}
/>
</View>
))}
</View>
)}
</View>
);
}

const styles = StyleSheet.create({
panel: {
position: "fixed",
right: 0,
height: "90vh",
overflow: "auto",
flex: "none",
backgroundColor: color.fadedBlue16,
padding: spacing.medium_16,
borderRadius: spacing.small_12,
alignItems: "end",
},
panelInner: {
flex: "none",
backgroundColor: color.white,
borderRadius: spacing.xSmall_8,
marginTop: spacing.medium_16,
width: "100%",
padding: spacing.xSmall_8,
},
closeButton: {
margin: 0,
},
openPanelButton: {
position: "fixed",
right: spacing.medium_16,
// Extra space so it doesn't get covered up by storybook's
// "Style warnings" button.
bottom: spacing.xxxLarge_64,
},
});

export default EditorPageWithStorybookPreview;
13 changes: 9 additions & 4 deletions packages/perseus-editor/src/__stories__/editor-page.stories.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import * as React from "react";

Check failure on line 1 in packages/perseus-editor/src/__stories__/editor-page.stories.tsx

View workflow job for this annotation

GitHub Actions / Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x)

'React' is defined but never used. Allowed unused vars must match /^_*$/u

import {registerAllWidgetsAndEditorsForTesting} from "../util/register-all-widgets-and-editors-for-testing";

import EditorPageWithStorybookPreview from "./editor-page-with-storybook-preview";

import type {Meta, StoryObj} from "@storybook/react";

registerAllWidgetsAndEditorsForTesting(); // SIDE_EFFECTY!!!! :cry:

export default {
const meta: Meta = {
title: "PerseusEditor/EditorPage",
component: EditorPageWithStorybookPreview,
args: {developerMode: false},
};
export default meta;

export const Demo = (): React.ReactElement => {
return <EditorPageWithStorybookPreview />;
};
type Story = StoryObj<typeof EditorPageWithStorybookPreview>;

export const Demo: Story = {};
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import {Renderer} from "@khanacademy/perseus";
// eslint-disable-next-line monorepo/no-internal-import
import {mockStrings} from "@khanacademy/perseus/strings";

Check failure on line 3 in packages/perseus-editor/src/__stories__/iframe-renderer.stories.tsx

View workflow job for this annotation

GitHub Actions / Lint, Typecheck, Format, and Test (ubuntu-latest, 20.x)

Unable to resolve path to module '@khanacademy/perseus/strings'

import IFrameRenderer from "../iframe-renderer";

import type {PerseusRenderer} from "@khanacademy/perseus";
import type {Meta, StoryObj} from "@storybook/react";

const meta: Meta<typeof IFrameRenderer> = {
title: "PerseusEditor/IFrameRenderer",
component: IFrameRenderer,
};

export default meta;
type Story = StoryObj<typeof IFrameRenderer>;

const question: PerseusRenderer = {
content:
"Which of the following values of $x$ satisfies the equation $\\sqrt{64}=x$ ?\n\n[[\u2603 radio 1]]\n\n",
images: {},
widgets: {
"radio 1": {
graded: true,
version: {
major: 1,
minor: 0,
},
static: false,
type: "radio",
options: {
displayCount: null,
onePerLine: false,
choices: [
{
content: "$-8$ and $8$",
correct: false,
clue: "The square root operation ($\\sqrt{\\phantom{x}}$) calculates *only* the positive square root when performed on a number, so $x$ is equal to *only* $8$.",
},
{
content: "$-8$",
correct: false,
clue: "While $(-8)^2=64$, the square root operation ($\\sqrt{\\phantom{x}}$) calculates *only* the positive square root when performed on a number.",
},
{
content: "$8$",
correct: true,
isNoneOfTheAbove: false,
clue: "$8$ is the positive square root of $64$.",
},
{
content: "No value of $x$ satisfies the equation.",
correct: false,
isNoneOfTheAbove: false,
clue: "$8$ satisfies the equation.",
},
],
countChoices: false,
hasNoneOfTheAbove: false,
multipleSelect: false,
randomize: false,
deselectEnabled: false,
},
alignment: "default",
},
},
};

export const Primary: Story = {
args: {
style: {width: "100%", height: "500px"},
styleSelector: 'link, style[type="text/css"]',
children: (
<Renderer strings={mockStrings} apiOptions={{}} {...question} />
),
},
};
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,9 @@ export const WithSaveWarnings = (): React.ReactElement => {
segmentWithLockedFigures,
);
const [hints, setHints] = React.useState<ReadonlyArray<Hint> | undefined>();
const [saveWarnings, setSaveWarnings] = React.useState<string[]>([]);
const [saveWarnings, setSaveWarnings] = React.useState<readonly string[]>(
[],
);

const editorPageRef = React.useRef<EditorPage>(null);

Expand Down
Loading
Loading