Skip to content

Commit

Permalink
Merge pull request #988 from vrc-get/ask-install-unity-if-not-installed
Browse files Browse the repository at this point in the history
Ask install unity if not installed
  • Loading branch information
anatawa12 committed May 18, 2024
2 parents a459e92 + 2fb0e21 commit 5608033
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 59 deletions.
1 change: 1 addition & 0 deletions CHANGELOG-gui.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ The format is based on [Keep a Changelog].

### Changed
- Improved project Template selection `#967`
- Ask installing unity for a project if not installed `#988`

### Deprecated

Expand Down
15 changes: 13 additions & 2 deletions vrc-get-gui/app/projects/manage/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ function PageBody() {
projectName={projectName}
projectPath={projectPath}
unityVersion={detailsResult.data?.unity_str ?? null}
unityRevision={detailsResult.data?.unity_revision ?? null}
unityVersions={unityVersionsResult?.data}
onRemove={onRemoveProject}
onBackup={onBackupProject}
Expand Down Expand Up @@ -1451,11 +1452,21 @@ function PackageLatestInfo(
}
}

function ProjectViewHeader({className, projectName, projectPath, unityVersion, unityVersions, onRemove, onBackup}: {
function ProjectViewHeader({
className,
projectName,
projectPath,
unityVersion,
unityRevision,
unityVersions,
onRemove,
onBackup
}: {
className?: string,
projectName: string,
projectPath: string
unityVersion: string | null,
unityRevision: string | null,
unityVersions: TauriUnityVersions | undefined,
onRemove?: () => void,
onBackup?: () => void,
Expand All @@ -1480,7 +1491,7 @@ function ProjectViewHeader({className, projectName, projectPath, unityVersion, u

<Menu>
<ButtonGroup>
<Button onClick={() => openUnity.openUnity(projectPath, unityVersion)}
<Button onClick={() => openUnity.openUnity(projectPath, unityVersion, unityRevision)}
className={"pl-4 pr-3"}>{tc("projects:button:open unity")}</Button>
<MenuHandler className={"pl-2 pr-2"}>
<Button>
Expand Down
2 changes: 1 addition & 1 deletion vrc-get-gui/app/projects/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ function ProjectRow(
<td className={noGrowCellClass}>
<div className="flex flex-row gap-2 max-w-min">
<RowButton
onClick={() => openUnity(project.path, project.unity)}>{tc("projects:button:open unity")}</RowButton>
onClick={() => openUnity(project.path, project.unity, project.unity_revision)}>{tc("projects:button:open unity")}</RowButton>
{manageButton}
<RowButton onClick={() => backupProjectModal.startBackup(project)}
color={"green"}>{tc("projects:backup")}</RowButton>
Expand Down
48 changes: 24 additions & 24 deletions vrc-get-gui/lib/bindings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,35 +202,35 @@ export function deepLinkInstallVcc() {
return invoke()<null>("deep_link_install_vcc")
}

export type TauriPickUnityResult = "NoFolderSelected" | "InvalidSelection" | "AlreadyAdded" | "Successful"
export type TauriVersion = { major: number; minor: number; patch: number; pre: string; build: string }
export type TauriProjectCreationInformation = { templates: TauriProjectTemplate[]; default_path: string }
export type TauriProjectDirCheckResult = "InvalidNameForFolderName" | "MayCompatibilityProblem" | "WideChar" | "AlreadyExists" | "Ok"
export type TauriAddProjectWithPickerResult = "NoFolderSelected" | "InvalidSelection" | "AlreadyAdded" | "Successful"
export type TauriPendingProjectChanges = { changes_version: number; package_changes: ([string, TauriPackageChange])[]; remove_legacy_files: string[]; remove_legacy_folders: string[]; conflicts: ([string, TauriConflictInfo])[] }
export type TauriPickProjectBackupPathResult = "NoFolderSelected" | "InvalidSelection" | "Successful"
export type TauriBasePackageInfo = { name: string; display_name: string | null; aliases: string[]; version: TauriVersion; unity: [number, number] | null; changelog_url: string | null; vpm_dependencies: string[]; legacy_packages: string[]; is_yanked: boolean }
export type TauriProjectType = "Unknown" | "LegacySdk2" | "LegacyWorlds" | "LegacyAvatars" | "UpmWorlds" | "UpmAvatars" | "UpmStarter" | "Worlds" | "Avatars" | "VpmStarter"
export type TauriPackageChange = { InstallNew: TauriBasePackageInfo } | { Remove: TauriRemoveReason }
export type TauriRemoteRepositoryInfo = { display_name: string; id: string; url: string; packages: TauriBasePackageInfo[] }
export type TauriUserRepository = { id: string; url: string | null; display_name: string }
export type TauriPackageSource = "LocalUser" | { Remote: { id: string; display_name: string } }
export type TauriConflictInfo = { packages: string[]; unity_conflict: boolean }
export type TauriCallUnityForMigrationResult = { type: "ExistsWithNonZero"; status: string } | { type: "FinishedSuccessfully" }
export type TauriPickProjectDefaultPathResult = { type: "NoFolderSelected" } | { type: "InvalidSelection" } | { type: "Successful"; new_path: string }
export type TauriPackage = ({ name: string; display_name: string | null; aliases: string[]; version: TauriVersion; unity: [number, number] | null; changelog_url: string | null; vpm_dependencies: string[]; legacy_packages: string[]; is_yanked: boolean }) & { env_version: number; index: number; source: TauriPackageSource }
export type TauriCreateProjectResult = "AlreadyExists" | "TemplateNotFound" | "Successful"
export type TauriPickUnityResult = "NoFolderSelected" | "InvalidSelection" | "AlreadyAdded" | "Successful"
export type TauriProjectCreationInformation = { templates: TauriProjectTemplate[]; default_path: string }
export type TauriRepositoriesInfo = { user_repositories: TauriUserRepository[]; hidden_user_repositories: string[]; hide_local_user_packages: boolean; show_prerelease_packages: boolean }
export type TauriAddProjectWithPickerResult = "NoFolderSelected" | "InvalidSelection" | "AlreadyAdded" | "Successful"
export type TauriRemoveReason = "Requested" | "Legacy" | "Unused"
export type LogEntry = { time: string; level: LogLevel; target: string; message: string }
export type TauriProjectType = "Unknown" | "LegacySdk2" | "LegacyWorlds" | "LegacyAvatars" | "UpmWorlds" | "UpmAvatars" | "UpmStarter" | "Worlds" | "Avatars" | "VpmStarter"
export type TauriProjectDetails = { unity: [number, number] | null; unity_str: string | null; installed_packages: ([string, TauriBasePackageInfo])[]; should_resolve: boolean }
export type TauriBasePackageInfo = { name: string; display_name: string | null; aliases: string[]; version: TauriVersion; unity: [number, number] | null; changelog_url: string | null; vpm_dependencies: string[]; legacy_packages: string[]; is_yanked: boolean }
export type TauriAddRepositoryResult = "BadUrl" | "Success"
export type LogLevel = "Error" | "Warn" | "Info" | "Debug" | "Trace"
export type TauriConflictInfo = { packages: string[]; unity_conflict: boolean }
export type TauriUnityVersions = { unity_paths: ([string, string, boolean])[]; recommended_version: string; install_recommended_version_link: string }
export type TauriEnvironmentSettings = { default_project_path: string; project_backup_path: string; unity_hub: string; unity_paths: ([string, string, boolean])[]; show_prerelease_packages: boolean; backup_format: string }
export type TauriRemoteRepositoryInfo = { display_name: string; id: string; url: string; packages: TauriBasePackageInfo[] }
export type LogLevel = "Error" | "Warn" | "Info" | "Debug" | "Trace"
export type TauriCreateProjectResult = "AlreadyExists" | "TemplateNotFound" | "Successful"
export type TauriPackageSource = "LocalUser" | { Remote: { id: string; display_name: string } }
export type AddRepositoryInfo = { url: string; headers: { [key: string]: string } }
export type TauriProjectTemplate = { type: "Builtin"; id: string; name: string } | { type: "Custom"; name: string }
export type TauriProjectDetails = { unity: [number, number] | null; unity_str: string | null; unity_revision: string | null; installed_packages: ([string, TauriBasePackageInfo])[]; should_resolve: boolean }
export type TauriPickProjectBackupPathResult = "NoFolderSelected" | "InvalidSelection" | "Successful"
export type AsyncCallResult<P, R> = { type: "Result"; value: R } | { type: "Started" } | { type: "UnusedProgress"; progress: P }
export type TauriVersion = { major: number; minor: number; patch: number; pre: string; build: string }
export type TauriPackage = ({ name: string; display_name: string | null; aliases: string[]; version: TauriVersion; unity: [number, number] | null; changelog_url: string | null; vpm_dependencies: string[]; legacy_packages: string[]; is_yanked: boolean }) & { env_version: number; index: number; source: TauriPackageSource }
export type TauriEnvironmentSettings = { default_project_path: string; project_backup_path: string; unity_hub: string; unity_paths: ([string, string, boolean])[]; show_prerelease_packages: boolean; backup_format: string }
export type TauriProjectDirCheckResult = "InvalidNameForFolderName" | "MayCompatibilityProblem" | "WideChar" | "AlreadyExists" | "Ok"
export type TauriAddRepositoryResult = "BadUrl" | "Success"
export type TauriPickUnityHubResult = "NoFolderSelected" | "InvalidSelection" | "Successful"
export type TauriRemoveReason = "Requested" | "Legacy" | "Unused"
export type TauriDownloadRepository = { type: "BadUrl" } | { type: "Duplicated" } | { type: "DownloadError"; message: string } | { type: "Success"; value: TauriRemoteRepositoryInfo }
export type AsyncCallResult<P, R> = { type: "Result"; value: R } | { type: "Started" } | { type: "UnusedProgress"; progress: P }
export type TauriRepositoriesInfo = { user_repositories: TauriUserRepository[]; hidden_user_repositories: string[]; hide_local_user_packages: boolean; show_prerelease_packages: boolean }
export type TauriPackageChange = { InstallNew: TauriBasePackageInfo } | { Remove: TauriRemoveReason }
export type TauriProjectTemplate = { type: "Builtin"; id: string; name: string } | { type: "Custom"; name: string }
export type TauriCallUnityForMigrationResult = { type: "ExistsWithNonZero"; status: string } | { type: "FinishedSuccessfully" }
export type TauriProject = { list_version: number; index: number; name: string; path: string; project_type: TauriProjectType; unity: string; last_modified: number; created_at: number; favorite: boolean; is_exists: boolean }
export type TauriProject = { list_version: number; index: number; name: string; path: string; project_type: TauriProjectType; unity: string; unity_revision: string | null; last_modified: number; created_at: number; favorite: boolean; is_exists: boolean }
75 changes: 70 additions & 5 deletions vrc-get-gui/lib/use-open-unity.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,32 @@
import {projectOpenUnity, TauriUnityVersions} from "@/lib/bindings";
import i18next from "@/lib/i18n";
import i18next, {tc} from "@/lib/i18n";
import {toastError, toastNormal} from "@/lib/toast";
import {useUnitySelectorDialog} from "@/lib/use-unity-selector-dialog";
import {shellOpen} from "@/lib/shellOpen";
import {Button, Dialog, DialogBody, DialogFooter, DialogHeader, Typography} from "@material-tailwind/react";
import React from "react";
import {nop} from "@/lib/nop";

export type OpenUnityFunction = (projectPath: string, unityVersion: string | null) => void;
export type OpenUnityFunction = (projectPath: string, unityVersion: string | null, unityRevision?: string | null) => void;

export type Result = {
dialog: React.ReactNode;
openUnity: OpenUnityFunction;
}

type StateInternal = {
state: "normal";
} | {
state: "suggest-unity-hub";
unityVersion: string;
unityHubLink: string;
}

export function useOpenUnity(unityVersions: TauriUnityVersions | undefined): Result {
const unitySelector = useUnitySelectorDialog();
const [installStatus, setInstallStatus] = React.useState<StateInternal>({state: "normal"});

const openUnity = async (projectPath: string, unityVersion: string | null) => {
const openUnity = async (projectPath: string, unityVersion: string | null, unityRevision?: string | null) => {
if (unityVersion == null) {
toastError(i18next.t("projects:toast:invalid project unity version"));
return;
Expand All @@ -27,7 +40,15 @@ export function useOpenUnity(unityVersions: TauriUnityVersions | undefined): Res

switch (foundVersions.length) {
case 0:
toastError(i18next.t("projects:toast:match version unity not found"));
if (unityRevision) {
setInstallStatus({
state: "suggest-unity-hub",
unityVersion: unityVersion,
unityHubLink: `unityhub://${unityVersion}/${unityRevision}`,
});
} else {
toastError(i18next.t("projects:toast:match version unity not found"));
}
return;
case 1:
toastNormal(i18next.t("projects:toast:opening unity..."));
Expand All @@ -41,5 +62,49 @@ export function useOpenUnity(unityVersions: TauriUnityVersions | undefined): Res
}
}

return {dialog: unitySelector.dialog, openUnity};
const thisDialog = installStatus.state === "suggest-unity-hub" ? <UnityInstallWindow
expectedVersion={installStatus.unityVersion}
installWithUnityHubLink={installStatus.unityHubLink}
close={() => setInstallStatus({state: "normal"})}
/> : null;

const dialog = <>
{unitySelector.dialog}
{thisDialog}
</>

return {dialog, openUnity};
}


function UnityInstallWindow(
{
expectedVersion,
installWithUnityHubLink,
close,
}: {
expectedVersion: string,
installWithUnityHubLink: string,
close: () => void
}) {
const openUnityHub = async () => {
console.log("openUnityHub", installWithUnityHubLink)
await shellOpen(installWithUnityHubLink);
}

return <Dialog open handler={nop}>
<DialogHeader>
{tc("projects:manage:dialog:unity not found")}
</DialogHeader>
<DialogBody>
<Typography>
{tc("projects:manage:dialog:unity version of the project not found", {unity: expectedVersion})}
</Typography>
</DialogBody>
<DialogFooter className={"gap-2"}>
<Button onClick={openUnityHub}>{tc("projects:manage:dialog:open unity hub")}</Button>
<Button onClick={close} className="mr-1">{tc("general:button:close")}</Button>
</DialogFooter>
</Dialog>;
}

2 changes: 2 additions & 0 deletions vrc-get-gui/locales/en.json5
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,8 @@
"projects:manage:button:select repositories": "Select Repositories",
"vpm repositories:source:official": "Official",
"vpm repositories:source:curated": "Curated",
"projects:manage:dialog:unity not found": "Unity was not found",
"projects:manage:dialog:unity version of the project not found": "The project is using Unity {{unity}} but it was not found.<br>Please install Unity {{unity}} with Unity Hub and restart ALCOM.",
"projects:manage:suggest resolve": "Some required packages for this project are not installed.<br>It is strongly recommended to install the packages.",
"projects:manage:button:resolve": "Install Packages",
"projects:manage:suggest unity migration": "Your project is using Unity 2019 which is no longer supported by VRChat SDK. VRChat recommends you migrate your project to Unity 2022.",
Expand Down
4 changes: 4 additions & 0 deletions vrc-get-gui/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,7 @@ struct TauriProject {
path: String,
project_type: TauriProjectType,
unity: String,
unity_revision: Option<String>,
last_modified: i64,
created_at: i64,
favorite: bool,
Expand Down Expand Up @@ -587,6 +588,7 @@ impl TauriProject {
.unity_version()
.map(|v| v.to_string())
.unwrap_or_else(|| "unknown".into()),
unity_revision: project.unity_revision().map(|x| x.to_string()),
last_modified: project.last_modified().timestamp_millis(),
created_at: project.crated_at().timestamp_millis(),
favorite: project.favorite(),
Expand Down Expand Up @@ -1882,6 +1884,7 @@ async fn environment_create_project(
struct TauriProjectDetails {
unity: Option<(u16, u8)>,
unity_str: Option<String>,
unity_revision: Option<String>,
installed_packages: Vec<(String, TauriBasePackageInfo)>,
should_resolve: bool,
}
Expand All @@ -1903,6 +1906,7 @@ async fn project_details(project_path: String) -> Result<TauriProjectDetails, Ru
.unity_version()
.map(|v| (v.major(), v.minor())),
unity_str: unity_project.unity_version().map(|v| v.to_string()),
unity_revision: unity_project.unity_revision().map(|x| x.to_string()),
installed_packages: unity_project
.installed_packages()
.map(|(k, p)| (k.to_string(), TauriBasePackageInfo::new(p)))
Expand Down

0 comments on commit 5608033

Please sign in to comment.