Skip to content

Commit

Permalink
feat: add param for prefill sandbox template (#8390)
Browse files Browse the repository at this point in the history
* feat: use preselected sandbox for the fork modal

* error handling

* update template data structure and create mapping functions

* fix typecheck
  • Loading branch information
alexnm committed Mar 18, 2024
1 parent d84588e commit b211a7d
Show file tree
Hide file tree
Showing 17 changed files with 484 additions and 339 deletions.
498 changes: 262 additions & 236 deletions packages/app/src/app/components/Create/CreateBox.tsx

Large diffs are not rendered by default.

25 changes: 11 additions & 14 deletions packages/app/src/app/components/Create/CreateBox/TemplateInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { getTemplateIcon } from '@codesandbox/common/lib/utils/getTemplateIcon';
import { Stack, Text, Icon } from '@codesandbox/components';
import { formatNumber } from '@codesandbox/components/lib/components/Stats';
import { TemplateFragment } from 'app/graphql/types';
import React from 'react';
import { SandboxToFork } from '../utils/types';

interface TemplateInfoProps {
template: TemplateFragment;
template: SandboxToFork;
}

export const TemplateInfo = ({ template }: TemplateInfoProps) => {
const { UserIcon } = getTemplateIcon(
template.sandbox.title,
template.title,
template.iconUrl,
template.sandbox?.source?.template
template.sourceTemplate
);

const title = template.sandbox.title || template.sandbox.alias;
const author =
template.sandbox?.team?.name ||
template.sandbox?.author?.username ||
'CodeSandbox';
const title = template.title || template.alias;
const owner = template.owner;

return (
<Stack direction="vertical" gap={4}>
Expand All @@ -28,23 +25,23 @@ export const TemplateInfo = ({ template }: TemplateInfoProps) => {
<Text size={3} weight="500">
{title}
</Text>
{author && (
{owner && (
<Text size={3} css={{ color: '#999' }}>
{author}
{owner}
</Text>
)}
</Stack>
<Text size={3} css={{ color: '#999', lineHeight: '1.4' }}>
{template.sandbox.description}
{template.description}
</Text>
<Stack gap={3} css={{ color: '#999' }}>
<Stack gap={1}>
<Icon name="eye" size={14} />
<Text size={2}>{formatNumber(template.sandbox.viewCount)}</Text>
<Text size={2}>{formatNumber(template.viewCount)}</Text>
</Stack>
<Stack gap={1}>
<Icon name="fork" size={14} />
<Text size={2}>{formatNumber(template.sandbox.forkCount)}</Text>
<Text size={2}>{formatNumber(template.forkCount)}</Text>
</Stack>
</Stack>
</Stack>
Expand Down
20 changes: 8 additions & 12 deletions packages/app/src/app/components/Create/TemplateCard.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import React from 'react';
import { formatNumber, Icon, Stack, Text } from '@codesandbox/components';
import { getTemplateIcon } from '@codesandbox/common/lib/utils/getTemplateIcon';
import { TemplateFragment } from 'app/graphql/types';
import { VisuallyHidden } from 'reakit/VisuallyHidden';
import { TemplateButton } from './elements';
import { SandboxToFork } from './utils/types';

interface TemplateCardProps {
disabled?: boolean;
template: TemplateFragment;
onSelectTemplate: (template: TemplateFragment) => void;
onOpenTemplate: (template: TemplateFragment) => void;
template: SandboxToFork;
onSelectTemplate: (template: SandboxToFork) => void;
onOpenTemplate: (template: SandboxToFork) => void;
padding?: number | string;
forks?: number;
}
Expand All @@ -23,17 +23,13 @@ export const TemplateCard = ({
forks,
}: TemplateCardProps) => {
const { UserIcon } = getTemplateIcon(
template.sandbox.title,
template.title,
template.iconUrl,
template.sandbox?.source?.template
template.sourceTemplate
);

const sandboxTitle = template.sandbox?.title || template.sandbox?.alias;

const teamName =
template.sandbox?.team?.name ||
template.sandbox?.author?.username ||
'CodeSandbox';
const sandboxTitle = template.title || template.alias;
const teamName = template.owner;

return (
<TemplateButton
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/app/components/Create/TemplateList.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
import React from 'react';
import { Text, Stack } from '@codesandbox/components';
import { useActions } from 'app/overmind';
import { TemplateFragment } from 'app/graphql/types';
import track from '@codesandbox/common/lib/utils/analytics';
import { TemplateCard } from './TemplateCard';
import {
DevboxAlternative,
SandboxAlternative,
TemplateGrid,
} from './elements';
import { SandboxToFork } from './utils/types';

interface TemplateListProps {
title: string;
showEmptyState?: boolean;
searchQuery?: string;
type: 'sandbox' | 'devbox';
templates: TemplateFragment[];
onSelectTemplate: (template: TemplateFragment) => void;
onOpenTemplate: (template: TemplateFragment) => void;
templates: SandboxToFork[];
onSelectTemplate: (template: SandboxToFork) => void;
onOpenTemplate: (template: SandboxToFork) => void;
}

export const TemplateList = ({
Expand Down
13 changes: 6 additions & 7 deletions packages/app/src/app/components/Create/hooks/useAllTemplates.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { TemplateFragment } from 'app/graphql/types';
import { TemplateCollection } from '../utils/types';
import { SandboxToFork, TemplateCollection } from '../utils/types';

interface UseAllTemplatesParams {
searchQuery?: string;
featuredTemplates: TemplateFragment[];
officialTemplates: TemplateFragment[];
teamTemplates: TemplateFragment[];
featuredTemplates: SandboxToFork[];
officialTemplates: SandboxToFork[];
teamTemplates: SandboxToFork[];
collections: TemplateCollection[];
}

Expand All @@ -17,7 +16,7 @@ export const useAllTemplates = ({
searchQuery,
}: UseAllTemplatesParams) => {
// Using a map to ensure unique entries for templates
const allTemplatesMap: Map<string, TemplateFragment> = new Map();
const allTemplatesMap: Map<string, SandboxToFork> = new Map();

featuredTemplates.forEach(t => {
allTemplatesMap.set(t.id, t);
Expand All @@ -39,7 +38,7 @@ export const useAllTemplates = ({

return Array.from(allTemplatesMap.values()).filter(t =>
searchQuery
? (t.sandbox.title || t.sandbox.alias || '')
? (t.title || t.alias || '')
.toLowerCase()
.includes(searchQuery.trim().toLowerCase())
: true
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { TemplateFragment } from 'app/graphql/types';
import { SandboxToFork } from '../utils/types';

interface UseFeaturedTemplatesParams {
recentTemplates: TemplateFragment[];
officialTemplates: TemplateFragment[];
recentTemplates: SandboxToFork[];
officialTemplates: SandboxToFork[];
}

const FEATURED_IDS = [
Expand All @@ -28,8 +28,8 @@ export const useFeaturedTemplates = ({
const featuredOfficialTemplates = FEATURED_IDS.map(
id =>
// If the template is already in recently used, don't add it twice
!recentlyUsedTemplates.find(t => t.sandbox.id === id) &&
officialTemplates.find(t => t.sandbox.id === id)
!recentlyUsedTemplates.find(t => t.id === id) &&
officialTemplates.find(t => t.id === id)
).filter(Boolean);

return featuredOfficialTemplates.slice(0, hasRecentlyUsedTemplates ? 6 : 9);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
import { TemplateFragment } from 'app/graphql/types';
import { useEffect, useState } from 'react';
import { useEffect, useState } from 'react';
import { useAppState } from 'app/overmind';
import { getTemplateInfosFromAPI } from '../utils/api';
import { SandboxToFork } from '../utils/types';

type State =
| {
state: 'loading';
templates: TemplateFragment[];
templates: SandboxToFork[];
}
| {
state: 'ready';
templates: TemplateFragment[];
templates: SandboxToFork[];
}
| {
state: 'error';
templates: TemplateFragment[];
templates: SandboxToFork[];
};

export const useOfficialTemplates = ({
Expand Down Expand Up @@ -55,8 +55,8 @@ export const useOfficialTemplates = ({

return {
state: officialTemplatesData.state,
templates: officialTemplatesData.templates.filter(t =>
type === 'sandbox' && !t.sandbox.isV2 || type === 'devbox' && t.sandbox.isV2
templates: officialTemplatesData.templates.filter(
t => (type === 'sandbox' && !t.isV2) || (type === 'devbox' && t.isV2)
),
};
};
14 changes: 10 additions & 4 deletions packages/app/src/app/components/Create/hooks/useTeamTemplates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import {
TemplateFragment,
} from 'app/graphql/types';
import { FETCH_TEAM_TEMPLATES } from '../utils/queries';
import { SandboxToFork } from '../utils/types';
import { mapTemplateGQLResponseToSandboxToFork } from '../utils/api';

type BaseState = {
recentTemplates: TemplateFragment[];
teamTemplates: TemplateFragment[];
recentTemplates: SandboxToFork[];
teamTemplates: SandboxToFork[];
};

type State = BaseState &
Expand Down Expand Up @@ -82,7 +84,11 @@ export const useTeamTemplates = ({

return {
state: 'ready',
recentTemplates: data.me.recentlyUsedTemplates.filter(respectBoxType),
teamTemplates: data.me.team.templates.filter(respectBoxType),
recentTemplates: data.me.recentlyUsedTemplates
.filter(respectBoxType)
.map(mapTemplateGQLResponseToSandboxToFork),
teamTemplates: data.me.team.templates
.filter(respectBoxType)
.map(mapTemplateGQLResponseToSandboxToFork),
};
};
90 changes: 56 additions & 34 deletions packages/app/src/app/components/Create/utils/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { TemplateType } from '@codesandbox/common/lib/templates';
import { isServer } from '@codesandbox/common/lib/templates/helpers/is-server';
import { TemplateCollection } from './types';
import {
GetSandboxWithTemplateQuery,
TemplateFragment,
} from 'app/graphql/types';
import { SandboxToFork, TemplateCollection } from './types';

interface ExploreTemplateAPIResponse {
title: string;
Expand Down Expand Up @@ -43,42 +46,61 @@ const mapAPIResponseToTemplateInfo = (
key: exploreTemplate.title,
title: exploreTemplate.title,
templates: exploreTemplate.sandboxes.map(sandbox => ({
id: sandbox.custom_template.id,
color: sandbox.custom_template.color,
id: sandbox.id,
alias: sandbox.alias,
title: sandbox.title,
description: sandbox.description,
insertedAt: sandbox.inserted_at,
updatedAt: sandbox.updated_at,
isV2: sandbox.v2 || false,
forkCount: sandbox.fork_count,
viewCount: sandbox.view_count,
iconUrl: sandbox.custom_template.icon_url,
published: true,
sandbox: {
id: sandbox.id,
insertedAt: sandbox.inserted_at,
updatedAt: sandbox.updated_at,
alias: sandbox.alias,
title: sandbox.title,
author: sandbox.author,
description: sandbox.description,
source: {
template: sandbox.environment,
},
// TODO: Update /official and /essential endpoints to return
// team -> name instead of collection -> team -> name
team: {
name: 'CodeSandbox',
},
isV2: sandbox.v2 || false,
isSse: isServer(sandbox.environment),
forkCount: sandbox.fork_count,
viewCount: sandbox.view_count,
git: sandbox.git && {
id: sandbox.git.id,
username: sandbox.git.username,
commitSha: sandbox.git.commit_sha,
path: sandbox.git.path,
repo: sandbox.git.repo,
branch: sandbox.git.branch,
},
},
sourceTemplate: sandbox.environment,
owner: 'CodeSandbox',
})),
});

export const mapTemplateGQLResponseToSandboxToFork = (
template: TemplateFragment
): SandboxToFork | null => {
if (!template.sandbox) {
return null;
}

return {
id: template.sandbox.id,
alias: template.sandbox.alias,
title: template.sandbox.title,
description: template.sandbox.description,
insertedAt: template.sandbox.insertedAt,
updatedAt: template.sandbox.updatedAt,
isV2: template.sandbox.isV2 || false,
forkCount: template.sandbox.forkCount,
viewCount: template.sandbox.viewCount,
iconUrl: template.iconUrl || undefined,
sourceTemplate: template.sandbox.source?.template || undefined,
owner: template.sandbox.team?.name || 'CodeSandbox',
};
};

export const mapSandboxGQLResponseToSandboxToFork = (
sandbox: NonNullable<GetSandboxWithTemplateQuery['sandbox']>
): SandboxToFork => ({
id: sandbox.id,
alias: sandbox.alias,
title: sandbox.title,
description: sandbox.description,
insertedAt: sandbox.insertedAt,
updatedAt: sandbox.updatedAt,
isV2: sandbox.isV2 || false,
forkCount: sandbox.forkCount,
viewCount: sandbox.viewCount,
iconUrl: sandbox.customTemplate?.iconUrl || undefined,
sourceTemplate: sandbox.source?.template || undefined,
owner: sandbox.team?.name || 'CodeSandbox',
});

export const getTemplateInfosFromAPI = (
url: string
): Promise<TemplateCollection[]> =>
Expand Down
19 changes: 17 additions & 2 deletions packages/app/src/app/components/Create/utils/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TemplateFragment, GetGithubRepoQuery } from 'app/graphql/types';
import { GetGithubRepoQuery } from 'app/graphql/types';

export type CreateParams = {
name?: string;
Expand All @@ -8,10 +8,25 @@ export type CreateParams = {
customVMTier?: number;
};

export type SandboxToFork = {
id: string;
alias: string | null;
title: string | null;
description: string | null;
insertedAt: string;
updatedAt: string;
isV2: boolean;
forkCount: number;
viewCount: number;
iconUrl?: string;
sourceTemplate?: string;
owner: string;
};

export interface TemplateCollection {
title?: string;
key: string;
templates: TemplateFragment[];
templates: SandboxToFork[];
isOwned?: boolean;
}

Expand Down
Loading

0 comments on commit b211a7d

Please sign in to comment.