Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ function buildRoutes() {
<IndexRoute component={make(() => import('sentry/views/onboarding'))} />
</Route>
<Route
path="/stories/"
path="/stories/:category?/:topic?"
component={make(() => import('sentry/stories/view/index'))}
withOrgPath
/>
Expand Down
8 changes: 5 additions & 3 deletions static/app/stories/view/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import styled from '@emotion/styled';
import {Alert} from 'sentry/components/core/alert';
import LoadingIndicator from 'sentry/components/loadingIndicator';
import {StorySidebar} from 'sentry/stories/view/storySidebar';
import {useStoryRedirect} from 'sentry/stories/view/useStoryRedirect';
import {space} from 'sentry/styles/space';
import {useLocation} from 'sentry/utils/useLocation';
import OrganizationContainer from 'sentry/views/organizationContainer';
Expand All @@ -14,20 +15,21 @@ import {StoryHeader} from './storyHeader';
import {useStoriesLoader, useStoryBookFiles} from './useStoriesLoader';

export default function Stories() {
useStoryRedirect();
const location = useLocation<{name: string; query?: string}>();
const files = useStoryBookFiles();

// If no story is selected, show the landing page stories
const storyFiles = useMemo(() => {
if (!location.query.name) {
if (!(location.state?.storyPath ?? location.query.name)) {
return files.filter(
file =>
file.endsWith('styles/colors.mdx') ||
file.endsWith('styles/typography.stories.tsx')
);
}
return [location.query.name];
}, [files, location.query.name]);
return [location.state?.storyPath ?? location.query.name];
}, [files, location.state?.storyPath, location.query.name]);

const story = useStoriesLoader({files: storyFiles});

Expand Down
10 changes: 7 additions & 3 deletions static/app/stories/view/storyTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export function useStoryTree(
}
) {
const location = useLocation();
const initialName = useRef(location.query.name);
const initialName = useRef(location.state?.storyPath ?? location.query.name);

const tree = useMemo(() => {
const root = new StoryTreeNode('root', '', '');
Expand Down Expand Up @@ -412,7 +412,9 @@ function Folder(props: {node: StoryTreeNode}) {
const [expanded, setExpanded] = useState(props.node.expanded);
const location = useLocation();
const hasActiveChild = useMemo(() => {
const child = props.node.find(n => n.filesystemPath === location.query.name);
const child = props.node.find(
n => n.filesystemPath === (location.state?.storyPath ?? location.query.name)
);
return !!child;
}, [location, props.node]);

Expand Down Expand Up @@ -471,7 +473,9 @@ function File(props: {node: StoryTreeNode}) {
<li>
<FolderLink
to={`/stories/?${query}`}
active={location.query.name === props.node.filesystemPath}
active={
props.node.filesystemPath === (location.state?.storyPath ?? location.query.name)
}
>
{normalizeFilename(props.node.name)}
</FolderLink>
Expand Down
101 changes: 101 additions & 0 deletions static/app/stories/view/useStoryRedirect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {useEffect} from 'react';
import kebabCase from 'lodash/kebabCase';

import {useStoryBookFilesByCategory} from 'sentry/stories/view/storySidebar';
import {useLocation} from 'sentry/utils/useLocation';
import {useNavigate} from 'sentry/utils/useNavigate';

type LegacyStoryQuery = {
name: string;
category?: never;
topic?: never;
};
type NewStoryQuery = {
category: StoryCategory;
topic: string;
name?: never;
};

type StoryQuery = LegacyStoryQuery | NewStoryQuery;

export function useStoryRedirect() {
const location = useLocation<StoryQuery>();
const navigate = useNavigate();
const stories = useStoryBookFilesByCategory();

useEffect(() => {
// If we already have a `storyPath` in state, bail out
if (location.state?.storyPath ?? location.query.name) {
return;
}
if (!location.pathname.startsWith('/stories')) {
return;
}
const story = getStoryMeta(location.query, stories);
if (!story) {
return;
}
if (story.category === 'shared') {
navigate(
{pathname: `/stories/`, search: `?name=${encodeURIComponent(story.path)}`},
{replace: true, state: {storyPath: story.path}}
);
} else {
navigate(
{pathname: `/stories/${story.category}/${kebabCase(story.label)}`},
{replace: true, state: {storyPath: story.path}}
);
}
}, [location, navigate, stories]);
}

type StoryCategory = keyof ReturnType<typeof useStoryBookFilesByCategory>;
interface StoryMeta {
category: StoryCategory;
label: string;
path: string;
}

function getStoryMeta(
query: StoryQuery,
stories: ReturnType<typeof useStoryBookFilesByCategory>
) {
if (query.name) {
return legacyGetStoryMetaFromQuery(query, stories);
}
if (query.category && query.topic) {
return getStoryMetaFromQuery(query, stories);
}
return undefined;
}

function legacyGetStoryMetaFromQuery(
query: LegacyStoryQuery,
stories: ReturnType<typeof useStoryBookFilesByCategory>
): StoryMeta | undefined {
for (const category of Object.keys(stories) as StoryCategory[]) {
const nodes = stories[category];
for (const node of nodes) {
const match = node.find(n => n.filesystemPath === query.name);
if (match) {
return {category, label: match.label, path: match.filesystemPath};
}
}
}
return undefined;
}

function getStoryMetaFromQuery(
query: NewStoryQuery,
stories: ReturnType<typeof useStoryBookFilesByCategory>
): StoryMeta | undefined {
const {category, topic} = query;
const nodes = category in stories ? stories[category] : [];
for (const node of nodes) {
const match = node.find(n => kebabCase(n.label) === topic);
if (match) {
return {category, label: match.label, path: match.filesystemPath};
}
}
return undefined;
}
Loading