From 21ed396471c74ae0b534c2604895eafd7fb11bcf Mon Sep 17 00:00:00 2001 From: Angelina Mkrtychyan Date: Mon, 16 Jun 2025 16:26:00 -0700 Subject: [PATCH 1/5] implemented view all functionality for other galleries, removing the unwanted functionality --- localtypings/pxteditor.d.ts | 1 + package.json | 2 +- webapp/src/app.tsx | 11 +++ webapp/src/galleryviewer.tsx | 139 +++++++++++++++++++++++++++++++++++ webapp/src/projects.tsx | 58 ++++++++++++++- 5 files changed, 207 insertions(+), 4 deletions(-) create mode 100644 webapp/src/galleryviewer.tsx diff --git a/localtypings/pxteditor.d.ts b/localtypings/pxteditor.d.ts index e1824cb9d2..7884f10aff 100644 --- a/localtypings/pxteditor.d.ts +++ b/localtypings/pxteditor.d.ts @@ -938,6 +938,7 @@ declare namespace pxt.editor { createProjectAsync(options: pxt.editor.ProjectCreationOptions): Promise; importExampleAsync(options: ExampleImportOptions): Promise; showScriptManager(): void; + showGalleryViewer(galleryPath: string, galleryName: string): void; importProjectDialog(): void; removeProject(): void; editText(): void; diff --git a/package.json b/package.json index 473a33207a..d265c54d7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pxt-core", - "version": "11.4.7", + "version": "11.4.8", "description": "Microsoft MakeCode provides Blocks / JavaScript / Python tools and editors", "keywords": [ "TypeScript", diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 6eea319b59..917b1da1cb 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -32,6 +32,7 @@ import * as scriptsearch from "./scriptsearch"; import * as extensionsBrowser from "./extensionsBrowser"; import * as projects from "./projects"; import * as scriptmanager from "./scriptmanager"; +import * as galleryviewer from "./galleryviewer"; import * as extensions from "./extensions"; import * as sounds from "./sounds"; import * as make from "./make"; @@ -144,6 +145,7 @@ export class ProjectView shareEditor: share.ShareEditor; languagePicker: lang.LanguagePicker; scriptManagerDialog: scriptmanager.ScriptManagerDialog; + galleryViewerDialog: galleryviewer.GalleryViewerDialog; importDialog: projects.ImportDialog; loginDialog: identity.LoginDialog; profileDialog: user.ProfileDialog; @@ -4126,6 +4128,10 @@ export class ProjectView this.scriptManagerDialog.show(); } + showGalleryViewer(galleryPath: string, galleryName: string) { + this.galleryViewerDialog.show(galleryPath, galleryName); + } + importProjectDialog() { this.importDialog.show(); } @@ -5297,6 +5303,10 @@ export class ProjectView this.scriptManagerDialog = c; } + private handleGalleryViewerDialogRef = (c: galleryviewer.GalleryViewerDialog) => { + this.galleryViewerDialog = c; + } + private handleImportDialogRef = (c: projects.ImportDialog) => { this.importDialog = c; } @@ -5499,6 +5509,7 @@ export class ProjectView {hasIdentity ? : undefined} {hasIdentity ? : undefined} {inHome && targetTheme.scriptManager ? : undefined} + {sandbox ? undefined : } {sandbox ? undefined : } {hwDialog ? : undefined} diff --git a/webapp/src/galleryviewer.tsx b/webapp/src/galleryviewer.tsx new file mode 100644 index 0000000000..b2e31c6342 --- /dev/null +++ b/webapp/src/galleryviewer.tsx @@ -0,0 +1,139 @@ +import * as React from "react"; +import * as data from "./data"; +import * as sui from "./sui"; +import * as core from "./core"; +import * as codecard from "./codecard"; + +import { SearchInput } from "./components/searchInput"; +import { fireClickOnEnter } from "./util"; + +import ISettingsProps = pxt.editor.ISettingsProps; +import { applyCodeCardAction } from "./projects"; + +export interface GalleryViewerDialogProps extends ISettingsProps { + onClose?: () => void; +} + +export interface GalleryViewerDialogState { + visible?: boolean; + galleryCards?: pxt.CodeCard[]; + searchFor?: string; + galleryName?: string; + galleryPath?: string; +} + +export class GalleryViewerDialog extends data.Component { + constructor(props: GalleryViewerDialogProps) { + super(props); + this.state = { + visible: false, + galleryCards: [], + galleryName: "", + galleryPath: "" + }; + + this.close = this.close.bind(this); + this.handleCardClick = this.handleCardClick.bind(this); + this.handleSearch = this.handleSearch.bind(this); + } + + hide() { + this.setState({ visible: false, searchFor: undefined }); + } + + close() { + this.setState({ visible: false, searchFor: undefined }); + if (this.props.onClose) this.props.onClose(); + } + + show(galleryPath: string, galleryName: string) { + this.setState({ + visible: true, + galleryPath: galleryPath, + galleryName: galleryName, + galleryCards: [] // Clear previous gallery cards + }, () => { + this.fetchGalleryData(); + }); + } + + fetchGalleryData() { + const { galleryPath } = this.state; + if (!galleryPath) return; + + // Fetch gallery cards + let res = this.getData(`gallery:${encodeURIComponent(galleryPath)}`) as pxt.gallery.Gallery[]; + if (res && !(res instanceof Error)) { + this.setState({ + galleryCards: pxt.Util.concat(res.map(g => g.cards)) + }); + } else { + // Handle error + this.setState({ + galleryCards: [] + }); + } + } + + handleCardClick(e: any, scr: pxt.CodeCard) { + pxt.tickEvent("galleryviewer.card", { name: scr.name, cardType: scr.cardType }); + + // Use the same application method as the regular gallery + applyCodeCardAction(this.props.parent, "projects", scr); + + // Close the dialog after selection + this.close(); + } + + handleSearch(inputValue: string) { + const searchFor = (inputValue || '').trim().toLowerCase(); + this.setState({ searchFor }); + } + + renderCore() { + const { visible, galleryCards, searchFor, galleryName } = this.state; + if (!visible) return
; + + // Filter gallery cards based on search term if provided + const filteredCards = searchFor && galleryCards ? + galleryCards.filter(card => + (card.name || "").toLowerCase().indexOf(searchFor) !== -1 || + (card.description || "").toLowerCase().indexOf(searchFor) !== -1 + ) : + galleryCards || []; + + return ( + +
+
+ +
+ {filteredCards.length === 0 ? +
+ {searchFor ? lf("No matching tutorials found.") : lf("No tutorials found.")} +
: +
+ {filteredCards.map((card, index) => + this.handleCardClick(e, card)} + /> + )} +
+ } +
+
+ ); + } +} diff --git a/webapp/src/projects.tsx b/webapp/src/projects.tsx index 4e1f9c54a3..9abf9816b9 100644 --- a/webapp/src/projects.tsx +++ b/webapp/src/projects.tsx @@ -197,7 +197,24 @@ export class Projects extends auth.Component { const url = typeof galProps === "string" ? galProps : galProps.url const shuffle: pxt.GalleryShuffle = typeof galProps === "string" ? undefined : galProps.shuffle; return
-

{pxt.Util.rlf(galleryName)}

+
+
url && this.props.parent.showGalleryViewer(url, galleryName)} + onKeyDown={fireClickOnEnter} + > +

+ {pxt.Util.rlf(galleryName)} + + {lf("View All")} + +

+
+
{ path={url} onClick={this.chgGallery} setSelected={this.setSelected} shuffle={shuffle} + showViewAll={false} selectedIndex={selectedCategory == galleryName ? selectedIndex : undefined} />
@@ -570,6 +588,7 @@ interface ProjectsCarouselProps extends ISettingsProps { selectedIndex?: number; setSelected?: (name: string, index: number) => void; shuffle?: pxt.GalleryShuffle; + showViewAll?: boolean; } interface ProjectsCarouselState { @@ -591,6 +610,7 @@ export class ProjectsCarousel extends data.Component) { + e.stopPropagation(); + e.preventDefault(); + + const { name, path } = this.props; + if (path) { + pxt.tickEvent("gallery.viewall", { gallery: name }); + this.props.parent.showGalleryViewer(path, name); + } + } + closeDetail() { const { name } = this.props; pxt.tickEvent("projects.detail.close"); @@ -705,7 +736,28 @@ export class ProjectsCarousel extends data.Component c.tags && c.tags.length != 0) - return
+ return
+ {this.props.showViewAll && ( +
+
+
+

+ {lf(name)} + + {lf("View All")} + +

+
+
+
+ )} {cards.map((scr, index) => , className: string /> } -function applyCodeCardAction(projectView: IProjectView, ticSrc: "projects" | "herobanner", scr: pxt.CodeCard, action?: pxt.CodeCardAction) { +export function applyCodeCardAction(projectView: IProjectView, ticSrc: "projects" | "herobanner", scr: pxt.CodeCard, action?: pxt.CodeCardAction) { let editor: string = (action && action.editor) || "blocks"; if (editor == "js") editor = "ts"; const url = action ? action.url : scr.url; From 80003a3e6899b6eb61a0ed729d49f84c8c50bd4b Mon Sep 17 00:00:00 2001 From: Angelina Mkrtychyan Date: Mon, 16 Jun 2025 16:37:45 -0700 Subject: [PATCH 2/5] trimmed trailing spaces --- webapp/src/galleryviewer.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/webapp/src/galleryviewer.tsx b/webapp/src/galleryviewer.tsx index b2e31c6342..72996ec184 100644 --- a/webapp/src/galleryviewer.tsx +++ b/webapp/src/galleryviewer.tsx @@ -47,9 +47,9 @@ export class GalleryViewerDialog extends data.Component { @@ -77,10 +77,10 @@ export class GalleryViewerDialog extends data.Component; // Filter gallery cards based on search term if provided - const filteredCards = searchFor && galleryCards ? - galleryCards.filter(card => + const filteredCards = searchFor && galleryCards ? + galleryCards.filter(card => (card.name || "").toLowerCase().indexOf(searchFor) !== -1 || (card.description || "").toLowerCase().indexOf(searchFor) !== -1 - ) : + ) : galleryCards || []; return ( @@ -116,7 +116,7 @@ export class GalleryViewerDialog extends data.Component :
- {filteredCards.map((card, index) => + {filteredCards.map((card, index) => Date: Mon, 16 Jun 2025 17:22:30 -0700 Subject: [PATCH 3/5] trim trailing spaces for projects.tsx --- webapp/src/projects.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/webapp/src/projects.tsx b/webapp/src/projects.tsx index 9abf9816b9..e2141eb981 100644 --- a/webapp/src/projects.tsx +++ b/webapp/src/projects.tsx @@ -199,15 +199,15 @@ export class Projects extends auth.Component { return
url && this.props.parent.showGalleryViewer(url, galleryName)} + onClick={() => url && this.props.parent.showGalleryViewer(url, galleryName)} onKeyDown={fireClickOnEnter} >

{pxt.Util.rlf(galleryName)} - {lf("View All")} @@ -662,7 +662,7 @@ export class ProjectsCarousel extends data.Component) { e.stopPropagation(); e.preventDefault(); - + const { name, path } = this.props; if (path) { pxt.tickEvent("gallery.viewall", { gallery: name }); @@ -743,10 +743,10 @@ export class ProjectsCarousel extends data.Component

{lf(name)} - Date: Fri, 27 Jun 2025 14:57:43 -0700 Subject: [PATCH 4/5] added view all testing --- package.json | 11 +++++++++++ tests/unit/galleryviewer.test.js | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+) create mode 100644 tests/unit/galleryviewer.test.js diff --git a/package.json b/package.json index d265c54d7d..8eda975b7d 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ "blockly": "11.2.1", "browserify": "17.0.0", "chai": "^3.5.0", + "cheerio": "^1.0.0-rc.12", "cssnano": "4.1.10", "dashjs": "^4.4.0", "diff-match-patch": "^1.0.5", @@ -107,8 +108,10 @@ "@microsoft/eslint-plugin-sdl": "^0.1.5", "@types/adm-zip": "^0.5.5", "@types/chai": "4.0.6", + "@types/enzyme": "^3.10.19", "@types/fuse": "2.5.2", "@types/highlight.js": "9.12.2", + "@types/jest": "^30.0.0", "@types/jquery": "3.3.29", "@types/marked": "0.3.0", "@types/mocha": "^2.2.44", @@ -120,12 +123,16 @@ "@types/react-tooltip": "3.3.6", "@types/request": "2.48.5", "@types/resize-observer-browser": "^0.1.5", + "@types/sinon": "^17.0.4", "@types/web-bluetooth": "0.0.4", "@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/parser": "^4.20.0", "@vusion/webfonts-generator": "0.8.0", + "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", "crypto-js": "4.2.0", "envify": "4.1.0", + "enzyme": "^3.11.0", + "enzyme-to-json": "^3.6.2", "eslint": "^7.23.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", @@ -137,6 +144,8 @@ "gulp-replace": "1.0.0", "gulp-typescript": "5.0.1", "highlight.js": "9.12.0", + "jest": "^30.0.2", + "jest-snapshot": "^30.0.2", "jquery": "3.5.0", "merge-stream": "2.0.0", "monaco-editor": "0.24.0", @@ -148,7 +157,9 @@ "react-redux": "7.2.9", "react-tooltip": "3.9.0", "redux": "4.2.0", + "sinon": "^21.0.0", "smoothie": "1.35.0", + "ts-jest": "^29.4.0", "typescript": "4.8.3", "uglifyify": "5.0.2" }, diff --git a/tests/unit/galleryviewer.test.js b/tests/unit/galleryviewer.test.js new file mode 100644 index 0000000000..5c3d71d43e --- /dev/null +++ b/tests/unit/galleryviewer.test.js @@ -0,0 +1,29 @@ +const { expect } = require('chai'); +const sinon = require('sinon'); + +describe('View All logic', () => { + it('calls showGalleryViewer when gallery View All is clicked', () => { + // Mock gallery structure + const galleryName = "Tutorials"; + const galleryUrl = "tutorials"; + + // Simulate a simplified Projects component with props + const parentMock = { + showGalleryViewer: sinon.spy() + }; + + // Simulate what the click handler does + function onClickViewAll(url, name) { + if (url && parentMock.showGalleryViewer) { + parentMock.showGalleryViewer(url, name); + } + } + + // Act: pretend user clicked + onClickViewAll(galleryUrl, galleryName); + + // Assert + expect(parentMock.showGalleryViewer.calledOnce).to.be.true; + expect(parentMock.showGalleryViewer.firstCall.args).to.deep.equal(["tutorials", "Tutorials"]); + }); +}); From 7f11e4a2620363f58798cac76bac05031585d6a1 Mon Sep 17 00:00:00 2001 From: Angelina Mkrtychyan Date: Fri, 27 Jun 2025 16:11:17 -0700 Subject: [PATCH 5/5] I forgot to change this file back and remove the stuff I added before my last commit. Its fixed now --- package.json | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/package.json b/package.json index 8eda975b7d..d265c54d7d 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "blockly": "11.2.1", "browserify": "17.0.0", "chai": "^3.5.0", - "cheerio": "^1.0.0-rc.12", "cssnano": "4.1.10", "dashjs": "^4.4.0", "diff-match-patch": "^1.0.5", @@ -108,10 +107,8 @@ "@microsoft/eslint-plugin-sdl": "^0.1.5", "@types/adm-zip": "^0.5.5", "@types/chai": "4.0.6", - "@types/enzyme": "^3.10.19", "@types/fuse": "2.5.2", "@types/highlight.js": "9.12.2", - "@types/jest": "^30.0.0", "@types/jquery": "3.3.29", "@types/marked": "0.3.0", "@types/mocha": "^2.2.44", @@ -123,16 +120,12 @@ "@types/react-tooltip": "3.3.6", "@types/request": "2.48.5", "@types/resize-observer-browser": "^0.1.5", - "@types/sinon": "^17.0.4", "@types/web-bluetooth": "0.0.4", "@typescript-eslint/eslint-plugin": "^4.20.0", "@typescript-eslint/parser": "^4.20.0", "@vusion/webfonts-generator": "0.8.0", - "@wojtekmaj/enzyme-adapter-react-17": "^0.8.0", "crypto-js": "4.2.0", "envify": "4.1.0", - "enzyme": "^3.11.0", - "enzyme-to-json": "^3.6.2", "eslint": "^7.23.0", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", @@ -144,8 +137,6 @@ "gulp-replace": "1.0.0", "gulp-typescript": "5.0.1", "highlight.js": "9.12.0", - "jest": "^30.0.2", - "jest-snapshot": "^30.0.2", "jquery": "3.5.0", "merge-stream": "2.0.0", "monaco-editor": "0.24.0", @@ -157,9 +148,7 @@ "react-redux": "7.2.9", "react-tooltip": "3.9.0", "redux": "4.2.0", - "sinon": "^21.0.0", "smoothie": "1.35.0", - "ts-jest": "^29.4.0", "typescript": "4.8.3", "uglifyify": "5.0.2" },