Skip to content

Commit f1dc222

Browse files
authored
Store version number in project.info.json (#206)
* Updated the Storage interface to use the term "file" instead of "module". Replaced listModulePaths with listFilePaths. Replaced fetchModuleContentText with fetchFileContentText. Replaced saveModule with saveFile. Replaced deleteModule with deleteFile. * In names.ts: Added comments. Updated regular expressions so they work for modules and non-module files. Added functions makeFilePathRegexPattern, makeFilePath, makeProjectInfoPath, isValidModuleFileName, isValidProjectInfoFileName. * In project.ts: Added CURRENT_VERSION. Added type ProjectInfo. Added functions saveProjectInfo, and deleteProjectInfo. Call saveProjectInfo after saving or deleting a module. Call deleteProjectInfo when deleting a project. Updated downloadProject and uploadProject to download/upload all files in the project. Updated processUploadedBlob to handle the project.info.json file and module files. In module_content.ts: Updated parseModuleContentText to check module content. * Install npm package semver. In project.ts: Added functions fetchProjectInfo and updateProjectIfNecessary. Call updateProjectIfNecessary in fetchProject. * Update project.ts
1 parent ba2a496 commit f1dc222

File tree

9 files changed

+366
-143
lines changed

9 files changed

+366
-143
lines changed

package-lock.json

Lines changed: 35 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"react-dom": "^19.1.0",
2525
"react-i18next": "^15.6.0",
2626
"react-syntax-highlighter": "^15.6.1",
27+
"semver": "^7.7.2",
2728
"typescript": "^5.9.2",
2829
"web-vitals": "^5.0.3"
2930
},
@@ -58,6 +59,7 @@
5859
"@shadcn/ui": "^0.0.4",
5960
"@types/node": "^24.0.15",
6061
"@types/react-syntax-highlighter": "^15.5.13",
62+
"@types/semver": "^7.7.0",
6163
"@vitejs/plugin-react": "^4.7.0",
6264
"autoprefixer": "^10.4.21",
6365
"playwright": "^1.54.1",

src/editor/editor.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,15 +137,15 @@ export class Editor {
137137
if (this.currentModule && this.currentProject) {
138138
// Fetch the content for the current module, the robot, and the mechanisms.
139139
const promises: { [modulePath: string]: Promise<string> } = {}; // value is promise of module content.
140-
promises[this.modulePath] = this.storage.fetchModuleContentText(this.modulePath);
140+
promises[this.modulePath] = this.storage.fetchFileContentText(this.modulePath);
141141
if (this.robotPath !== this.modulePath) {
142142
// Also fetch the robot module content. It contains components, etc, that can be used in OpModes.
143-
promises[this.robotPath] = this.storage.fetchModuleContentText(this.robotPath)
143+
promises[this.robotPath] = this.storage.fetchFileContentText(this.robotPath)
144144
}
145145
for (const mechanism of this.currentProject.mechanisms) {
146146
// Fetch the module content text for the mechanism.
147147
if (mechanism.modulePath !== this.modulePath) {
148-
promises[mechanism.modulePath] = this.storage.fetchModuleContentText(mechanism.modulePath)
148+
promises[mechanism.modulePath] = this.storage.fetchFileContentText(mechanism.modulePath);
149149
}
150150
}
151151

@@ -318,8 +318,11 @@ export class Editor {
318318
public async saveBlocks() {
319319
const moduleContentText = this.getModuleContentText();
320320
try {
321-
await this.storage.saveModule(this.modulePath, moduleContentText);
321+
await this.storage.saveFile(this.modulePath, moduleContentText);
322322
this.moduleContentText = moduleContentText;
323+
if (this.currentProject) {
324+
await storageProject.saveProjectInfo(this.storage, this.currentProject.projectName);
325+
}
323326
} catch (e) {
324327
throw e;
325328
}

src/storage/client_side_storage.ts

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ import * as commonStorage from './common_storage';
2323
import * as storageModuleContent from './module_content';
2424
import * as storageNames from './names';
2525

26-
// Functions for saving blocks modules to client side storage.
26+
// Functions for saving blocks files to client side storage.
2727

2828
const DATABASE_NAME = 'systemcore-blocks-interface';
2929

3030
const ENTRIES_STORE_NAME = 'entries';
3131
const ENTRIES_KEY = 'key';
3232

33-
const MODULES_STORE_NAME = 'modules';
34-
const MODULES_KEY = 'path';
33+
const FILES_STORE_NAME = 'modules';
34+
const FILES_KEY = 'path';
3535

3636
export async function openClientSideStorage(): Promise<commonStorage.Storage> {
3737
return new Promise((resolve, reject) => {
@@ -51,9 +51,9 @@ export async function openClientSideStorage(): Promise<commonStorage.Storage> {
5151
db.createObjectStore(ENTRIES_STORE_NAME, { keyPath: ENTRIES_KEY });
5252
}
5353

54-
if (!stores.contains(MODULES_STORE_NAME)) {
55-
// Create the object store for modules.
56-
db.createObjectStore(MODULES_STORE_NAME, { keyPath: MODULES_KEY });
54+
if (!stores.contains(FILES_STORE_NAME)) {
55+
// Create the object store for files.
56+
db.createObjectStore(FILES_STORE_NAME, { keyPath: FILES_KEY });
5757
}
5858
};
5959
openRequest.onsuccess = () => {
@@ -69,16 +69,16 @@ export async function openClientSideStorage(): Promise<commonStorage.Storage> {
6969
// TODO(lizlooney): Remove this function.
7070
async function fixOldModules(db: IDBDatabase): Promise<void> {
7171
return new Promise((resolve, reject) => {
72-
const transaction = db.transaction([MODULES_STORE_NAME], 'readwrite');
72+
const transaction = db.transaction([FILES_STORE_NAME], 'readwrite');
7373
transaction.oncomplete = () => {
7474
resolve();
7575
};
7676
transaction.onabort = () => {
7777
console.log('IndexedDB transaction aborted.');
7878
reject(new Error('IndexedDB transaction aborted.'));
7979
};
80-
const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME);
81-
const openCursorRequest = modulesObjectStore.openCursor();
80+
const filesObjectStore = transaction.objectStore(FILES_STORE_NAME);
81+
const openCursorRequest = filesObjectStore.openCursor();
8282
openCursorRequest.onerror = () => {
8383
console.log('IndexedDB openCursor request failed. openCursorRequest.error is...');
8484
console.log(openCursorRequest.error);
@@ -96,13 +96,13 @@ async function fixOldModules(db: IDBDatabase): Promise<void> {
9696
const className = result[2];
9797
const moduleType = storageModuleContent.parseModuleContentText(value.content).getModuleType();
9898
value.path = storageNames.makeModulePath(projectName, className, moduleType);
99-
const putRequest = modulesObjectStore.put(value);
99+
const putRequest = filesObjectStore.put(value);
100100
putRequest.onerror = () => {
101101
console.log('IndexedDB put request failed. putRequest.error is...');
102102
console.log(putRequest.error);
103103
throw new Error('IndexedDB put request failed.');
104104
};
105-
const deleteRequest = modulesObjectStore.delete(oldModulePath);
105+
const deleteRequest = filesObjectStore.delete(oldModulePath);
106106
deleteRequest.onerror = () => {
107107
console.log('IndexedDB delete request failed. deleteRequest.error is...');
108108
console.log(deleteRequest.error);
@@ -111,7 +111,7 @@ async function fixOldModules(db: IDBDatabase): Promise<void> {
111111
}
112112
cursor.continue();
113113
} else {
114-
// The cursor is done. We have finished reading all the modules.
114+
// The cursor is done. We have finished reading all the files.
115115
resolve();
116116
}
117117
};
@@ -181,15 +181,15 @@ class ClientSideStorage implements commonStorage.Storage {
181181
});
182182
}
183183

184-
async listModulePaths(opt_modulePathRegexPattern?: string): Promise<string[]> {
184+
async listFilePaths(opt_filePathRegexPattern?: string): Promise<string[]> {
185185

186-
const regExp = opt_modulePathRegexPattern
187-
? new RegExp(opt_modulePathRegexPattern)
186+
const regExp = opt_filePathRegexPattern
187+
? new RegExp(opt_filePathRegexPattern)
188188
: null;
189189
return new Promise((resolve, reject) => {
190-
const modulePaths: string[] = [];
191-
const openKeyCursorRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly')
192-
.objectStore(MODULES_STORE_NAME)
190+
const filePaths: string[] = [];
191+
const openKeyCursorRequest = this.db.transaction([FILES_STORE_NAME], 'readonly')
192+
.objectStore(FILES_STORE_NAME)
193193
.openKeyCursor();
194194
openKeyCursorRequest.onerror = () => {
195195
console.log('IndexedDB openKeyCursor request failed. openKeyCursorRequest.error is...');
@@ -199,51 +199,51 @@ class ClientSideStorage implements commonStorage.Storage {
199199
openKeyCursorRequest.onsuccess = () => {
200200
const cursor = openKeyCursorRequest.result;
201201
if (cursor && cursor.key) {
202-
const modulePath: string = cursor.key as string;
203-
if (!regExp || regExp.test(modulePath)) {
204-
modulePaths.push(modulePath);
202+
const filePath: string = cursor.key as string;
203+
if (!regExp || regExp.test(filePath)) {
204+
filePaths.push(filePath);
205205
}
206206
cursor.continue();
207207
} else {
208-
// The cursor is done. We have finished reading all the modules.
209-
resolve(modulePaths);
208+
// The cursor is done. We have finished reading all the files.
209+
resolve(filePaths);
210210
}
211211
};
212212
});
213213
}
214214

215-
async fetchModuleContentText(modulePath: string): Promise<string> {
215+
async fetchFileContentText(filePath: string): Promise<string> {
216216
return new Promise((resolve, reject) => {
217-
const getRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly')
218-
.objectStore(MODULES_STORE_NAME).get(modulePath);
217+
const getRequest = this.db.transaction([FILES_STORE_NAME], 'readonly')
218+
.objectStore(FILES_STORE_NAME).get(filePath);
219219
getRequest.onerror = () => {
220220
console.log('IndexedDB get request failed. getRequest.error is...');
221221
console.log(getRequest.error);
222222
reject(new Error('IndexedDB get request failed.'));
223223
};
224224
getRequest.onsuccess = () => {
225225
if (getRequest.result === undefined) {
226-
// Module does not exist.
227-
reject(new Error('IndexedDB get request succeeded, but the module does not exist.'));
226+
// File does not exist.
227+
reject(new Error('IndexedDB get request succeeded, but the file does not exist.'));
228228
return;
229229
}
230230
resolve(getRequest.result.content);
231231
};
232232
});
233233
}
234234

235-
async saveModule(modulePath: string, moduleContentText: string): Promise<void> {
235+
async saveFile(filePath: string, fileContentText: string): Promise<void> {
236236
return new Promise((resolve, reject) => {
237-
const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite');
237+
const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite');
238238
transaction.oncomplete = () => {
239239
resolve();
240240
};
241241
transaction.onabort = () => {
242242
console.log('IndexedDB transaction aborted.');
243243
reject(new Error('IndexedDB transaction aborted.'));
244244
};
245-
const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME);
246-
const getRequest = modulesObjectStore.get(modulePath);
245+
const filesObjectStore = transaction.objectStore(FILES_STORE_NAME);
246+
const getRequest = filesObjectStore.get(filePath);
247247
getRequest.onerror = () => {
248248
console.log('IndexedDB get request failed. getRequest.error is...');
249249
console.log(getRequest.error);
@@ -252,15 +252,15 @@ class ClientSideStorage implements commonStorage.Storage {
252252
getRequest.onsuccess = () => {
253253
let value;
254254
if (getRequest.result === undefined) {
255-
// The module does not exist. Create it now.
255+
// The file does not exist. Create it now.
256256
value = Object.create(null);
257-
value.path = modulePath;
257+
value.path = filePath;
258258
} else {
259-
// The module already exists.
259+
// The file already exists.
260260
value = getRequest.result;
261261
}
262-
value.content = moduleContentText;
263-
const putRequest = modulesObjectStore.put(value);
262+
value.content = fileContentText;
263+
const putRequest = filesObjectStore.put(value);
264264
putRequest.onerror = () => {
265265
console.log('IndexedDB put request failed. putRequest.error is...');
266266
console.log(putRequest.error);
@@ -270,18 +270,18 @@ class ClientSideStorage implements commonStorage.Storage {
270270
});
271271
}
272272

273-
async deleteModule(modulePath: string): Promise<void> {
273+
async deleteFile(filePath: string): Promise<void> {
274274
return new Promise((resolve, reject) => {
275-
const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite');
275+
const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite');
276276
transaction.oncomplete = () => {
277277
resolve();
278278
};
279279
transaction.onabort = () => {
280280
console.log('IndexedDB transaction aborted.');
281281
reject(new Error('IndexedDB transaction aborted.'));
282282
};
283-
const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME);
284-
const deleteRequest = modulesObjectStore.delete(modulePath);
283+
const filesObjectStore = transaction.objectStore(FILES_STORE_NAME);
284+
const deleteRequest = filesObjectStore.delete(filePath);
285285
deleteRequest.onerror = () => {
286286
console.log('IndexedDB delete request failed. deleteRequest.error is...');
287287
console.log(deleteRequest.error);

src/storage/common_storage.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ export interface Storage {
2626

2727
fetchEntry(entryKey: string, defaultValue: string): Promise<string>;
2828

29-
// Functions for storing modules.
29+
// Functions for storing files.
3030

31-
listModulePaths(opt_modulePathRegexPattern?: string): Promise<string[]>;
31+
listFilePaths(opt_filePathRegexPattern?: string): Promise<string[]>;
3232

33-
fetchModuleContentText(modulePath: string): Promise<string>;
33+
fetchFileContentText(filePath: string): Promise<string>;
3434

35-
saveModule(modulePath: string, moduleContentText: string): Promise<void>;
35+
saveFile(filePath: string, fileContentText: string): Promise<void>;
3636

37-
deleteModule(modulePath: string): Promise<void>;
37+
deleteFile(filePath: string): Promise<void>;
3838
}

src/storage/create_python_files.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ async function generatePythonForModule(module: Module, storage: Storage): Promis
5858

5959
try {
6060
// Fetch the module content from storage
61-
const moduleContentText = await storage.fetchModuleContentText(module.modulePath);
61+
const moduleContentText = await storage.fetchFileContentText(module.modulePath);
6262
const moduleContent = parseModuleContentText(moduleContentText);
6363

6464
// Create a headless workspace

src/storage/module_content.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,15 @@ export function makeModuleContentText(
147147
export function parseModuleContentText(moduleContentText: string): ModuleContent {
148148
const parsedContent = JSON.parse(moduleContentText);
149149
fixOldParsedContent(parsedContent);
150+
if (!('moduleType' in parsedContent) ||
151+
!('moduleId' in parsedContent) ||
152+
!('blocks' in parsedContent) ||
153+
!('mechanisms' in parsedContent) ||
154+
!('components' in parsedContent) ||
155+
!('events' in parsedContent) ||
156+
!('methods' in parsedContent)) {
157+
throw new Error('Module content text is not valid.');
158+
}
150159
return new ModuleContent(
151160
parsedContent.moduleType,
152161
parsedContent.moduleId,

0 commit comments

Comments
 (0)