diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/components/svg.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/components/svg.tsx index 4359b39180e36..7a62b529d5b21 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/components/svg.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/components/svg.tsx @@ -136,7 +136,7 @@ export const PLAY_ICON = ( width={21} height={21} rx={10.5} - stroke="#3E515F" + stroke="currentColor" strokeOpacity={0.1} strokeWidth={3} /> @@ -164,6 +164,25 @@ export const ARROW_UP_ICON = ( ) +/** An icon representing creation of an item. */ +export const CIRCLED_PLUS_ICON = ( + + + +) + /** Icon with three bars. */ export const BARS_ICON = ( diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx index 58cf40e6094b5..80ef19287bbe1 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/dashboard.tsx @@ -18,6 +18,7 @@ import PermissionDisplay, * as permissionDisplay from './permissionDisplay' import Ide from './ide' import ProjectActionButton from './projectActionButton' import Rows from './rows' +import Templates from './templates' import TopBar from './topBar' // ============= @@ -232,13 +233,6 @@ function Dashboard(props: DashboardProps) { const directory = directoryStack[directoryStack.length - 1] const parentDirectory = directoryStack[directoryStack.length - 2] - // The purpose of this effect is to enable search action. - react.useEffect(() => { - return () => { - // TODO - } - }, [searchVal]) - /** React components for the name column. */ const nameRenderers: { [Type in backend.AssetType]: (asset: backend.Asset) => JSX.Element @@ -329,6 +323,63 @@ function Dashboard(props: DashboardProps) { })() }, [accessToken, directoryId]) + const getNewProjectName = (templateName?: string | null): string => { + const prefix = `${templateName ?? 'New_Project'}_` + const projectNameTemplate = new RegExp(`^${prefix}(?\\d+)$`) + let highestProjectIndex = 0 + for (const projectAsset of projectAssets) { + let projectIndex = projectNameTemplate.exec(projectAsset.title)?.groups?.projectIndex + if (projectIndex) { + highestProjectIndex = Math.max(highestProjectIndex, parseInt(projectIndex, 10)) + } + } + return `${prefix}${highestProjectIndex + 1}` + } + + const handleCreateProject = async (templateName?: string | null) => { + const projectName = getNewProjectName(templateName) + switch (platform) { + case platformModule.Platform.cloud: { + const body: backend.CreateProjectRequestBody = { + projectName, + } + if (templateName) { + body.projectTemplateName = templateName.replace(/_/g, '').toLocaleLowerCase() + } + const projectAsset = await backendService.createProject(body) + setProjectAssets(oldProjectAssets => [ + ...oldProjectAssets, + { + type: backend.AssetType.project, + title: projectAsset.name, + id: projectAsset.projectId, + parentId: '', + permissions: [], + }, + ]) + break + } + case platformModule.Platform.desktop: { + const result = await props.projectManager.createProject({ + name: newtype.asNewtype(projectName), + ...(templateName ? { projectTemplate: templateName } : {}), + }) + const newProject = result.result + setProjectAssets(oldProjectAssets => [ + ...oldProjectAssets, + { + type: backend.AssetType.project, + title: projectName, + id: newProject.projectId, + parentId: '', + permissions: [], + }, + ]) + break + } + } + } + return (
@@ -345,9 +396,7 @@ function Dashboard(props: DashboardProps) { searchVal={searchVal} setSearchVal={setSearchVal} /> - {/* This is a placeholder. When implementing a feature, - * please replace it with the actual element.*/} -
+

Drive

diff --git a/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/templates.tsx b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/templates.tsx new file mode 100644 index 0000000000000..01cb40de01ddb --- /dev/null +++ b/app/ide-desktop/lib/dashboard/src/authentication/src/dashboard/components/templates.tsx @@ -0,0 +1,133 @@ +/** @file Renders the list of templates from which a project can be created. */ +import * as svg from '../../components/svg' + +// ================= +// === Constants === +// ================= + +/** + * Dash border spacing is not supported by native CSS. + * Therefore, use a background image to create the border. + * It is essentially an SVG image that was generated by the website. + * @see {@link https://kovart.github.io/dashed-border-generator} + */ +const BORDER = `url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='16' ry='16' stroke='%233e515f' stroke-width='4' stroke-dasharray='15%2c 15' stroke-dashoffset='0' stroke-linecap='butt'/%3e%3c/svg%3e")` + +// ================= +// === Templates === +// ================= + +/** Template metadata. */ +interface Template { + title: string + description: string + id: string +} + +/** All templates for creating projects that have contents. */ +const TEMPLATES: Template[] = [ + { + title: 'Colorado COVID', + id: 'Colorado_COVID', + description: 'Learn to glue multiple spreadsheets to analyses all your data at once.', + }, + { + title: 'KMeans', + id: 'Kmeans', + description: 'Learn where to open a coffee shop to maximize your income.', + }, + { + title: 'NASDAQ Returns', + id: 'NASDAQ_Returns', + description: 'Learn how to clean your data to prepare it for advanced analysis.', + }, + { + title: 'Restaurants', + id: 'Orders', + description: 'Learn how to clean your data to prepare it for advanced analysis.', + }, + { + title: 'Github Stars', + id: 'Stargazers', + description: 'Learn how to clean your data to prepare it for advanced analysis.', + }, +] + +// ======================= +// === TemplatesRender === +// ======================= + +/** Render all templates, and a button to create an empty project. */ +interface TemplatesRenderProps { + // Later this data may be requested and therefore needs to be passed dynamically. + templates: Template[] + onTemplateClick: (name?: string | null) => void +} + +function TemplatesRender(props: TemplatesRenderProps) { + const { templates, onTemplateClick } = props + + /** The action button for creating an empty project. */ + const CreateEmptyTemplate = ( + +

New empty project

+
+
+ + ) + + return ( + <> + {CreateEmptyTemplate} + {templates.map(template => ( + + ))} + + ) +} + +// ================= +// === Templates === +// ================= + +/** The `TemplatesRender`'s container. */ +interface TemplatesProps { + onTemplateClick: (name?: string | null) => void +} + +function Templates(props: TemplatesProps) { + const { onTemplateClick } = props + return ( +
+
+
+ +
+
+
+ ) +} +export default Templates diff --git a/app/ide-desktop/lib/dashboard/src/tailwind.css b/app/ide-desktop/lib/dashboard/src/tailwind.css index 7de975659e94f..6d55769518cd8 100644 --- a/app/ide-desktop/lib/dashboard/src/tailwind.css +++ b/app/ide-desktop/lib/dashboard/src/tailwind.css @@ -69,5 +69,9 @@ body { .dasharray-100 { stroke-dasharray: calc(12 * 6.2832) 0; } + + .border-dashed-custom { + background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='16' ry='16' stroke='%233e515f' stroke-width='4' stroke-dasharray='15%2c 15' stroke-dashoffset='0' stroke-linecap='butt'/%3e%3c/svg%3e"); + } } }