Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 35 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"react-dom": "^19.1.0",
"react-i18next": "^15.6.0",
"react-syntax-highlighter": "^15.6.1",
"semver": "^7.7.2",
"typescript": "^5.9.2",
"web-vitals": "^5.0.3"
},
Expand Down Expand Up @@ -58,6 +59,7 @@
"@shadcn/ui": "^0.0.4",
"@types/node": "^24.0.15",
"@types/react-syntax-highlighter": "^15.5.13",
"@types/semver": "^7.7.0",
"@vitejs/plugin-react": "^4.7.0",
"autoprefixer": "^10.4.21",
"playwright": "^1.54.1",
Expand Down
11 changes: 7 additions & 4 deletions src/editor/editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,15 @@ export class Editor {
if (this.currentModule && this.currentProject) {
// Fetch the content for the current module, the robot, and the mechanisms.
const promises: { [modulePath: string]: Promise<string> } = {}; // value is promise of module content.
promises[this.modulePath] = this.storage.fetchModuleContentText(this.modulePath);
promises[this.modulePath] = this.storage.fetchFileContentText(this.modulePath);
if (this.robotPath !== this.modulePath) {
// Also fetch the robot module content. It contains components, etc, that can be used in OpModes.
promises[this.robotPath] = this.storage.fetchModuleContentText(this.robotPath)
promises[this.robotPath] = this.storage.fetchFileContentText(this.robotPath)
}
for (const mechanism of this.currentProject.mechanisms) {
// Fetch the module content text for the mechanism.
if (mechanism.modulePath !== this.modulePath) {
promises[mechanism.modulePath] = this.storage.fetchModuleContentText(mechanism.modulePath)
promises[mechanism.modulePath] = this.storage.fetchFileContentText(mechanism.modulePath);
}
}

Expand Down Expand Up @@ -318,8 +318,11 @@ export class Editor {
public async saveBlocks() {
const moduleContentText = this.getModuleContentText();
try {
await this.storage.saveModule(this.modulePath, moduleContentText);
await this.storage.saveFile(this.modulePath, moduleContentText);
this.moduleContentText = moduleContentText;
if (this.currentProject) {
await storageProject.saveProjectInfo(this.storage, this.currentProject.projectName);
}
} catch (e) {
throw e;
}
Expand Down
82 changes: 41 additions & 41 deletions src/storage/client_side_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ import * as commonStorage from './common_storage';
import * as storageModuleContent from './module_content';
import * as storageNames from './names';

// Functions for saving blocks modules to client side storage.
// Functions for saving blocks files to client side storage.

const DATABASE_NAME = 'systemcore-blocks-interface';

const ENTRIES_STORE_NAME = 'entries';
const ENTRIES_KEY = 'key';

const MODULES_STORE_NAME = 'modules';
const MODULES_KEY = 'path';
const FILES_STORE_NAME = 'modules';
const FILES_KEY = 'path';

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

if (!stores.contains(MODULES_STORE_NAME)) {
// Create the object store for modules.
db.createObjectStore(MODULES_STORE_NAME, { keyPath: MODULES_KEY });
if (!stores.contains(FILES_STORE_NAME)) {
// Create the object store for files.
db.createObjectStore(FILES_STORE_NAME, { keyPath: FILES_KEY });
}
};
openRequest.onsuccess = () => {
Expand All @@ -69,16 +69,16 @@ export async function openClientSideStorage(): Promise<commonStorage.Storage> {
// TODO(lizlooney): Remove this function.
async function fixOldModules(db: IDBDatabase): Promise<void> {
return new Promise((resolve, reject) => {
const transaction = db.transaction([MODULES_STORE_NAME], 'readwrite');
const transaction = db.transaction([FILES_STORE_NAME], 'readwrite');
transaction.oncomplete = () => {
resolve();
};
transaction.onabort = () => {
console.log('IndexedDB transaction aborted.');
reject(new Error('IndexedDB transaction aborted.'));
};
const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME);
const openCursorRequest = modulesObjectStore.openCursor();
const filesObjectStore = transaction.objectStore(FILES_STORE_NAME);
const openCursorRequest = filesObjectStore.openCursor();
openCursorRequest.onerror = () => {
console.log('IndexedDB openCursor request failed. openCursorRequest.error is...');
console.log(openCursorRequest.error);
Expand All @@ -96,13 +96,13 @@ async function fixOldModules(db: IDBDatabase): Promise<void> {
const className = result[2];
const moduleType = storageModuleContent.parseModuleContentText(value.content).getModuleType();
value.path = storageNames.makeModulePath(projectName, className, moduleType);
const putRequest = modulesObjectStore.put(value);
const putRequest = filesObjectStore.put(value);
putRequest.onerror = () => {
console.log('IndexedDB put request failed. putRequest.error is...');
console.log(putRequest.error);
throw new Error('IndexedDB put request failed.');
};
const deleteRequest = modulesObjectStore.delete(oldModulePath);
const deleteRequest = filesObjectStore.delete(oldModulePath);
deleteRequest.onerror = () => {
console.log('IndexedDB delete request failed. deleteRequest.error is...');
console.log(deleteRequest.error);
Expand All @@ -111,7 +111,7 @@ async function fixOldModules(db: IDBDatabase): Promise<void> {
}
cursor.continue();
} else {
// The cursor is done. We have finished reading all the modules.
// The cursor is done. We have finished reading all the files.
resolve();
}
};
Expand Down Expand Up @@ -181,15 +181,15 @@ class ClientSideStorage implements commonStorage.Storage {
});
}

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

const regExp = opt_modulePathRegexPattern
? new RegExp(opt_modulePathRegexPattern)
const regExp = opt_filePathRegexPattern
? new RegExp(opt_filePathRegexPattern)
: null;
return new Promise((resolve, reject) => {
const modulePaths: string[] = [];
const openKeyCursorRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly')
.objectStore(MODULES_STORE_NAME)
const filePaths: string[] = [];
const openKeyCursorRequest = this.db.transaction([FILES_STORE_NAME], 'readonly')
.objectStore(FILES_STORE_NAME)
.openKeyCursor();
openKeyCursorRequest.onerror = () => {
console.log('IndexedDB openKeyCursor request failed. openKeyCursorRequest.error is...');
Expand All @@ -199,51 +199,51 @@ class ClientSideStorage implements commonStorage.Storage {
openKeyCursorRequest.onsuccess = () => {
const cursor = openKeyCursorRequest.result;
if (cursor && cursor.key) {
const modulePath: string = cursor.key as string;
if (!regExp || regExp.test(modulePath)) {
modulePaths.push(modulePath);
const filePath: string = cursor.key as string;
if (!regExp || regExp.test(filePath)) {
filePaths.push(filePath);
}
cursor.continue();
} else {
// The cursor is done. We have finished reading all the modules.
resolve(modulePaths);
// The cursor is done. We have finished reading all the files.
resolve(filePaths);
}
};
});
}

async fetchModuleContentText(modulePath: string): Promise<string> {
async fetchFileContentText(filePath: string): Promise<string> {
return new Promise((resolve, reject) => {
const getRequest = this.db.transaction([MODULES_STORE_NAME], 'readonly')
.objectStore(MODULES_STORE_NAME).get(modulePath);
const getRequest = this.db.transaction([FILES_STORE_NAME], 'readonly')
.objectStore(FILES_STORE_NAME).get(filePath);
getRequest.onerror = () => {
console.log('IndexedDB get request failed. getRequest.error is...');
console.log(getRequest.error);
reject(new Error('IndexedDB get request failed.'));
};
getRequest.onsuccess = () => {
if (getRequest.result === undefined) {
// Module does not exist.
reject(new Error('IndexedDB get request succeeded, but the module does not exist.'));
// File does not exist.
reject(new Error('IndexedDB get request succeeded, but the file does not exist.'));
return;
}
resolve(getRequest.result.content);
};
});
}

async saveModule(modulePath: string, moduleContentText: string): Promise<void> {
async saveFile(filePath: string, fileContentText: string): Promise<void> {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite');
const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite');
transaction.oncomplete = () => {
resolve();
};
transaction.onabort = () => {
console.log('IndexedDB transaction aborted.');
reject(new Error('IndexedDB transaction aborted.'));
};
const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME);
const getRequest = modulesObjectStore.get(modulePath);
const filesObjectStore = transaction.objectStore(FILES_STORE_NAME);
const getRequest = filesObjectStore.get(filePath);
getRequest.onerror = () => {
console.log('IndexedDB get request failed. getRequest.error is...');
console.log(getRequest.error);
Expand All @@ -252,15 +252,15 @@ class ClientSideStorage implements commonStorage.Storage {
getRequest.onsuccess = () => {
let value;
if (getRequest.result === undefined) {
// The module does not exist. Create it now.
// The file does not exist. Create it now.
value = Object.create(null);
value.path = modulePath;
value.path = filePath;
} else {
// The module already exists.
// The file already exists.
value = getRequest.result;
}
value.content = moduleContentText;
const putRequest = modulesObjectStore.put(value);
value.content = fileContentText;
const putRequest = filesObjectStore.put(value);
putRequest.onerror = () => {
console.log('IndexedDB put request failed. putRequest.error is...');
console.log(putRequest.error);
Expand All @@ -270,18 +270,18 @@ class ClientSideStorage implements commonStorage.Storage {
});
}

async deleteModule(modulePath: string): Promise<void> {
async deleteFile(filePath: string): Promise<void> {
return new Promise((resolve, reject) => {
const transaction = this.db.transaction([MODULES_STORE_NAME], 'readwrite');
const transaction = this.db.transaction([FILES_STORE_NAME], 'readwrite');
transaction.oncomplete = () => {
resolve();
};
transaction.onabort = () => {
console.log('IndexedDB transaction aborted.');
reject(new Error('IndexedDB transaction aborted.'));
};
const modulesObjectStore = transaction.objectStore(MODULES_STORE_NAME);
const deleteRequest = modulesObjectStore.delete(modulePath);
const filesObjectStore = transaction.objectStore(FILES_STORE_NAME);
const deleteRequest = filesObjectStore.delete(filePath);
deleteRequest.onerror = () => {
console.log('IndexedDB delete request failed. deleteRequest.error is...');
console.log(deleteRequest.error);
Expand Down
10 changes: 5 additions & 5 deletions src/storage/common_storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,13 @@ export interface Storage {

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

// Functions for storing modules.
// Functions for storing files.

listModulePaths(opt_modulePathRegexPattern?: string): Promise<string[]>;
listFilePaths(opt_filePathRegexPattern?: string): Promise<string[]>;

fetchModuleContentText(modulePath: string): Promise<string>;
fetchFileContentText(filePath: string): Promise<string>;

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

deleteModule(modulePath: string): Promise<void>;
deleteFile(filePath: string): Promise<void>;
}
2 changes: 1 addition & 1 deletion src/storage/create_python_files.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ async function generatePythonForModule(module: Module, storage: Storage): Promis

try {
// Fetch the module content from storage
const moduleContentText = await storage.fetchModuleContentText(module.modulePath);
const moduleContentText = await storage.fetchFileContentText(module.modulePath);
const moduleContent = parseModuleContentText(moduleContentText);

// Create a headless workspace
Expand Down
9 changes: 9 additions & 0 deletions src/storage/module_content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,15 @@ export function makeModuleContentText(
export function parseModuleContentText(moduleContentText: string): ModuleContent {
const parsedContent = JSON.parse(moduleContentText);
fixOldParsedContent(parsedContent);
if (!('moduleType' in parsedContent) ||
!('moduleId' in parsedContent) ||
!('blocks' in parsedContent) ||
!('mechanisms' in parsedContent) ||
!('components' in parsedContent) ||
!('events' in parsedContent) ||
!('methods' in parsedContent)) {
throw new Error('Module content text is not valid.');
}
return new ModuleContent(
parsedContent.moduleType,
parsedContent.moduleId,
Expand Down
Loading