Skip to content
Draft
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
1 change: 1 addition & 0 deletions src/providers/TourProvider/TourModeContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { TourDefinition } from "@/components/Learn/tours/registry";
export interface TourModeValue {
tour: TourDefinition;
tempPipelineName: string;
promoteToPipeline: (newName: string, yamlContent: string) => Promise<void>;
}

const TourModeContext = createContext<TourModeValue | null>(null);
Expand Down
35 changes: 35 additions & 0 deletions src/providers/TourProvider/TourPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Icon } from "@/components/ui/icon";
import { BlockStack } from "@/components/ui/layout";
import { Text } from "@/components/ui/typography";
import { cn } from "@/lib/utils";
import { APP_ROUTES } from "@/routes/router";
import { tracking } from "@/utils/tracking";
Expand Down Expand Up @@ -93,6 +94,19 @@ export function computeDefaultPopoverPosition(

type NextButtonProps = Parameters<NonNullable<ProviderProps["nextButton"]>>[0];

let saveExploreHandler: (() => void) | null = null;

export function registerSaveExploreHandler(
handler: (() => void) | null,
): () => void {
saveExploreHandler = handler;
return () => {
if (saveExploreHandler === handler) {
saveExploreHandler = null;
}
};
}

export function TourCompletionActions() {
const navigate = useNavigate();
const { setIsOpen } = useTour();
Expand All @@ -102,6 +116,11 @@ export function TourCompletionActions() {
void navigate({ to: APP_ROUTES.LEARN_TOURS });
};

const onSavePipeline = () => {
setIsOpen(false);
saveExploreHandler?.();
};

return (
<BlockStack gap="3" align="center">
<Button
Expand All @@ -113,6 +132,22 @@ export function TourCompletionActions() {
<Icon name="Check" size="sm" />
Finish Tour
</Button>
{saveExploreHandler && (
<BlockStack align="center">
<Text size="xs" tone="subdued">
Continue exploring:
</Text>
<Button
size="xs"
variant="link"
onClick={onSavePipeline}
{...tracking("v2.pipeline_editor.tour.save_as_pipeline")}
>
<Icon name="SaveAll" size="xs" />
Save demo pipeline
</Button>
</BlockStack>
)}
</BlockStack>
);
}
Expand Down
43 changes: 43 additions & 0 deletions src/providers/TourProvider/TourSaveExploreDialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { useEffect, useState } from "react";

import { PipelineNameDialog } from "@/components/shared/Dialogs";
import { serializeComponentSpecToText } from "@/models/componentSpec";
import { useTourMode } from "@/providers/TourProvider/TourModeContext";
import { registerSaveExploreHandler } from "@/providers/TourProvider/TourPopover";
import { usePipelineActions } from "@/routes/v2/pages/Editor/store/actions/usePipelineActions";
import { useSharedStores } from "@/routes/v2/shared/store/SharedStoreContext";

export function TourSaveExploreDialog() {
const tourMode = useTourMode();
const { navigation } = useSharedStores();
const { renamePipeline } = usePipelineActions();
const [open, setOpen] = useState(false);

useEffect(() => {
if (!tourMode) return;
return registerSaveExploreHandler(() => setOpen(true));
}, [tourMode]);

if (!tourMode) return null;

const onSubmit = async (name: string) => {
const rootSpec = navigation.rootSpec;
if (!rootSpec) return;

renamePipeline(rootSpec, name);
const yamlContent = serializeComponentSpecToText(rootSpec);
await tourMode.promoteToPipeline(name, yamlContent);
};

return (
<PipelineNameDialog
open={open}
onOpenChange={setOpen}
title="Save pipeline"
description="Convert this demo pipeline into a regular pipeline you can keep editing."
initialName={tourMode.tour.displayName ?? tourMode.tour.id}
onSubmit={onSubmit}
submitButtonText="Save"
/>
);
}
33 changes: 31 additions & 2 deletions src/routes/Dashboard/Learn/Tour.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ import {
getTour,
type TourDefinition,
} from "@/components/Learn/tours/registry";
import useToastNotification from "@/hooks/useToastNotification";
import { TourContent } from "@/providers/TourProvider/TourContent";
import { TourModeProvider } from "@/providers/TourProvider/TourModeContext";
import {
TourModeProvider,
type TourModeValue,
} from "@/providers/TourProvider/TourModeContext";
import {
buildTourPipelineYaml,
TOUR_PIPELINE_PREFIX,
Expand Down Expand Up @@ -142,24 +146,48 @@ export function TourPage() {
? params.tourId
: "";
const tour = getTour(tourId);
const navigate = useNavigate();
const storage = usePipelineStorage();
const notify = useToastNotification();

const promoteToPipeline = async (newName: string, yamlContent: string) => {
try {
const file = await storage.rootFolder.addFile(newName, yamlContent);
await navigate({
to: APP_ROUTES.EDITOR_V2_PIPELINE,
params: { pipelineName: newName },
search: { fileId: file.id },
});
} catch (error) {
const message =
error instanceof Error ? error.message : "Failed to save pipeline";
notify(message, "error");
}
};

if (!tour) {
return <Navigate to={APP_ROUTES.LEARN_TOURS} replace />;
}

return (
<TourPipelineStorageProvider>
<TourPageBody tour={tour} tourId={tourId} />
<TourPageBody
tour={tour}
tourId={tourId}
promoteToPipeline={promoteToPipeline}
/>
</TourPipelineStorageProvider>
);
}

function TourPageBody({
tour,
tourId,
promoteToPipeline,
}: {
tour: TourDefinition;
tourId: string;
promoteToPipeline: TourModeValue["promoteToPipeline"];
}) {
const search = useSearch({ strict: false });
const navigate = useNavigate();
Expand Down Expand Up @@ -212,6 +240,7 @@ function TourPageBody({
value={{
tour,
tempPipelineName: resolved?.name ?? tourPipelineName(tour),
promoteToPipeline,
}}
>
{resolved && (
Expand Down
2 changes: 2 additions & 0 deletions src/routes/v2/pages/Editor/EditorV2.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { ComponentLibraryProvider } from "@/providers/ComponentLibraryProvider";
import { ForcedSearchProvider } from "@/providers/ComponentLibraryProvider/ForcedSearchProvider";
import { DialogProvider } from "@/providers/DialogProvider/DialogProvider";
import { useTourMode } from "@/providers/TourProvider/TourModeContext";
import { TourSaveExploreDialog } from "@/providers/TourProvider/TourSaveExploreDialog";
import { useDockAreaAccordion } from "@/routes/v2/shared/hooks/useDockAreaAccordion";
import { useFocusMode } from "@/routes/v2/shared/hooks/useFocusMode";
import { NodeRegistryProvider } from "@/routes/v2/shared/nodes/NodeRegistryContext";
Expand Down Expand Up @@ -151,6 +152,7 @@ function EditorV2Content({ pipelineRef }: { pipelineRef: PipelineRef | null }) {
<ReactFlowProvider>
<EditorMenuBar />
<EditorTourBridge />
<TourSaveExploreDialog />
<ForcedSearchProvider>{body}</ForcedSearchProvider>
</ReactFlowProvider>
</ComponentLibraryProvider>
Expand Down
Loading