Skip to content

Commit

Permalink
Fix new project wizard for web (#5051)
Browse files Browse the repository at this point in the history
Switch to using URI for location to preserve scheme
  • Loading branch information
timtmok authored Oct 17, 2024
1 parent f45f9ca commit e8be5d2
Show file tree
Hide file tree
Showing 9 changed files with 44 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,25 @@ export async function checkIfPathExists(path: string | number, fileService: IFil
return undefined;
}

/**
* Check if the current URI exists. For use with Positron web.
*
* @see `checkIfPathValid` `useDebouncedValidator` `LabeledTextInput` `LabeledFolderInput`
* @returns Promise with error message if path doesn't exist or undefined if it does.
*/
export async function checkIfURIExists(path: URI, fileService: IFileService): Promise<string | undefined> {
try {
const pathExists = await fileService.exists(path);

if (!pathExists) {
return localize('pathDoesNotExistError', "The path {0} does not exist.", sanitizePathForDisplay(path.path));
}
} catch (e) {
return localize('errorCheckingIfPathExists', "An error occurred while checking if the path {0} exists.", sanitizePathForDisplay(path.path));
}

return undefined;
}

/**
* Helper function to print paths in a more readable format.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface LabeledTextInputProps {
* Custom error message. Will override the validator error message if present.
*/
errorMsg?: string;
validator?: ValidatorFn;
validator?: ValidatorFn<string | number>;
onChange: ChangeEventHandler<HTMLInputElement>;
/**
* Maximum allowed number of characters in the input field.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

import * as React from 'react';

export type ValidatorFn = (value: string | number) => (string | undefined) | Promise<string | undefined>;
export type ValidatorFn<T> = (value: T) => (string | undefined) | Promise<string | undefined>;

/**
* A hook to debounce the validation of input values.
* @param validator The function to validate the input value. Can be synchronous or asynchronous.
*/
export function useDebouncedValidator({ validator, value, debounceDelayMs = 100 }: { validator?: ValidatorFn; value: string | number; debounceDelayMs?: number }) {
export function useDebouncedValidator<T>({ validator, value, debounceDelayMs = 100 }: { validator?: ValidatorFn<T>; value: T; debounceDelayMs?: number }) {
const [errorMsg, setErrorMsg] = React.useState<string | undefined>(undefined);

const callbackTimeoutRef = React.useRef<NodeJS.Timeout | undefined>();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ const NewFolderModalDialog = (props: NewFolderModalDialogProps) => {
autoFocus
value={result.folder}
onChange={e => setResult({ ...result, folder: e.target.value })}
validator={x => checkIfPathValid(x, { parentPath: result.parentFolder })}
validator={(x: string | number) => checkIfPathValid(x, { parentPath: result.parentFolder })}
/>
<LabeledFolderInput
label={(() => localize(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import { WizardFormattedText, WizardFormattedTextType } from 'vs/workbench/brows
import { checkProjectName } from 'vs/workbench/browser/positronNewProjectWizard/utilities/projectNameUtils';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { NewProjectType } from 'vs/workbench/services/positronNewProject/common/positronNewProject';
import { checkIfPathExists, checkIfPathValid } from 'vs/workbench/browser/positronComponents/positronModalDialog/components/fileInputValidators';
import { checkIfPathValid, checkIfURIExists } from 'vs/workbench/browser/positronComponents/positronModalDialog/components/fileInputValidators';
import { PathDisplay } from 'vs/workbench/browser/positronNewProjectWizard/components/pathDisplay';
import { useDebouncedValidator } from 'vs/workbench/browser/positronComponents/positronModalDialog/components/useDebouncedValidator';

Expand All @@ -46,12 +46,12 @@ export const ProjectNameLocationStep = (props: PropsWithChildren<NewProjectWizar
// function.
const nameValidationErrorMsg = useDebouncedValidator({
value: projectName,
validator: x => checkIfPathValid(x, { parentPath: parentFolder })
validator: x => checkIfPathValid(x, { parentPath: parentFolder.fsPath })
});
const isInvalidName = nameValidationErrorMsg !== undefined;
const parentPathErrorMsg = useDebouncedValidator({
value: parentFolder,
validator: (path: string | number) => checkIfPathExists(path, fileService)
validator: (path: URI) => checkIfURIExists(path, fileService)
});
const isInvalidParentPath = parentPathErrorMsg !== undefined;

Expand All @@ -75,15 +75,15 @@ export const ProjectNameLocationStep = (props: PropsWithChildren<NewProjectWizar
const browseHandler = async () => {
// Show the open dialog.
const uri = await fileDialogService.showOpenDialog({
defaultUri: URI.file(parentFolder),
defaultUri: parentFolder,
canSelectFiles: false,
canSelectFolders: true,
canSelectMany: false,
});

// If the user made a selection, set the parent directory.
if (uri?.length) {
onChangeParentFolder(uri[0].fsPath);
onChangeParentFolder(uri[0]);
}
};

Expand All @@ -99,7 +99,7 @@ export const ProjectNameLocationStep = (props: PropsWithChildren<NewProjectWizar
};

// Update the parent folder and the project name feedback.
const onChangeParentFolder = async (folder: string) => {
const onChangeParentFolder = async (folder: URI) => {
context.parentFolder = folder;
context.projectNameFeedback = await checkProjectName(
projectName,
Expand Down Expand Up @@ -181,7 +181,7 @@ export const ProjectNameLocationStep = (props: PropsWithChildren<NewProjectWizar
onChange={(e) => onChangeProjectName(e.target.value)}
type='text'
// Don't let the user create a project with a location that is too long.
maxLength={255 - parentFolder.length}
maxLength={255 - parentFolder.fsPath.length}
error={
(projectNameFeedback &&
projectNameFeedback.type === WizardFormattedTextType.Error) ||
Expand All @@ -207,7 +207,7 @@ export const ProjectNameLocationStep = (props: PropsWithChildren<NewProjectWizar
"Your project will be created at: "
))()}
<PathDisplay
pathComponents={[parentFolder, projectName]}
pathComponents={[parentFolder.fsPath, projectName]}
pathService={pathService}
/>
</WizardFormattedText>
Expand All @@ -219,11 +219,11 @@ export const ProjectNameLocationStep = (props: PropsWithChildren<NewProjectWizar
'projectNameLocationSubStep.parentDirectory.description',
"Select a directory to create your project in"
))()}
value={parentFolder}
value={parentFolder.fsPath}
onBrowse={browseHandler}
error={Boolean(parentPathErrorMsg)}
skipValidation
onChange={(e) => onChangeParentFolder(e.target.value)}
onChange={(e) => onChangeParentFolder(parentFolder.with({ path: e.target.value }))}
/>
</PositronWizardSubStep>
<PositronWizardSubStep
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ export const PythonEnvironmentStep = (props: PropsWithChildren<NewProjectWizardS
maxLength={65}
pathComponents={
locationForNewEnv(
context.parentFolder,
context.parentFolder.path,
context.projectName,
envProviderNameForId(envProviderId, envProviders!)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { IRuntimeStartupService } from 'vs/workbench/services/runtimeStartup/com
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { PositronModalReactRenderer } from 'vs/workbench/browser/positronModalReactRenderer/positronModalReactRenderer';
import { PositronModalDialog } from 'vs/workbench/browser/positronComponents/positronModalDialog/positronModalDialog';
import { URI } from 'vs/base/common/uri';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { IFileService } from 'vs/platform/files/common/files';
import { ICommandService } from 'vs/platform/commands/common/commands';
Expand Down Expand Up @@ -74,14 +73,14 @@ export const showNewProjectModalDialog = async (
runtimeSessionService,
runtimeStartupService,
}}
parentFolder={(await fileDialogService.defaultFolderPath()).fsPath}
parentFolder={await fileDialogService.defaultFolderPath()}
initialStep={NewProjectWizardStep.ProjectTypeSelection}
>
<NewProjectModalDialog
renderer={renderer}
createProject={async result => {
// Create the new project folder if it doesn't already exist.
const folder = URI.file((await pathService.path).join(result.parentFolder, result.projectName));
const folder = result.parentFolder.with({ path: (await pathService.path).join(result.parentFolder.fsPath, result.projectName) });
const existingFolder = await fileService.exists(folder);
if (!existingFolder) {
await fileService.createFolder(folder);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { WizardFormattedTextItem } from 'vs/workbench/browser/positronNewProject
import { LanguageIds, NewProjectType } from 'vs/workbench/services/positronNewProject/common/positronNewProject';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { CondaPythonVersionInfo, EMPTY_CONDA_PYTHON_VERSION_INFO } from 'vs/workbench/browser/positronNewProjectWizard/utilities/condaUtils';
import { URI } from 'vs/base/common/uri';

/**
* NewProjectWizardServices interface.
Expand All @@ -49,7 +50,7 @@ interface NewProjectWizardServices {
*/
export interface NewProjectWizardStateConfig {
readonly services: NewProjectWizardServices;
readonly parentFolder: string;
readonly parentFolder: URI;
readonly initialStep: NewProjectWizardStep;
readonly steps?: NewProjectWizardStep[];
}
Expand All @@ -62,7 +63,7 @@ export interface NewProjectWizardState {
selectedRuntime: ILanguageRuntimeMetadata | undefined;
projectType: NewProjectType | undefined;
projectName: string;
parentFolder: string;
parentFolder: URI;
initGitRepo: boolean;
openInNewWindow: boolean;
pythonEnvSetupType: EnvironmentSetupType | undefined;
Expand Down Expand Up @@ -106,7 +107,7 @@ export class NewProjectWizardStateManager
private _projectType: NewProjectType | undefined;
private _projectName: string;
private _projectNameFeedback: WizardFormattedTextItem | undefined;
private _parentFolder: string;
private _parentFolder: URI;
private _initGitRepo: boolean;
private _openInNewWindow: boolean;
// Python-specific state.
Expand Down Expand Up @@ -282,15 +283,15 @@ export class NewProjectWizardStateManager
* Gets the parent folder.
* @returns The parent folder.
*/
get parentFolder(): string {
get parentFolder(): URI {
return this._parentFolder;
}

/**
* Sets the parent folder.
* @param value The parent folder.
*/
set parentFolder(value: string) {
set parentFolder(value: URI) {
this._parentFolder = value;
this._onUpdateProjectDirectoryEmitter.fire();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { IPathService } from 'vs/workbench/services/path/common/pathService';
*/
export const checkProjectName = async (
projectName: string,
parentFolder: string,
parentFolder: URI,
pathService: IPathService,
fileService: IFileService
) => {
Expand All @@ -37,9 +37,7 @@ export const checkProjectName = async (
// TODO: Additional project name validation (i.e. unsupported characters, length, etc.)

// The project directory can't already exist.
const folderPath = URI.file(
(await pathService.path).join(parentFolder, projectName)
);
const folderPath = parentFolder.with({ path: (await pathService.path).join(parentFolder.fsPath, projectName) });
if (await fileService.exists(folderPath)) {
return {
type: WizardFormattedTextType.Error,
Expand Down

0 comments on commit e8be5d2

Please sign in to comment.