@@ -2,13 +2,19 @@ import { mapFileContentsToModel } from "@SpCore/Project/FileMapping";
22import loadFilesFromGist from "@SpUtil/gists/loadFilesFromGist" ;
33import {
44 ProjectDataModel ,
5+ ProjectKnownFiles ,
6+ SamplingOpts ,
57 defaultSamplingOpts ,
68 initialDataModel ,
79 parseSamplingOpts ,
810 persistStateToEphemera ,
911 validateSamplingOpts ,
1012} from "@SpCore/Project/ProjectDataModel" ;
11- import { loadFromProjectFiles } from "@SpCore/Project/ProjectSerialization" ;
13+ import {
14+ deserializeProjectFromURLParameter ,
15+ hasKnownProjectParameterPrefix ,
16+ loadFromProjectFiles ,
17+ } from "@SpCore/Project/ProjectSerialization" ;
1218import { tryFetch } from "@SpUtil/tryFetch" ;
1319
1420export enum QueryParamKeys {
@@ -60,145 +66,135 @@ export const fromQueryParams = (searchParams: URLSearchParams) => {
6066 return queries ;
6167} ;
6268
69+ const queryParamToDataModelFieldMap = {
70+ [ QueryParamKeys . StanFile ] : ProjectKnownFiles . STANFILE ,
71+ [ QueryParamKeys . DataFile ] : ProjectKnownFiles . DATAFILE ,
72+ [ QueryParamKeys . AnalysisPyFile ] : ProjectKnownFiles . ANALYSISPYFILE ,
73+ [ QueryParamKeys . AnalysisRFile ] : ProjectKnownFiles . ANALYSISRFILE ,
74+ [ QueryParamKeys . DataPyFile ] : ProjectKnownFiles . DATAPYFILE ,
75+ [ QueryParamKeys . DataRFile ] : ProjectKnownFiles . DATARFILE ,
76+ } as const ;
77+
6378export const queryStringHasParameters = ( query : QueryParams ) => {
6479 return Object . values ( query ) . some ( ( v ) => v !== null ) ;
6580} ;
6681
6782export const fetchRemoteProject = async ( query : QueryParams ) => {
68- const projectUri = query . project ;
69-
70- let data : ProjectDataModel = structuredClone ( initialDataModel ) ;
71- if ( projectUri ) {
72- if ( projectUri . startsWith ( "https://gist.github.com/" ) ) {
73- let contentLoadedFromGist : {
74- files : { [ key : string ] : string } ;
75- description : string ;
76- } ;
77- try {
78- contentLoadedFromGist = await loadFilesFromGist ( projectUri ) ;
79- } catch ( err ) {
80- console . error ( "Failed to load content from gist" , err ) ;
81- alert ( `Failed to load content from gist ${ projectUri } ` ) ;
82- // do not continue with any other query parameters if we failed to load the gist
83- return persistStateToEphemera ( data ) ;
84- }
85- data = loadFromProjectFiles (
86- data ,
87- mapFileContentsToModel ( contentLoadedFromGist . files ) ,
88- false ,
89- ) ;
90- data . meta . title = contentLoadedFromGist . description ;
91- return persistStateToEphemera ( data ) ;
92- } else {
93- // right now we only support loading from a gist
94- console . error ( "Unsupported project URI" , projectUri ) ;
95- }
83+ if ( query . project ) {
84+ // other parameters are ignored whenever project= is set
85+ return await loadFromProjectParameter ( query . project ) ;
9686 }
9787
98- const stanFilePromise = query . stan
99- ? tryFetch ( query . stan )
100- : Promise . resolve ( data . stanFileContent ) ;
101- const dataFilePromise = query . data
102- ? tryFetch ( query . data )
103- : Promise . resolve ( data . dataFileContent ) ;
104- const analysisPyFilePromise = query [ "analysis_py" ]
105- ? tryFetch ( query [ "analysis_py" ] )
106- : Promise . resolve ( data . analysisPyFileContent ) ;
107- const analysisRFilePromise = query [ "analysis_r" ]
108- ? tryFetch ( query [ "analysis_r" ] )
109- : Promise . resolve ( data . analysisRFileContent ) ;
110- const dataPyFilePromise = query [ "data_py" ]
111- ? tryFetch ( query [ "data_py" ] )
112- : Promise . resolve ( data . dataPyFileContent ) ;
113- const dataRFilePromise = query [ "data_r" ]
114- ? tryFetch ( query [ "data_r" ] )
115- : Promise . resolve ( data . dataRFileContent ) ;
116- const sampling_optsPromise = query . sampling_opts
117- ? tryFetch ( query . sampling_opts )
118- : Promise . resolve ( null ) ;
119-
120- const stanFileContent = await stanFilePromise ;
121- if ( stanFileContent !== undefined ) {
122- data . stanFileContent = stanFileContent ;
123- } else {
124- data . stanFileContent = `// Failed to load content from ${ query . stan } ` ;
125- }
88+ const data : ProjectDataModel = structuredClone ( initialDataModel ) ;
12689
127- const dataFileContent = await dataFilePromise ;
128- if ( dataFileContent !== undefined ) {
129- data . dataFileContent = dataFileContent ;
130- } else {
131- data . dataFileContent = `// Failed to load content from ${ query . data } ` ;
90+ if ( query . title ) {
91+ data . meta . title = query . title ;
13292 }
13393
134- const analysisPyFileContent = await analysisPyFilePromise ;
135- if ( analysisPyFileContent !== undefined ) {
136- data . analysisPyFileContent = analysisPyFileContent ;
137- } else {
138- data . analysisPyFileContent = `# Failed to load content from ${ query [ "analysis_py" ] } ` ;
139- }
94+ const fetchFileForParameter = async (
95+ param : keyof typeof queryParamToDataModelFieldMap ,
96+ comment : string = "# " ,
97+ ) => {
98+ if ( query [ param ] ) {
99+ const value = await tryFetch ( query [ param ] ) ;
100+ return value ?? `${ comment } Failed to load content from ${ query [ param ] } ` ;
101+ }
102+ return data [ queryParamToDataModelFieldMap [ param ] ] ;
103+ } ;
140104
141- const analysisRFileContent = await analysisRFilePromise ;
142- if ( analysisRFileContent !== undefined ) {
143- data . analysisRFileContent = analysisRFileContent ;
144- } else {
145- data . analysisRFileContent = `# Failed to load content from ${ query [ "analysis_r" ] } ` ;
146- }
105+ [
106+ data . stanFileContent ,
107+ data . dataFileContent ,
108+ data . analysisPyFileContent ,
109+ data . analysisRFileContent ,
110+ data . dataPyFileContent ,
111+ data . dataRFileContent ,
112+ data . samplingOpts ,
113+ ] = await Promise . all ( [
114+ fetchFileForParameter ( QueryParamKeys . StanFile , "// " ) ,
115+ fetchFileForParameter ( QueryParamKeys . DataFile , "// " ) ,
116+ fetchFileForParameter ( QueryParamKeys . AnalysisPyFile ) ,
117+ fetchFileForParameter ( QueryParamKeys . AnalysisRFile ) ,
118+ fetchFileForParameter ( QueryParamKeys . DataPyFile ) ,
119+ fetchFileForParameter ( QueryParamKeys . DataRFile ) ,
120+ loadSamplingOptsFromQueryParams ( query ) ,
121+ ] ) ;
147122
148- const dataPyFileContent = await dataPyFilePromise ;
149- if ( dataPyFileContent !== undefined ) {
150- data . dataPyFileContent = dataPyFileContent ;
151- } else {
152- data . dataPyFileContent = `# Failed to load content from ${ query [ "data_py" ] } ` ;
153- }
123+ return persistStateToEphemera ( data ) ;
124+ } ;
154125
155- const dataRFileContent = await dataRFilePromise ;
156- if ( dataRFileContent !== undefined ) {
157- data . dataRFileContent = dataRFileContent ;
126+ const loadFromProjectParameter = async ( projectParam : string ) => {
127+ if ( projectParam . startsWith ( "https://gist.github.com/" ) ) {
128+ try {
129+ const contentLoadedFromGist = await loadFilesFromGist ( projectParam ) ;
130+ const dataFromGist = loadFromProjectFiles (
131+ mapFileContentsToModel ( contentLoadedFromGist . files ) ,
132+ ) ;
133+ dataFromGist . meta . title = contentLoadedFromGist . description ;
134+ return persistStateToEphemera ( dataFromGist ) ;
135+ } catch ( err ) {
136+ console . error ( "Failed to load content from gist" , err ) ;
137+ alert ( `Failed to load content from gist ${ projectParam } ` ) ;
138+ }
139+ } else if ( hasKnownProjectParameterPrefix ( projectParam ) ) {
140+ try {
141+ const dataFromParam = deserializeProjectFromURLParameter ( projectParam ) ;
142+ if ( dataFromParam ) {
143+ return persistStateToEphemera ( dataFromParam ) ;
144+ } else {
145+ throw new Error ( "Failed to deserialize project from URL parameter" ) ;
146+ }
147+ } catch ( err ) {
148+ console . error ( "Failed to load content from project string" , err ) ;
149+ alert ( "Failed to load content from compressed project" ) ;
150+ }
158151 } else {
159- data . dataRFileContent = `# Failed to load content from ${ query [ "data_r" ] } ` ;
152+ console . error ( "Unsupported project parameter type" , projectParam ) ;
160153 }
154+ return initialDataModel ;
155+ } ;
161156
162- const sampling_opts = await sampling_optsPromise ;
163- if ( sampling_opts === undefined ) {
164- const msg = `Failed to load content from ${ query [ "sampling_opts" ] } ` ;
165- alert ( msg ) ;
166- console . error ( msg ) ;
167- } else if ( sampling_opts !== null ) {
157+ const loadSamplingOptsFromQueryParams = async ( query : QueryParams ) => {
158+ // try to load json of all opts
159+ if ( query . sampling_opts ) {
160+ const sampling_opts = await tryFetch ( query . sampling_opts ) ;
161+ if ( sampling_opts === undefined ) {
162+ const msg = `Failed to load content from ${ query [ "sampling_opts" ] } ` ;
163+ alert ( msg ) ;
164+ console . error ( msg ) ;
165+ return defaultSamplingOpts ;
166+ }
168167 try {
169- data . samplingOpts = parseSamplingOpts ( sampling_opts ) ;
168+ return parseSamplingOpts ( sampling_opts ) ;
170169 } catch ( err ) {
171170 console . error ( "Failed to parse sampling_opts" , err ) ;
172171 alert ( "Invalid sampling options: " + sampling_opts ) ;
173172 }
174- } else {
175- if ( query . num_chains ) {
176- data . samplingOpts . num_chains = parseInt ( query . num_chains ) ;
177- }
178- if ( query . num_warmup ) {
179- data . samplingOpts . num_warmup = parseInt ( query . num_warmup ) ;
180- }
181- if ( query . num_samples ) {
182- data . samplingOpts . num_samples = parseInt ( query . num_samples ) ;
183- }
184- if ( query . init_radius ) {
185- data . samplingOpts . init_radius = parseFloat ( query . init_radius ) ;
186- }
187- if ( query . seed ) {
188- data . samplingOpts . seed =
189- query . seed === "undefined" ? undefined : parseInt ( query . seed ) ;
190- }
191-
192- if ( ! validateSamplingOpts ( data . samplingOpts ) ) {
193- console . error ( "Invalid sampling options" , data . samplingOpts ) ;
194- alert ( "Invalid sampling options: " + JSON . stringify ( data . samplingOpts ) ) ;
195- data . samplingOpts = defaultSamplingOpts ;
196- }
197173 }
198174
199- if ( query . title ) {
200- data . meta . title = query . title ;
175+ // load individual opts from query params
176+ const samplingOpts : SamplingOpts = { ...defaultSamplingOpts } ;
177+ if ( query . num_chains ) {
178+ samplingOpts . num_chains = parseInt ( query . num_chains ) ;
179+ }
180+ if ( query . num_warmup ) {
181+ samplingOpts . num_warmup = parseInt ( query . num_warmup ) ;
182+ }
183+ if ( query . num_samples ) {
184+ samplingOpts . num_samples = parseInt ( query . num_samples ) ;
185+ }
186+ if ( query . init_radius ) {
187+ samplingOpts . init_radius = parseFloat ( query . init_radius ) ;
188+ }
189+ if ( query . seed ) {
190+ samplingOpts . seed =
191+ query . seed === "undefined" ? undefined : parseInt ( query . seed ) ;
201192 }
202193
203- return persistStateToEphemera ( data ) ;
194+ if ( ! validateSamplingOpts ( samplingOpts ) ) {
195+ console . error ( "Invalid sampling options" , samplingOpts ) ;
196+ alert ( "Invalid sampling options: " + JSON . stringify ( samplingOpts ) ) ;
197+ return defaultSamplingOpts ;
198+ }
199+ return samplingOpts ;
204200} ;
0 commit comments