diff --git a/apps/scratch/src/containers/Gui.tsx b/apps/scratch/src/containers/Gui.tsx index 483fcad..5b2b0b8 100644 --- a/apps/scratch/src/containers/Gui.tsx +++ b/apps/scratch/src/containers/Gui.tsx @@ -9,6 +9,7 @@ import ErrorBoundaryHOC from "@scratch-submodule/scratch-gui/src/lib/error-bound import { getIsError, getIsShowingProject, + onFetchedProjectData, } from "@scratch-submodule/scratch-gui/src/reducers/project-state"; import { activateTab, @@ -25,11 +26,7 @@ import { import FontLoaderHOC from "@scratch-submodule/scratch-gui/src/lib/font-loader-hoc.jsx"; import LocalizationHOC from "@scratch-submodule/scratch-gui/src/lib/localization-hoc.jsx"; -import SBFileUploaderHOC from "@scratch-submodule/scratch-gui/src/lib/sb-file-uploader-hoc.jsx"; -import ProjectFetcherHOC from "@scratch-submodule/scratch-gui/src/lib/project-fetcher-hoc.jsx"; import TitledHOC from "@scratch-submodule/scratch-gui/src/lib/titled-hoc.jsx"; -import ProjectSaverHOC from "@scratch-submodule/scratch-gui/src/lib/project-saver-hoc.jsx"; -import QueryParserHOC from "@scratch-submodule/scratch-gui/src/lib/query-parser-hoc.jsx"; import storage from "@scratch-submodule/scratch-gui/src/lib/storage"; import vmListenerHOC from "@scratch-submodule/scratch-gui/src/lib/vm-listener-hoc.jsx"; import vmManagerHOC from "@scratch-submodule/scratch-gui/src/lib/vm-manager-hoc.jsx"; @@ -39,6 +36,7 @@ import { setIsScratchDesktop } from "@scratch-submodule/scratch-gui/src/lib/isSc import { StageSizeMode } from "@scratch-submodule/scratch-gui/src/lib/screen-utils"; import { AppStateHOC } from "@scratch-submodule/scratch-gui/src"; import HashParserHOC from "@scratch-submodule/scratch-gui/src/lib/hash-parser-hoc"; +import { loadCrtProject } from "../vm/load-crt-project"; const { RequestMetadata, setMetadata, unsetMetadata } = storage.scratchFetch; @@ -65,6 +63,7 @@ interface Props { isScratchDesktop: boolean; isShowingProject?: boolean; isTotallyNormal: boolean; + isCreatingNew: boolean; loadingStateVisible?: boolean; onProjectLoaded?: () => void; onSeeCommunity?: () => void; @@ -73,11 +72,12 @@ interface Props { onVmInit?: (vm: VM) => void; projectHost?: string; projectId: string | number; - telemetryModalVisible?: boolean; isRtl: boolean; isFullScreen: boolean; basePath: string; + onFetchedProjectData: (projectData: unknown, loadingState: unknown) => void; + backpackHost: string | null; backpackVisible: boolean; blocksId: string; @@ -134,6 +134,7 @@ interface ReduxState { class GUI extends React.Component { componentDidMount() { setIsScratchDesktop(this.props.isScratchDesktop); + if (this.props.onStorageInit) { this.props.onStorageInit(storage); } @@ -141,7 +142,24 @@ class GUI extends React.Component { if (this.props.onVmInit) { this.props.onVmInit(this.props.vm); } + setProjectIdMetadata(this.props.projectId); + + storage + .load(storage.AssetType.Project, "0", storage.DataFormat.JSON) + .then((projectAsset) => { + if (projectAsset) { + loadCrtProject( + this.props.vm, + (projectAsset as unknown as { data: object }).data, + ); + } else { + throw new Error("Could not load default project"); + } + }) + .catch((err) => { + console.error(err); + }); } componentDidUpdate(prevProps: Props) { @@ -182,6 +200,8 @@ class GUI extends React.Component { onVmInit, projectHost, projectId, + isCreatingNew, + onFetchedProjectData, /* eslint-enable @typescript-eslint/no-unused-vars */ children, fetchingProject, @@ -227,7 +247,6 @@ const mapStateToProps = (state: ReduxState) => { state.scratchGui.targets.stage && state.scratchGui.targets.stage.id === state.scratchGui.targets.editingTarget, - telemetryModalVisible: state.scratchGui.modals.telemetryModal, tipsLibraryVisible: state.scratchGui.modals.tipsLibrary, vm: state.scratchGui.vm, }; @@ -240,6 +259,8 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ onActivateSoundsTab: () => dispatch(activateTab(SOUNDS_TAB_INDEX)), onRequestCloseBackdropLibrary: () => dispatch(closeBackdropLibrary()), onRequestCloseCostumeLibrary: () => dispatch(closeCostumeLibrary()), + onFetchedProjectData: (projectData: unknown, loadingState: unknown) => + dispatch(onFetchedProjectData(projectData, loadingState) as Action), }); const ConnectedGUI = injectIntl( @@ -253,13 +274,9 @@ const WrappedGui = compose( LocalizationHOC, ErrorBoundaryHOC("Top Level App"), FontLoaderHOC, - QueryParserHOC, - ProjectFetcherHOC, TitledHOC, - ProjectSaverHOC, vmListenerHOC, vmManagerHOC, - SBFileUploaderHOC, systemPreferencesHOC, )(ConnectedGUI); diff --git a/apps/scratch/src/pages/Solve.tsx b/apps/scratch/src/pages/Solve.tsx index 66fd228..e1f06be 100644 --- a/apps/scratch/src/pages/Solve.tsx +++ b/apps/scratch/src/pages/Solve.tsx @@ -7,6 +7,8 @@ import { AppIFrameResponse, } from "../../../../frontend/src/types/app-iframe-message"; import { patchScratchVm } from "../vm"; +import { saveCrtProject } from "../vm/save-crt-project"; +import { loadCrtProject } from "../vm/load-crt-project"; const respondToMessageEvent = ( event: MessageEvent, @@ -79,7 +81,7 @@ export const Solve = () => { break; case "getTask": if (vm) { - vm.saveProjectSb3().then((content) => { + saveCrtProject(vm).then((content) => { respondToMessageEvent(event, { procedure: "getTask", result: content, @@ -91,7 +93,7 @@ export const Solve = () => { if (vm) { const sb3Project = await message.arguments.arrayBuffer(); - vm.loadProject(sb3Project).then(() => { + loadCrtProject(vm, sb3Project).then(() => { respondToMessageEvent(event, { procedure: "loadTask", }); @@ -122,7 +124,6 @@ export const Solve = () => { return ( void; }) => storageInstance.addOfficialScratchWebStores()} diff --git a/apps/scratch/src/vm/default-crt-config.ts b/apps/scratch/src/vm/default-crt-config.ts new file mode 100644 index 0000000..456f0cb --- /dev/null +++ b/apps/scratch/src/vm/default-crt-config.ts @@ -0,0 +1,5 @@ +import { ScratchCrtConfig } from "scratch-vm"; + +export const defaultCrtConfig: ScratchCrtConfig = { + allowedBlocks: {}, +}; diff --git a/apps/scratch/src/vm/index.ts b/apps/scratch/src/vm/index.ts index 3df8996..812c52c 100644 --- a/apps/scratch/src/vm/index.ts +++ b/apps/scratch/src/vm/index.ts @@ -1,12 +1,7 @@ -import VM, { ScratchCrtConfig } from "scratch-vm"; -import JSZip from "jszip"; +import VM from "scratch-vm"; import { ExtensionId } from "../extensions"; import ExampleExtension from "../extensions/example"; -const defaultCrtConfig: ScratchCrtConfig = { - allowedBlocks: {}, -}; - export const patchScratchVm = (vm: VM): void => { // patch extension manager load function with a custom implementation vm.extensionManager.loadExtensionURL = async ( @@ -31,66 +26,6 @@ export const patchScratchVm = (vm: VM): void => { return Promise.resolve(0); }; - // modify the loadProject function to parse our additional project data - const originalLoadProject = vm.loadProject.bind(vm); - - /** - * Load a Scratch project from a .sb, .sb2, .sb3 or json string. - * @param input A json string, object, or ArrayBuffer representing the project to load. - * @return Promise that resolves after targets are installed. - */ - vm.loadProject = async ( - input: ArrayBufferView | ArrayBuffer | string | object, - ): Promise => { - // overwrite any existing config - vm.crtConfig = { - ...defaultCrtConfig, - }; - - if (input instanceof ArrayBuffer) { - const zip = new JSZip(); - await zip.loadAsync(input); - - const configFile = zip.file("crt.json"); - - if (configFile) { - // if the project contains a crt.json file, we parse it - const config: ScratchCrtConfig = await configFile - .async("text") - .then((text) => JSON.parse(text)); - - vm.crtConfig = config; - } - } - - return originalLoadProject(input); - }; - - // modify the saveProjectSb3 function to include our additional project data - const originalSaveProjectSb3 = vm.saveProjectSb3.bind(vm); - - /** - * @returns Project in a Scratch 3.0 JSON representation. - */ - vm.saveProjectSb3 = async (): Promise => { - const blob = await originalSaveProjectSb3(); - - const zip = new JSZip(); - await zip.loadAsync(blob); - - zip.file("crt.json", JSON.stringify(vm.crtConfig)); - - return zip.generateAsync({ - // options consistent with https://github.com/scratchfoundation/scratch-vm/blob/766c767c7a2f3da432480ade515de0a9f98804ba/src/virtual-machine.js#L400C19-L407C12 - type: "blob", - mimeType: "application/x.scratch.sb3", - compression: "DEFLATE", - compressionOptions: { - level: 6, - }, - }); - }; - // add custom callback to when the greenFlag event is triggered vm.runtime.on("PROJECT_START", () => { console.log("Green flag clicked"); diff --git a/apps/scratch/src/vm/load-crt-project.ts b/apps/scratch/src/vm/load-crt-project.ts new file mode 100644 index 0000000..515afff --- /dev/null +++ b/apps/scratch/src/vm/load-crt-project.ts @@ -0,0 +1,36 @@ +import VM, { ScratchCrtConfig } from "scratch-vm"; +import { defaultCrtConfig } from "./default-crt-config"; +import JSZip from "jszip"; + +/** + * Load a Scratch project from a .sb, .sb2, .sb3 or json string. + * @param input A json string, object, or ArrayBuffer representing the project to load. + * @return Promise that resolves after targets are installed. + */ +export const loadCrtProject = async ( + vm: VM, + input: ArrayBufferView | ArrayBuffer | string | object, +): Promise => { + // overwrite any existing config + vm.crtConfig = { + ...defaultCrtConfig, + }; + + if (input instanceof ArrayBuffer) { + const zip = new JSZip(); + await zip.loadAsync(input); + + const configFile = zip.file("crt.json"); + + if (configFile) { + // if the project contains a crt.json file, we parse it + const config: ScratchCrtConfig = await configFile + .async("text") + .then((text) => JSON.parse(text)); + + vm.crtConfig = config; + } + } + + return vm.loadProject(input); +}; diff --git a/apps/scratch/src/vm/save-crt-project.ts b/apps/scratch/src/vm/save-crt-project.ts new file mode 100644 index 0000000..38dee64 --- /dev/null +++ b/apps/scratch/src/vm/save-crt-project.ts @@ -0,0 +1,24 @@ +import VM from "scratch-vm"; +import JSZip from "jszip"; + +/** + * @returns Project in a Scratch 3.0 JSON representation. + */ +export const saveCrtProject = async (vm: VM): Promise => { + const blob = await vm.saveProjectSb3(); + + const zip = new JSZip(); + await zip.loadAsync(blob); + + zip.file("crt.json", JSON.stringify(vm.crtConfig)); + + return zip.generateAsync({ + // options consistent with https://github.com/scratchfoundation/scratch-vm/blob/766c767c7a2f3da432480ade515de0a9f98804ba/src/virtual-machine.js#L400C19-L407C12 + type: "blob", + mimeType: "application/x.scratch.sb3", + compression: "DEFLATE", + compressionOptions: { + level: 6, + }, + }); +};