Skip to content

Commit

Permalink
Finalize Create New Story File feature
Browse files Browse the repository at this point in the history
  • Loading branch information
valentinpalkovic committed Apr 20, 2024
1 parent a007256 commit 3cf9846
Show file tree
Hide file tree
Showing 11 changed files with 98 additions and 128 deletions.
50 changes: 23 additions & 27 deletions code/addons/controls/src/manager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,20 @@ addons.register(ADDON_ID, (api) => {
if (data.type !== 'story') throw new Error('Not a story');

try {
const response = await experimental_requestResponse<SaveStoryResponsePayload>(
channel,
SAVE_STORY_REQUEST,
SAVE_STORY_RESPONSE,
{
// Only send updated args
args: stringifyArgs(
Object.entries(data.args || {}).reduce<Args>((acc, [key, value]) => {
if (!deepEqual(value, data.initialArgs?.[key])) acc[key] = value;
return acc;
}, {})
),
csfId: data.id,
importPath: data.importPath,
} satisfies SaveStoryRequestPayload
);
const response = await experimental_requestResponse<
SaveStoryRequestPayload,
SaveStoryResponsePayload
>(channel, SAVE_STORY_REQUEST, SAVE_STORY_RESPONSE, {
// Only send updated args
args: stringifyArgs(
Object.entries(data.args || {}).reduce<Args>((acc, [key, value]) => {
if (!deepEqual(value, data.initialArgs?.[key])) acc[key] = value;
return acc;
}, {})
),
csfId: data.id,
importPath: data.importPath,
});

api.addNotification({
id: 'save-story-success',
Expand Down Expand Up @@ -96,17 +94,15 @@ addons.register(ADDON_ID, (api) => {
const data = api.getCurrentStoryData();
if (data.type !== 'story') throw new Error('Not a story');

const response = await experimental_requestResponse<SaveStoryResponsePayload>(
channel,
SAVE_STORY_REQUEST,
SAVE_STORY_RESPONSE,
{
args: data.args && stringifyArgs(data.args),
csfId: data.id,
importPath: data.importPath,
name,
} satisfies SaveStoryRequestPayload
);
const response = await experimental_requestResponse<
SaveStoryRequestPayload,
SaveStoryResponsePayload
>(channel, SAVE_STORY_REQUEST, SAVE_STORY_RESPONSE, {
args: data.args && stringifyArgs(data.args),
csfId: data.id,
importPath: data.importPath,
name,
});

api.addNotification({
id: 'save-story-success',
Expand Down
4 changes: 3 additions & 1 deletion code/lib/core-events/src/data/argtypes-info.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import type { ArgTypes } from '@storybook/csf';

export interface ArgTypesRequestPayload {}
export interface ArgTypesRequestPayload {
storyId: string;
}

export interface ArgTypesResponsePayload {
argTypes: ArgTypes;
Expand Down
2 changes: 2 additions & 0 deletions code/lib/core-events/src/data/create-new-story.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface CreateNewStoryRequestPayload {
componentExportName: string;
// is default export
componentIsDefaultExport: boolean;
// The amount of exports in the file
componentExportCount: number;
}

export interface CreateNewStoryResponsePayload {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { existsSync } from 'node:fs';
import { getNewStoryFile } from '../utils/get-new-story-file';
import { getStoryId } from '../utils/get-story-id';
import path from 'node:path';
import { getProjectRoot } from '@storybook/core-common';

export function initCreateNewStoryChannel(channel: Channel, options: Options) {
/**
Expand All @@ -29,7 +30,7 @@ export function initCreateNewStoryChannel(channel: Channel, options: Options) {
options
);

const relativeStoryFilePath = path.relative(process.cwd(), storyFilePath);
const relativeStoryFilePath = path.relative(getProjectRoot(), storyFilePath);

if (existsSync(storyFilePath)) {
throw new Error(`Story file already exists at .${path.sep}${relativeStoryFilePath}`);
Expand All @@ -41,7 +42,7 @@ export function initCreateNewStoryChannel(channel: Channel, options: Options) {

channel.emit(CREATE_NEW_STORYFILE_RESPONSE, {
success: true,
id: storyId,
id: data.id,
payload: {
storyId,
storyFilePath: `./${relativeStoryFilePath}`,
Expand Down
12 changes: 7 additions & 5 deletions code/lib/core-server/src/utils/get-new-story-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export async function getNewStoryFile(
componentFilePath,
componentExportName,
componentIsDefaultExport,
componentExportCount,
}: CreateNewStoryRequestPayload,
options: Options
) {
Expand Down Expand Up @@ -54,13 +55,14 @@ export async function getNewStoryFile(
exportedStoryName,
});

const doesStoryFileExist = fs.existsSync(path.join(cwd, storyFileName));
const doesStoryFileExist = fs.existsSync(path.join(cwd, dirname, storyFileName));

const storyFilePath = doesStoryFileExist
? path.join(cwd, dirname, alternativeStoryFileName)
: path.join(cwd, dirname, storyFileName);
const storyFilePath =
doesStoryFileExist && componentExportCount > 1
? path.join(cwd, dirname, alternativeStoryFileName)
: path.join(cwd, dirname, storyFileName);

return { storyFilePath, exportedStoryName, storyFileContent };
return { storyFilePath, exportedStoryName, storyFileContent, dirname };
}

export const getStoryMetadata = (componentFilePath: string) => {
Expand Down
4 changes: 2 additions & 2 deletions code/lib/manager-api/src/lib/request-response.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import type { RequestData, ResponseData } from '@storybook/core-events';
class RequestResponseError extends Error {}

// eslint-disable-next-line @typescript-eslint/naming-convention
export const experimental_requestResponse = <ResponsePayload = void>(
export const experimental_requestResponse = <RequestPayload, ResponsePayload = void>(
channel: Channel,
requestEvent: string,
responseEvent: string,
payload: any,
payload: RequestPayload,
timeout = 5000
): Promise<ResponsePayload> => {
let timeoutId: NodeJS.Timeout;
Expand Down
1 change: 1 addition & 0 deletions code/lib/preview-api/src/modules/preview-web/Preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,7 @@ export class Preview<TRenderer extends Renderer> {
this.channel.emit(ARGTYPES_INFO_RESPONSE, {
id,
success: false,
payload: null,
error: e?.message,
} satisfies ResponseData<ArgTypesResponsePayload>);
}
Expand Down
125 changes: 41 additions & 84 deletions code/ui/manager/src/components/sidebar/CreateNewStoryFileModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ import {
SAVE_STORY_REQUEST,
SAVE_STORY_RESPONSE,
} from '@storybook/core-events';
import { addons, useStorybookApi } from '@storybook/manager-api';
import { addons, experimental_requestResponse, useStorybookApi } from '@storybook/manager-api';

import { useDebounce } from '../../hooks/useDebounce';
import type { NewStoryPayload, SearchResult } from './FileSearchList';
import type { Channel } from '@storybook/channels';
import { extractSeededRequiredArgs, trySelectNewStory } from './FileSearchModal.utils';
import { FileSearchModal } from './FileSearchModal';

Expand All @@ -36,6 +35,14 @@ interface CreateNewStoryFileModalProps {
onOpenChange: (open: boolean) => void;
}

const stringifyArgs = (args: Record<string, any>) =>
stringify(args, {
allowDate: true,
allowFunction: true,
allowUndefined: true,
allowSymbol: true,
});

export const CreateNewStoryFileModal = ({ open, onOpenChange }: CreateNewStoryFileModalProps) => {
const [isLoading, setLoading] = useState(false);
const [fileSearchQuery, setFileSearchQuery] = useState('');
Expand Down Expand Up @@ -125,82 +132,60 @@ export const CreateNewStoryFileModal = ({ open, onOpenChange }: CreateNewStoryFi
componentExportName,
componentFilePath,
componentIsDefaultExport,
componentExportCount,
selectedItemId,
}: NewStoryPayload) => {
try {
const channel = addons.getChannel();

const createNewStoryResult = await oncePromise<
const createNewStoryResult = await experimental_requestResponse<
CreateNewStoryRequestPayload,
CreateNewStoryResponsePayload
>({
channel,
request: {
name: CREATE_NEW_STORYFILE_REQUEST,
payload: {
id: `${selectedItemId}`,
payload: {
componentExportName,
componentFilePath,
componentIsDefaultExport,
},
},
},
resolveEvent: CREATE_NEW_STORYFILE_RESPONSE,
>(channel, CREATE_NEW_STORYFILE_REQUEST, CREATE_NEW_STORYFILE_RESPONSE, {
componentExportName,
componentFilePath,
componentIsDefaultExport,
componentExportCount,
});

if (createNewStoryResult.success) {
setError(null);
setError(null);

const storyId = createNewStoryResult.payload.storyId;
const storyId = createNewStoryResult.storyId;

await trySelectNewStory(api.selectStory, storyId);
await trySelectNewStory(api.selectStory, storyId);

const argTypesInfoResult = await oncePromise<
try {
const argTypesInfoResult = await experimental_requestResponse<
ArgTypesRequestPayload,
ArgTypesResponsePayload
>({
channel,
request: {
name: ARGTYPES_INFO_REQUEST,
payload: { id: storyId, payload: {} },
},
resolveEvent: ARGTYPES_INFO_RESPONSE,
>(channel, ARGTYPES_INFO_REQUEST, ARGTYPES_INFO_RESPONSE, {
storyId,
});

if (argTypesInfoResult.success) {
const argTypes = argTypesInfoResult.payload.argTypes;

const requiredArgs = extractSeededRequiredArgs(argTypes);
const argTypes = argTypesInfoResult.argTypes;

await oncePromise<SaveStoryRequestPayload, SaveStoryResponsePayload>({
channel,
request: {
name: SAVE_STORY_REQUEST,
payload: {
id: storyId,
payload: {
args: stringify(requiredArgs),
importPath: createNewStoryResult.payload.storyFilePath,
csfId: storyId,
},
},
},
resolveEvent: SAVE_STORY_RESPONSE,
});
}
const requiredArgs = extractSeededRequiredArgs(argTypes);

handleSuccessfullyCreatedStory(componentExportName);
handleFileSearch();
} else {
setError({ selectedItemId: selectedItemId, error: createNewStoryResult.error });
}
await experimental_requestResponse<SaveStoryRequestPayload, SaveStoryResponsePayload>(
channel,
SAVE_STORY_REQUEST,
SAVE_STORY_RESPONSE,
{
args: stringifyArgs(requiredArgs),
importPath: createNewStoryResult.storyFilePath,
csfId: storyId,
}
);
} catch (e) {}

handleSuccessfullyCreatedStory(componentExportName);
handleFileSearch();
} catch (e) {
handleErrorWhenCreatingStory();
setError({ selectedItemId: selectedItemId, error: e?.message });
}
},
[
api.selectStory,
api?.selectStory,
handleSuccessfullyCreatedStory,
handleFileSearch,
handleErrorWhenCreatingStory,
Expand Down Expand Up @@ -230,31 +215,3 @@ export const CreateNewStoryFileModal = ({ open, onOpenChange }: CreateNewStoryFi
/>
);
};

interface OncePromiseOptions<Payload> {
channel: Channel;
request: {
name: string;
payload: Payload;
};
resolveEvent: string;
}

function oncePromise<Payload, Result>({
channel,
request,
resolveEvent,
}: OncePromiseOptions<RequestData<Payload>>): Promise<ResponseData<Result>> {
return new Promise((resolve, reject) => {
channel.once(resolveEvent, (data: ResponseData<Result>) => {
resolve(data);
});

channel.emit(request.name, request.payload as Payload);

// If the channel supports error events, you can reject the promise on error
channel.once(resolveEvent, (error: any) => {
reject(error);
});
});
}
2 changes: 2 additions & 0 deletions code/ui/manager/src/components/sidebar/FileSearchList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const FileSearchList = memo(function FileSearchList({
componentFilePath: searchResult.filepath,
componentIsDefaultExport: searchResult.exportedComponents[0].default,
selectedItemId: itemId,
componentExportCount: 1,
});
}
},
Expand All @@ -153,6 +154,7 @@ export const FileSearchList = memo(function FileSearchList({
componentFilePath: searchResult.filepath,
componentIsDefaultExport: component.default,
selectedItemId: id,
componentExportCount: searchResult.exportedComponents.length,
});
},
[onNewStory]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,7 @@ export function extractSeededRequiredArgs(argTypes: ArgTypes) {
break;
case 'union':
case 'enum':
const valueName = (argType.type.value?.[0] as any).name;
if (
'value' in argType.type &&
(valueName === 'string' || valueName === 'number' || valueName === 'boolean')
) {
acc[key] = argType.type.value[0];
}
acc[key] = argType.options?.[0];
break;
case 'array':
acc[key] = [];
Expand Down
13 changes: 13 additions & 0 deletions code/ui/manager/src/components/sidebar/IconSymbols.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Meta, StoryObj } from '@storybook/react';

import { IconSymbols } from './IconSymbols';

const meta = {
component: IconSymbols,
} satisfies Meta<typeof IconSymbols>;

export default meta;

type Story = StoryObj<typeof meta>;

export const Default: Story = {};

0 comments on commit 3cf9846

Please sign in to comment.