Skip to content

Commit

Permalink
feat(playground): customizable prettier config + multiple prettier tabs
Browse files Browse the repository at this point in the history
  • Loading branch information
astahmer committed Nov 11, 2022
1 parent c851903 commit bf68177
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 37 deletions.
60 changes: 41 additions & 19 deletions playground/src/routes/Playground.machine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type PlaygroundContext = {

selectedOpenApiFileName: string;
selectedTemplateName: string;
selectedPrettierConfig: string;

templateContext: TemplateContext | null;
presetTemplates: Record<string, string>;
Expand All @@ -56,7 +57,6 @@ type PlaygroundEvent =
| { type: "Select preset template"; template: PresetTemplate }
| { type: "Open options" }
| { type: "Close options" }
| { type: "Open prettier config" }
| { type: "Open monaco settings" }
| { type: "Add file" }
| { type: "Edit file"; tab: FileTabData }
Expand All @@ -66,7 +66,6 @@ type PlaygroundEvent =
| { type: "Update preview options"; options: OptionsFormValues }
| { type: "Reset preview options" }
| { type: "Save options"; options: OptionsFormValues }
| { type: "Update prettier config"; options: PrettierOptions }
| { type: "Update monaco settings" }
| { type: "Submit file modal"; tab: FileTabData }
| { type: "Close modal" }
Expand All @@ -85,9 +84,10 @@ const initialInputList = [
const initialOuputTab = "api.client.ts";

const isValidDocumentName = (name: string) =>
!name.startsWith(".prettier") && (name.endsWith(".yml") || name.endsWith(".yaml") || name.endsWith(".json"));
!isValidPrettierConfig(name) && (name.endsWith(".yml") || name.endsWith(".yaml") || name.endsWith(".json"));

const isValidTemplateName = (name: string) => name.endsWith(".hbs");
const isValidPrettierConfig = (name: string) => name.startsWith(".prettier") && name.endsWith(".json");

export const playgroundMachine =
/** @xstate-layout N4IgpgJg5mDOIC5QAcA2BDAnlATgewFcA7CAOlT3QgEsioBiAURoBc8cACAGUokgG0ADAF1EKPLGotqeImJAAPRAFoAHAEYAbKQDsAJgAsmzQFYAnJoDMJweYA0ITIgNmDpA4L0bXqgzvM6lgC+QQ5oWLiEJOS8tAzMUuzcvALqokggyBJSMnIZSgjK6oKqZqRm6qomqt4Glv4GDk4IJnrqulV6gSVaqpp6IWEY2PjEZDhgVJikAArDcfQQsmCktABueADWK+EjUeOTENNzWHEI63gAxujSskLC9-JZkrd5oAWWtrpdJi46FZZLHpLJomoh1OoTCZSKodH0DCZPrD-ppBplhpExqQJlNZvM6PQAKrICA3MAcWjIAgsR4ZZ45WTyArqAyqUiCdR6aqCEFeYzAsEIdRA9rAvQcnn6CF+NG7THRHFHPGnAkAZTAqDAlxYFKIVJ1LHQACNaeIXrkmYh6to+hUEZYzDoWTVBWYLLodJpBGYanorP1ZRjRgrDsd8Qx1Zrtbr9RxDSa0k9sq9LQhPYJSF4LBDfiZ-FZXe6dJ7Of8kYJNKpAxFgwdcSdMAtI1qdYQWLH46bMsmLflwao9LooWYTF6vII-KDHIg3Wz9F1-izXMZgqF0TX9tjQ8rG2qNS2OMgJrAwAawABbcIsMBd+kpvsITSs9kGAyGTT+VTe36CzmuGFwsWbrqMWIGrkMG5YoqYYqgwADyyBgEQHB4MgrywLePaMg+RQeOyno6AipjqG6-y-lY7SCJ4Bi9CUCKOtWexQduDYLAhSGHhMLDSGAnCXLIABm1BQJh5rYe8KggWUtimC44p5pyPK-vUg6fE6lYgoIxYmIx8p1kqrEEuxyHnrI6D8RwJ7cXEGEiEmYlvIokmwqQ-SVn4rQ+rCPq-lUZRZqolh+FoE48rptZbvW4b0AAghAEAcEJmqiQyjkFMohgZnoFQlBoJGBJ6v48myDrAkCFhupYLLhZu0E7gsCQ6klN52XSWFpSo-iWDCjq8kBEKuK6b74V0X7GKUfTqDVzFRbB9AAEoXngazks1KX3hJCBVdCrLCl+u1+o005CoC0JmJ4gJwhoXokdNIa4o1cQoWhuSwESJJkpxYBrNQYAAO7Peh629ptyiVtoxbBR+1gFeov4BK5Zg-EF5iOgYd36dMj10IDr0LXAp5fT9-247ItnpGaqWpsol3slp2XbV4xY6PDLKkK0eicyBdS+joGORUq2NQKTRBvaq6ArSL5P2VTOF+IOnJaP02VPpolRTs0IGmOynwcl4rSApU-N1ULUv0AAwhQJ5S8D4lOYUXhuJYVQroBzomL+hE6OzEKwhy13GOja5yhFJusE9R6njxfGCcJ72kteX3WbxHD8UQQkia1lMbfbYMIu4NEaYbQKWMpz4aDUJick+LITsb26m5Hycx+nceWxI5KmaSqC2x1hTO2UIIaFRo5vsPGvgq+0nnSCSNVzyo71w94c46ZRDmXgllRzZFtW53eDd731M0d1bRlVo-x8j+x0kTYrleEFskkaRS+CyvwvNXGxr0KqBBGueUhErUE1BwLu6Ae5Z27A5amxQBzlH8DYKuvtCKCkCG4KocIfDeTqAMYOQZaoN3fkAkB8Zd4d1AQfcBR8cKIm9k6cUJQvR5kdCzY6A42RM2qFCD8sJISv2mObHE0gcaf1Ib-f+gDP5gIgRTKBstQYuDKMKT0XpKwVF8CyV0WhSDCjqFXN0OUgr8NIIIyYwiP7APJKQ9u1tpHUNBlyGEthKh+kXJ8KqroPzszdOKHQWlXxaQxvjSQAAvFqsi7wg1ztmXQlY2iaDMECAcE8EBcnaAkx0xQ2i5hcKiNERAD5wCePgrEFAqBxBljndKEJKgdGok6JGH4jrNDqN1Ec2U-F1D+CyPJEEmL3QMuGSpUSCg1FICCYwcJxQeAdEYX80zyhBU+Nlb0mTenrn6ZjUgptULoWGXbdKHhoQkU+LJQKgRKjkTaDo4eDoahLL6MYxuXFo6p1jlAfZfdlBvkHFCXoVdRz6BKB7G+KlxlaWKH4lkej1khwIcvHIq8zIWSsuY+AbVoFy1KK5fQQIaJaBBJCEFms+glTFLCU6XQzBPKIaI40nzqaESyrip0HkeTO0FIFaETDfhwnMDUWExjTE3CenSo0DKcKGDcCySEVEKzO2sJ472qNgSQ38Co-hErQYQk+PAqEzjkHNMQE+aEGDShPg5COT0GMtW5whHCPViCamwiNVtV85RPJ9BsIpAcIQQhAA */
Expand All @@ -112,7 +112,8 @@ export const playgroundMachine =
activeInputIndex: 0,
inputList: initialInputList as any as FileTabData[], // TODO rm with ts 4.9 satisfies
selectedOpenApiFileName: initialInputList[0].name,
selectedTemplateName: initialInputList[1].preset,
selectedTemplateName: initialInputList[1].name,
selectedPrettierConfig: initialInputList[2].name,
presetTemplates: {},
activeOutputTab: initialOuputTab,
activeOutputIndex: 0,
Expand Down Expand Up @@ -156,12 +157,15 @@ export const playgroundMachine =
actions: ["selectInputTab", "updateSelectedTemplateName", "updateOutput"],
cond: "isNextTabAnotherTemplate",
},
{
actions: ["selectInputTab", "updateSelectedPrettierConfig", "updateOutput"],
cond: "isNextTabAnotherPrettierConfig",
},
{ actions: ["selectInputTab"] },
],
"Select output tab": { actions: "selectOutputTab" },
"Select preset template": { actions: ["selectPresetTemplate", "updateOutput"] },
"Open options": { target: "Editing options" },
"Open prettier config": { target: "Editing prettier config" },
"Open monaco settings": { target: "Editing monaco settings" },
"Add file": { target: "Creating file tab", actions: "initFileForm" },
"Edit file": { target: "Editing file tab", actions: "assignFileToForm" },
Expand All @@ -170,6 +174,7 @@ export const playgroundMachine =
"removeFile",
"updateSelectedOpenApiFileName",
"updateSelectedTemplateName",
"updateSelectedPrettierConfig",
"updateOutput",
],
},
Expand Down Expand Up @@ -197,16 +202,10 @@ export const playgroundMachine =
"Close options": { target: "Playing" },
},
},
"Editing prettier config": {
on: {
"Update prettier config": { actions: ["updatePrettierConfig", "updateOutput"] },
"Close modal": { target: "Playing" },
},
},
"Editing monaco settings": {
on: {
// TODO
// "Update prettier config": { actions: "updatePrettierConfig" },
// "Update monaco settings": { actions: "updateMonacoSettings" },
"Close modal": { target: "Playing" },
},
},
Expand All @@ -221,6 +220,7 @@ export const playgroundMachine =
"updateInputEditorValue",
"updateSelectedOpenApiFileName",
"updateSelectedTemplateName",
"updateSelectedPrettierConfig",
"updateOutput",
],
},
Expand All @@ -238,6 +238,7 @@ export const playgroundMachine =
"updateInputEditorValue",
"updateSelectedOpenApiFileName",
"updateSelectedTemplateName",
"updateSelectedPrettierConfig",
"updateOutput",
],
},
Expand Down Expand Up @@ -298,6 +299,7 @@ export const playgroundMachine =

const hbs = getHandlebars();
const templateString = match(ctx.selectedTemplateName)
// TODO ?
.with(initialInputList[1].preset, () => initialInputList[1].content)
.otherwise(() => {
return (
Expand All @@ -311,12 +313,12 @@ export const playgroundMachine =
});
if (!templateString) return ctx;
const template = hbs.compile(templateString);
const prettierConfig = safeJSONParse<PrettierOptions>(
ctx.inputList.find((tab) => tab.name === ctx.selectedPrettierConfig)?.content ?? "{}"
);

// adapted from lib/src/generateZodClientFromOpenAPI.ts:60-120
if (options.groupStrategy.includes("file")) {
// TODO
const prettierConfig = presets.defaultPrettierConfig;

const outputByGroupName: Record<string, string> = {};

const groupNames = Object.fromEntries(
Expand Down Expand Up @@ -386,7 +388,7 @@ export const playgroundMachine =
}

const output = template({ ...templateContext, options });
const prettyOutput = maybePretty(output, presets.defaultPrettierConfig);
const prettyOutput = maybePretty(output, prettierConfig);

if (ctx.outputEditor) {
ctx.outputEditor.setValue(prettyOutput);
Expand Down Expand Up @@ -436,6 +438,23 @@ export const playgroundMachine =
: ctx.selectedTemplateName;
},
}),
updateSelectedPrettierConfig: assign({
selectedPrettierConfig: (ctx, event) => {
if (event.type === "Remove file") {
const nextIndex = ctx.inputList.findIndex((tab) => isValidPrettierConfig(tab.name));
return nextIndex === -1 ? ctx.selectedPrettierConfig : ctx.inputList[nextIndex].name;
}

if (!event.tab.content) return ctx.selectedPrettierConfig;

const nextIndex = ctx.inputList.findIndex((tab) => tab.name === event.tab.name);
if (nextIndex === -1) return ctx.selectedPrettierConfig;

return isValidPrettierConfig(ctx.inputList[nextIndex].name)
? event.tab.name
: ctx.selectedPrettierConfig;
},
}),
updateSelectedDocOrTemplate: assign((ctx, event) => {
const tab = ctx.inputList[ctx.activeInputIndex];

Expand Down Expand Up @@ -502,9 +521,6 @@ export const playgroundMachine =
options: (_ctx, event) => event.options,
previewOptions: (_ctx, event) => event.options,
}),
updatePrettierConfig: assign({
options: (ctx, event) => ({ ...ctx.options, prettier: event.options }),
}),
updateEditingFile: assign({
inputList: (ctx, event) => updateAtIndex(ctx.inputList, ctx.fileForm.index, event.tab),
}),
Expand Down Expand Up @@ -533,6 +549,12 @@ export const playgroundMachine =
const nextIndex = ctx.inputList.findIndex((tab) => tab.name === event.tab.name);
return isValidTemplateName(ctx.inputList[nextIndex].name);
},
isNextTabAnotherPrettierConfig: (ctx, event) => {
if (event.tab.name === ctx.selectedPrettierConfig) return false;

const nextIndex = ctx.inputList.findIndex((tab) => tab.name === event.tab.name);
return isValidPrettierConfig(ctx.inputList[nextIndex].name);
},
wasInputEmpty: (ctx, event) => {
return Boolean(ctx.inputList[ctx.activeInputIndex].content.trim() === "" && event.value);
},
Expand Down
8 changes: 3 additions & 5 deletions playground/src/routes/Playground.machine.typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,17 +39,17 @@ export interface Typegen0 {
| "Select input tab"
| "Select preset template"
| "Submit file modal"
| "Update input"
| "Update prettier config";
updatePrettierConfig: "Update prettier config";
| "Update input";
updatePreviewOptions: "Update preview options";
updateSelectedDocOrTemplate: "Update input";
updateSelectedOpenApiFileName: "Remove file" | "Select input tab" | "Submit file modal";
updateSelectedPrettierConfig: "Remove file" | "Select input tab" | "Submit file modal";
updateSelectedTemplateName: "Remove file" | "Select input tab" | "Submit file modal";
};
eventsCausingServices: {};
eventsCausingGuards: {
isNextTabAnotherOpenApiDoc: "Select input tab";
isNextTabAnotherPrettierConfig: "Select input tab";
isNextTabAnotherTemplate: "Select input tab";
wasInputEmpty: "Update input";
willInputAndOutputEditorBothBeReady: "Editor Loaded";
Expand All @@ -62,15 +62,13 @@ export interface Typegen0 {
| "ready.Editing file tab"
| "ready.Editing monaco settings"
| "ready.Editing options"
| "ready.Editing prettier config"
| "ready.Playing"
| {
ready?:
| "Creating file tab"
| "Editing file tab"
| "Editing monaco settings"
| "Editing options"
| "Editing prettier config"
| "Playing";
};
tags: "file";
Expand Down
22 changes: 9 additions & 13 deletions playground/src/routes/Playground.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import { Field, FormDialog, FormLayout, useFormContext } from "@saas-ui/react";
import { useActor, useSelector } from "@xstate/react";
import type { TemplateContextOptions } from "openapi-zod-client";
import { PropsWithChildren } from "react";
import { match } from "ts-pattern";
import { defaultOptionValues, OptionsForm, OptionsFormValues } from "../components/OptionsForm";
import { SplitPane } from "../components/SplitPane/SplitPane";
import { presetTemplateList } from "./Playground.consts";
Expand Down Expand Up @@ -86,22 +87,18 @@ export const Playground = () => {
}}
>
{inputList.map((fileTab) => {
const isSelectedAsOpenApiDoc =
fileTab.name === state.context.selectedOpenApiFileName;
const isSelectedAsTemplate = fileTab.preset === state.context.selectedTemplateName;
const isSelectedAsInput =
isSelectedAsOpenApiDoc ||
isSelectedAsTemplate ||
fileTab.name === state.context.selectedTemplateName;
const indicator = match(fileTab.name)
.with(state.context.selectedOpenApiFileName, () => "[o]")
.with(state.context.selectedTemplateName, () => "[t]")
.with(state.context.selectedPrettierConfig, () => "[p]")
.otherwise(() => "");

return (
<FileTab
key={fileTab.name}
onClick={() => send({ type: "Select input tab", tab: fileTab })}
>
{isSelectedAsInput ? (
<Box mr="1">{isSelectedAsOpenApiDoc ? "[o]" : "[t]"}</Box>
) : null}
{indicator ? <Box mr="1">{indicator}</Box> : null}
<Box>{fileTab.name}</Box>
<FileTabActions fileTab={fileTab} />
</FileTab>
Expand Down Expand Up @@ -257,7 +254,7 @@ const PlaygroundActions = () => {
<PopoverBody>
<MenuOptionGroup
defaultValue={defaultValue}
title="Template"
title="Template presets"
type="radio"
onChange={(value) =>
send({
Expand All @@ -283,7 +280,6 @@ const PlaygroundActions = () => {
</Popover>
<MenuItem>Use OpenAPI samples</MenuItem>
<MenuItem onClick={() => send({ type: "Open options" })}>Edit lib options</MenuItem>
<MenuItem onClick={() => send({ type: "Open prettier config" })}>Edit prettier config</MenuItem>
<MenuItem onClick={() => send({ type: "Open monaco settings" })}>Edit monaco settings</MenuItem>
<MenuItem as="a" href="https://apis.guru/" target="_blank" rel="external">
Browse APIs.guru
Expand All @@ -306,7 +302,7 @@ const FileTabForm = () => {
title={state.matches("ready.Creating file tab") ? "Add input file" : "Edit input file"}
defaultValues={formModalDefaultValues}
mode="onSubmit"
isOpen={state.matches("ready.Creating file tab") || state.matches("ready.Editing file tab")}
isOpen={state.hasTag("file")}
onClose={() => send({ type: "Close modal" })}
onSubmit={(fileTab) => send({ type: "Submit file modal", tab: fileTab })}
footer={<CreateFileFormFooter />}
Expand Down

0 comments on commit bf68177

Please sign in to comment.