@@ -6,7 +6,11 @@ import {connect} from 'react-redux';
66import log from '../lib/log' ;
77import sharedMessages from './shared-messages' ;
88
9- import { extractScratchProjectId } from './url-parser' ;
9+ import {
10+ extractScratchProjectId ,
11+ extractGoogleDriveFileId ,
12+ getUrlType
13+ } from './url-parser' ;
1014
1115import {
1216 LoadingStates ,
@@ -57,8 +61,10 @@ const URLLoaderHOC = function (WrappedComponent) {
5761 bindAll ( this , [
5862 'handleStartSelectingUrlLoad' ,
5963 'handleUrlSubmit' ,
60- 'loadProjectFromUrl' ,
61- 'handleFinishedLoadingUpload'
64+ 'loadScratchProjectFromUrl' ,
65+ 'loadGoogleDriveProjectFromUrl' ,
66+ 'handleFinishedLoadingUpload' ,
67+ 'clearLoadingReferences'
6268 ] ) ;
6369 }
6470 componentDidUpdate ( prevProps ) {
@@ -83,18 +89,25 @@ const URLLoaderHOC = function (WrappedComponent) {
8389 userOwnsProject
8490 } = this . props ;
8591
86- const projectId = extractScratchProjectId ( url ) ;
87- if ( ! projectId ) {
92+ const urlType = getUrlType ( url ) ;
93+ if ( ! urlType ) {
8894 // Instead of alert, pass error to modal via callback
8995 if ( errorCallback ) {
9096 errorCallback ( intl . formatMessage ( messages . invalidUrl ) ) ;
9197 }
9298 return ;
9399 }
94100
95- this . projectIdToLoad = projectId ;
101+ // Store URL type and appropriate identifier
102+ this . urlType = urlType ;
96103 this . projectUrlToLoad = url ;
97104
105+ if ( urlType === 'scratch' ) {
106+ this . projectIdToLoad = extractScratchProjectId ( url ) ;
107+ } else if ( urlType === 'google-drive' ) {
108+ this . googleDriveFileId = extractGoogleDriveFileId ( url ) ;
109+ }
110+
98111 // If user owns the project, or user has changed the project,
99112 // we must confirm with the user that they really intend to
100113 // replace it.
@@ -118,15 +131,18 @@ const URLLoaderHOC = function (WrappedComponent) {
118131
119132 // Step 3: Load project from URL (called from componentDidUpdate)
120133 handleFinishedLoadingUpload ( ) {
121- if ( this . projectIdToLoad ) {
122- this . loadProjectFromUrl ( this . projectIdToLoad ) ;
134+ if ( this . urlType === 'scratch' && this . projectIdToLoad ) {
135+ this . loadScratchProjectFromUrl ( this . projectIdToLoad ) ;
136+ return ;
137+ } else if ( this . urlType === 'google-drive' && this . googleDriveFileId ) {
138+ this . loadGoogleDriveProjectFromUrl ( this . googleDriveFileId ) ;
123139 return ;
124140 }
125141 this . props . cancelFileUpload ( this . props . loadingState ) ;
126142 }
127143
128- // Step 4: Actually load the project data
129- loadProjectFromUrl ( projectId ) {
144+ // Step 4a: Load Scratch project from URL
145+ loadScratchProjectFromUrl ( projectId ) {
130146 this . props . onLoadingStarted ( ) ;
131147
132148 // Set project ID in Redux state first (like project-fetcher-hoc.jsx)
@@ -184,25 +200,78 @@ const URLLoaderHOC = function (WrappedComponent) {
184200 . then ( ( ) => {
185201 this . props . onLoadingFinished ( ) ;
186202 // Clear the project reference
187- this . projectIdToLoad = null ;
188- this . projectUrlToLoad = null ;
203+ this . clearLoadingReferences ( ) ;
204+ } ) ;
205+ }
206+
207+ // Step 4b: Load Google Drive project from URL
208+ loadGoogleDriveProjectFromUrl ( fileId ) {
209+ this . props . onLoadingStarted ( ) ;
210+
211+ // Convert Google Drive URL to direct download URL via CORS proxy
212+ const directDownloadUrl = `https://drive.google.com/uc?export=download&id=${ fileId } ` ;
213+ const corsProxyUrl = `https://api.smalruby.app/cors-proxy?url=${ encodeURIComponent ( directDownloadUrl ) } ` ;
214+
215+ fetch ( corsProxyUrl )
216+ . then ( response => {
217+ if ( ! response . ok ) {
218+ throw new Error ( `HTTP ${ response . status } ` ) ;
219+ }
220+ return response . arrayBuffer ( ) ;
221+ } )
222+ . then ( arrayBuffer =>
223+ // Load SB3 file directly to VM (same as sb-file-uploader-hoc.jsx)
224+ this . props . vm . loadProject ( arrayBuffer )
225+ )
226+ . then ( ( ) => {
227+ // Set project title based on the Google Drive file
228+ const projectTitle = `Google Drive Project ${ fileId } ` ;
229+ this . props . onSetProjectTitle ( projectTitle ) ;
230+
231+ // Use onLoadedProject for LOADING_VM_FILE_UPLOAD state
232+ this . props . onLoadedProject ( this . props . loadingState , true , true ) ;
233+ } )
234+ . catch ( error => {
235+ log . warn ( 'Google Drive URL loader error:' , error ) ;
236+ this . props . onError ( error ) ;
237+ alert ( this . props . intl . formatMessage ( messages . loadError ) ) ; // eslint-disable-line no-alert
238+ } )
239+ . then ( ( ) => {
240+ this . props . onLoadingFinished ( ) ;
241+ // Clear the references
242+ this . clearLoadingReferences ( ) ;
189243 } ) ;
190244 }
191245
246+ // Clear all loading references
247+ clearLoadingReferences ( ) {
248+ this . urlType = null ;
249+ this . projectIdToLoad = null ;
250+ this . googleDriveFileId = null ;
251+ this . projectUrlToLoad = null ;
252+ }
253+
192254 render ( ) {
193255 const {
194256 /* eslint-disable no-unused-vars */
195257 cancelFileUpload,
196258 closeFileMenu : closeFileMenuProp ,
259+ closeUrlLoaderModal : closeUrlLoaderModalProp ,
260+ intl,
197261 isLoadingUpload,
198262 isShowingWithoutId,
199263 loadingState,
264+ onError,
265+ onLoadedProject : onLoadedProjectProp ,
200266 onLoadingFinished,
201267 onLoadingStarted,
202268 onSetProjectTitle,
269+ openUrlLoaderModal : openUrlLoaderModalProp ,
203270 projectChanged,
204271 requestProjectUpload : requestProjectUploadProp ,
272+ setProjectId : setProjectIdProp ,
205273 userOwnsProject,
274+ vm,
206275 onStartSelectingUrlLoad : onStartSelectingUrlLoadProp ,
207276 /* eslint-enable no-unused-vars */
208277 ...componentProps
@@ -212,6 +281,7 @@ const URLLoaderHOC = function (WrappedComponent) {
212281 < WrappedComponent
213282 onStartSelectingUrlLoad = { this . handleStartSelectingUrlLoad }
214283 onUrlLoaderSubmit = { this . handleUrlSubmit }
284+ vm = { vm }
215285 { ...componentProps }
216286 />
217287 </ React . Fragment >
0 commit comments