Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[stable8.4] Extension search fixes port #9240

Merged
merged 2 commits into from
Nov 23, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
9 changes: 5 additions & 4 deletions pxtlib/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1674,8 +1674,9 @@ namespace ts.pxtc.service {


export enum ExtensionType {
Bundled,
Github
Bundled = 1,
Github = 2,
ShareScript = 3,
}

export interface ExtensionMeta {
Expand All @@ -1684,13 +1685,13 @@ namespace ts.pxtc.service {
description?: string,
imageUrl?: string,
type?: ExtensionType
learnMoreUrl?: string;

pkgConfig?: pxt.PackageConfig; // Added if the type is Bundled
repo?: pxt.github.GitRepo; //Added if the type is Github VVN TODO ADD THIS
learnMoreUrl?: string;
scriptInfo?: pxt.Cloud.JsonScript
}


export interface SearchInfo {
id: string;
name: string;
Expand Down
152 changes: 101 additions & 51 deletions webapp/src/extensionsBrowser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { Modal } from "../../react-common/components/controls/Modal";
import { classList } from "../../react-common/components/util";

type ExtensionMeta = pxtc.service.ExtensionMeta;
const ExtensionType = pxtc.service.ExtensionType;
type EmptyCard = { name: string, loading?: boolean }
const emptyCard: EmptyCard = { name: "", loading: true }

Expand Down Expand Up @@ -81,6 +82,12 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
parsedExt.unshift(e)
}
})

const shareUrlData = await fetchShareUrlDataAsync(searchFor);
if (shareUrlData) {
parsedExt.unshift(parseShareScript(shareUrlData));
}

addExtensionsToPool(parsedExt)
setExtensionsToShow(parsedExt)
setSearchComplete(true)
Expand All @@ -100,23 +107,25 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
}

function getExtensionFromFetched(extensionUrl: string) {
const parsedGithubRepo = pxt.github.parseRepoId(extensionUrl);
if (parsedGithubRepo)
return allExtensions.get(parsedGithubRepo.slug.toLowerCase());

const fullName = allExtensions.get(extensionUrl.toLowerCase())
if (fullName) {
if (fullName)
return fullName
}
const parsedGithubRepo = pxt.github.parseRepoId(extensionUrl)
if (!parsedGithubRepo) return undefined;
return allExtensions.get(parsedGithubRepo.slug.toLowerCase())

return undefined;
}

async function addDepIfNoConflict(config: pxt.PackageConfig, version: string) {
try {
props.hideExtensions();
core.showLoading("installingextension", lf("Adding extension..."))
core.showLoading("installingextension", lf("Adding extension..."));
const added = await pkg.mainEditorPkg()
.addDependencyAsync({ ...config, isExtension: true }, version, false)
if (added) {
await pxt.Util.delay(1000)
await pxt.Util.delay(200);
await props.reloadHeaderAsync();
}
}
Expand Down Expand Up @@ -152,11 +161,11 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
let r: { version: string, config: pxt.PackageConfig };
try {
core.showLoading("downloadingpackage", lf("downloading extension..."));
const pkg = getExtensionFromFetched(scr.name);
const pkg = getExtensionFromFetched(scr.repo.slug);
if (pkg) {
r = await pxt.github.downloadLatestPackageAsync(pkg.repo);
} else {
const res = await fetchGithubDataAsync([scr.name]);
const res = await fetchGithubDataAsync([scr.repo.slug]);
if (res && res.length > 0) {
const parsed = parseGithubRepo(res[0])
addExtensionsToPool([parsed])
Expand All @@ -172,6 +181,34 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
return await addDepIfNoConflict(r.config, r.version)
}

async function fetchShareUrlDataAsync(potentialShareUrl: string): Promise<pxt.Cloud.JsonScript> {
const scriptId = pxt.Cloud.parseScriptId(potentialShareUrl);
if (!scriptId)
return undefined;

const scriptData = await data.getAsync<pxt.Cloud.JsonScript>(`cloud-search:${scriptId}`);

// TODO: fix typing on getAsync? it looks like it returns T or the failed network request
if ((scriptData as any).statusCode == 404) {
return undefined;
}
// unwrap array if returned as array
if (Array.isArray(scriptData)) {
return scriptData[0];
}

return scriptData;
}
async function addShareUrlExtension(scr: pxt.Cloud.JsonScript): Promise<void> {
// todo: we justed used name before but that's easy to lead to conflicts?
// should this be scr.id or something as pkgid?
// todo: how to handle persistent links? right now scr.id is the current version,
// we should probably persist the s id and make it updatable with a refresh.
const shareScript = await workspace.getPublishedScriptAsync(scr.id);
const config = pxt.Util.jsonTryParse(shareScript[pxt.CONFIG_NAME]);
addDepIfNoConflict({...config, version: scr.id }, `pub:${scr.id}`);
}

async function fetchGithubDataAsync(preferredRepos: string[]): Promise<pxt.github.GitRepo[]> {
// When searching multiple repos at the same time, use 'extension-search' which caches results
// for much longer than 'gh-search'
Expand Down Expand Up @@ -208,15 +245,19 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {

function installExtension(scr: ExtensionMeta) {
switch (scr.type) {
case pxtc.service.ExtensionType.Bundled:
case ExtensionType.Bundled:
pxt.tickEvent("packages.bundled", { name: scr.name });
props.hideExtensions();
addDepIfNoConflict(scr.pkgConfig, "*")
addDepIfNoConflict(scr.pkgConfig, "*");
break;
case pxtc.service.ExtensionType.Github:
case ExtensionType.Github:
props.hideExtensions();
addGithubPackage(scr);
break;
case ExtensionType.ShareScript:
props.hideExtensions();
addShareUrlExtension(scr.scriptInfo);
break;
}
}

Expand All @@ -234,14 +275,24 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
function parseGithubRepo(r: pxt.github.GitRepo): ExtensionMeta {
return {
name: ghName(r),
type: pxtc.service.ExtensionType.Github,
type: ExtensionType.Github,
imageUrl: pxt.github.repoIconUrl(r),
repo: r,
description: r.description,
fullName: r.fullName
}
}

function parseShareScript(s: pxt.Cloud.JsonScript): ExtensionMeta {
return {
name: s.name,
type: ExtensionType.ShareScript,
imageUrl: s.thumb ? `${pxt.Cloud.apiRoot}/${s.id}/thumb` : undefined,
description: s.description,
scriptInfo: s,
}
}


function getCategoryNames(): string[] {
if (!extensionTags) return [];
Expand Down Expand Up @@ -286,7 +337,7 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
return {
name: p.name,
imageUrl: p.icon,
type: pxtc.service.ExtensionType.Bundled,
type: ExtensionType.Bundled,
learnMoreUrl: `/reference/${p.name}`,
pkgConfig: p,
description: p.description
Expand Down Expand Up @@ -377,6 +428,34 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
}
}

function ExtensionMetaCard(props: {
extensionInfo: ExtensionMeta & EmptyCard,
}) {
const { extensionInfo } = props;
const {
description,
fullName,
imageUrl,
learnMoreUrl,
loading,
name,
repo,
type,
} = extensionInfo;

return <ExtensionCard
title={name || fullName}
description={description}
imageUrl={imageUrl}
extension={extensionInfo}
onClick={installExtension}
learnMoreUrl={learnMoreUrl || (fullName ? `/pkg/${fullName}` : undefined)}
loading={loading}
label={pxt.isPkgBeta(extensionInfo) ? lf("Beta") : undefined}
showDisclaimer={type != ExtensionType.Bundled && repo?.status != pxt.github.GitRepoStatus.Approved}
/>;
}

enum ExtensionView {
Tabbed,
Search,
Expand Down Expand Up @@ -492,19 +571,9 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
{displayMode == ExtensionView.Search &&
<>
<div className="extension-cards">
{extensionsToShow?.map((scr, index) =>
<ExtensionCard
key={classList("searched", index + "", scr.loading && "loading")}
title={scr.name ?? `${index}`}
description={scr.description}
imageUrl={scr.imageUrl}
extension={scr}
onClick={installExtension}
learnMoreUrl={scr.learnMoreUrl || (scr.fullName ? `/pkg/${scr.fullName}` : undefined)}
loading={scr.loading}
label={pxt.isPkgBeta(scr) ? lf("Beta") : undefined}
showDisclaimer={scr.type != pxtc.service.ExtensionType.Bundled && scr.repo?.status != pxt.github.GitRepoStatus.Approved}
/>)}
{extensionsToShow?.map(
(scr, index) => <ExtensionMetaCard extensionInfo={scr} key={index} />
)}
</div>
{searchComplete && extensionsToShow.length == 0 &&
<div aria-label="Extension search results">
Expand All @@ -514,33 +583,14 @@ export const ExtensionsBrowser = (props: ExtensionsProps) => {
</>}
{displayMode == ExtensionView.Tags &&
<div className="extension-cards">
{extensionsToShow?.map((scr, index) =>
<ExtensionCard
key={classList("tagged", index + "", scr.loading && "loading")}
title={scr.name ?? `${index}`}
description={scr.description}
imageUrl={scr.imageUrl}
extension={scr}
onClick={installExtension}
learnMoreUrl={scr.learnMoreUrl || (scr.fullName ? `/pkg/${scr.fullName}` : undefined)}
loading={scr.loading}
label={pxt.isPkgBeta(scr) ? lf("Beta") : undefined}
/>)}
{extensionsToShow?.map(
(scr, index) => <ExtensionMetaCard extensionInfo={scr} key={index} />
)}
</div>}
{displayMode == ExtensionView.Tabbed &&
<div className="extension-cards">
{currentTab == TabState.Recommended && preferredExts.map((scr, index) =>
<ExtensionCard
key={classList("preferred", index + "", scr.loading && "loading")}
extension={scr}
title={scr.name ?? `${index}`}
onClick={installExtension}
imageUrl={scr.imageUrl}
description={scr.description}
learnMoreUrl={scr.learnMoreUrl || (scr.fullName ? `/pkg/${scr.fullName}` : undefined)}
loading={scr.loading}
label={pxt.isPkgBeta(scr) ? lf("Beta") : undefined}
/>
{currentTab == TabState.Recommended && preferredExts.map(
(scr, index) => <ExtensionMetaCard extensionInfo={scr} key={index} />
)}
{currentTab == TabState.InDevelopment && extensionsInDevelopment.map((p, index) =>
<ExtensionCard
Expand Down