Skip to content

Commit cefe0fa

Browse files
committed
Make new project name dialog ask for a PascalCase style class name.
1 parent ca4e1aa commit cefe0fa

File tree

4 files changed

+117
-12
lines changed

4 files changed

+117
-12
lines changed

src/App.tsx

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,7 @@ const App: React.FC = () => {
263263
});
264264
const parent: TreeDataNode = {
265265
key: project.modulePath,
266-
title: project.projectName,
266+
title: project.className,
267267
children: children,
268268
icon: <ProjectOutlined />,
269269
};
@@ -437,6 +437,14 @@ const App: React.FC = () => {
437437
});
438438
};
439439

440+
const getProjectClassNames = (): string[] => {
441+
const projectClassNames: string[] = [];
442+
modules.forEach((project) => {
443+
projectClassNames.push(project.className);
444+
});
445+
return projectClassNames;
446+
};
447+
440448
const getProjectNames = (): string[] => {
441449
const projectNames: string[] = [];
442450
modules.forEach((project) => {
@@ -445,10 +453,12 @@ const App: React.FC = () => {
445453
return projectNames;
446454
};
447455

448-
const handleNewProjectNameOk = async (newProjectName: string) => {
456+
const handleNewProjectNameOk = async (newProjectClassName: string) => {
457+
const newProjectName = commonStorage.classNameToModuleName(newProjectClassName);
449458
const newProjectPath = commonStorage.makeProjectPath(newProjectName);
450459
if (newProjectNameModalPurpose === PURPOSE_NEW_PROJECT) {
451-
const projectContent = commonStorage.newProjectContent(newProjectName);
460+
const projectContent = commonStorage.newProjectContent(
461+
newProjectClassName, newProjectName, newProjectPath);
452462
try {
453463
await storage.createModule(
454464
commonStorage.MODULE_TYPE_PROJECT, newProjectPath, projectContent);
@@ -1078,7 +1088,7 @@ const App: React.FC = () => {
10781088
title={newProjectNameModalTitle}
10791089
isOpen={newProjectNameModalIsOpen}
10801090
initialValue={newProjectNameModalInitialValue}
1081-
getProjectNames={getProjectNames}
1091+
getProjectClassNames={getProjectClassNames}
10821092
onOk={(newName) => {
10831093
setNewProjectNameModalIsOpen(false);
10841094
handleNewProjectNameOk(newName);

src/modal/NewProjectNameModal.tsx

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,20 @@ import type { InputRef } from 'antd';
3333

3434
import * as commonStorage from '../storage/common_storage';
3535

36+
const DESCRIPTION = 'No spaces are allowed in the project name. Each word in the project should start with a capital letter.';
37+
const EXAMPLE = 'For example, MyAwesomeRobot';
38+
3639

3740
export type NewProjectNameModalProps = {
3841
title: string;
3942
isOpen: boolean;
4043
initialValue: string;
41-
getProjectNames: () => string[];
44+
getProjectClassNames: () => string[];
4245
onOk: (newName: string) => void;
4346
onCancel: () => void;
4447
}
4548

46-
export const NewProjectNameModal: React.FC<NewProjectNameModalProps> = ({ title, isOpen, initialValue, getProjectNames, onOk, onCancel }) => {
49+
export const NewProjectNameModal: React.FC<NewProjectNameModalProps> = ({ title, isOpen, initialValue, getProjectClassNames, onOk, onCancel }) => {
4750
const inputRef = useRef<InputRef>(null);
4851
const [message, setMessage] = useState('');
4952
const [value, setValue] = useState('');
@@ -54,7 +57,7 @@ export const NewProjectNameModal: React.FC<NewProjectNameModalProps> = ({ title,
5457
const afterOpenChange = (open: boolean) => {
5558
if (open) {
5659
setValue(initialValue);
57-
const w = getProjectNames();
60+
const w = getProjectClassNames();
5861
if (w.length === 0) {
5962
setMessage('Let\'s create your first project. You just need to give it a name.');
6063
}
@@ -68,9 +71,13 @@ export const NewProjectNameModal: React.FC<NewProjectNameModalProps> = ({ title,
6871
}
6972
};
7073

74+
const onChange = (e) => {
75+
setValue(commonStorage.onChangeClassName(e.target.value));
76+
};
77+
7178
const handleOk = () => {
72-
if (!commonStorage.isValidPythonModuleName(value)) {
73-
setAlertErrorMessage(value + ' is not a valid blocks module name');
79+
if (!commonStorage.isValidClassName(value)) {
80+
setAlertErrorMessage(value + ' is not a valid blocks project name');
7481
setAlertErrorVisible(true);
7582
return;
7683
}
@@ -98,11 +105,13 @@ export const NewProjectNameModal: React.FC<NewProjectNameModalProps> = ({ title,
98105
style={message.length === 0 ? { display: 'none' } : { }}
99106
>{message}</Typography.Paragraph>
100107
<Typography.Paragraph> Project Name </Typography.Paragraph>
108+
<Typography.Paragraph>{DESCRIPTION}</Typography.Paragraph>
109+
<Typography.Paragraph>{EXAMPLE}</Typography.Paragraph>
101110
<Input
102111
ref={inputRef}
103112
value={value}
104113
onPressEnter={handleOk}
105-
onChange={(e) => setValue(e.target.value.toLowerCase())}
114+
onChange={onChange}
106115
/>
107116
{alertErrorVisible && (
108117
<Alert

src/storage/client_side_storage.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,12 +135,14 @@ class ClientSideStorage implements commonStorage.Storage {
135135
const value = cursor.value;
136136
const path = value.path;
137137
const moduleType = value.type;
138+
const moduleName = commonStorage.getModuleName(path);
138139
const module: commonStorage.Module = {
139140
modulePath: path,
140141
moduleType: moduleType,
141142
projectName: commonStorage.getProjectName(path),
142-
moduleName: commonStorage.getModuleName(path),
143+
moduleName: moduleName,
143144
dateModifiedMillis: value.dateModifiedMillis,
145+
className: commonStorage.moduleNameToClassName(moduleName),
144146
}
145147
if (moduleType === commonStorage.MODULE_TYPE_PROJECT) {
146148
const project: commonStorage.Project = {

src/storage/common_storage.ts

Lines changed: 85 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import JSZip from 'jszip';
2323

2424
import * as Blockly from 'blockly/core';
2525

26-
import {Block} from "../toolbox/items";
26+
import {Block} from '../toolbox/items';
2727
import startingOpModeBlocks from '../modules/opmode_start.json';
2828
import startingMechanismBlocks from '../modules/mechanism_start.json';
2929
import startingRobotBlocks from '../modules/robot_start.json';
@@ -39,6 +39,7 @@ export type Module = {
3939
projectName: string,
4040
moduleName: string,
4141
dateModifiedMillis: number,
42+
className: string,
4243
};
4344

4445
export type Mechanism = Module;
@@ -104,6 +105,84 @@ export function findModule(modules: Project[], modulePath: string): Module | nul
104105
return null;
105106
}
106107

108+
/**
109+
* Makes the given name a valid class name.
110+
*/
111+
export function onChangeClassName(name: string): string {
112+
let newName = '';
113+
114+
// Force the first character to be an upper case letter
115+
let i = 0;
116+
for (; i < name.length; i++) {
117+
const firstChar = name.charAt(0);
118+
if (firstChar >= 'A' && firstChar <= 'Z') {
119+
newName += firstChar;
120+
i++;
121+
break;
122+
} else if (firstChar >= 'a' && firstChar <= 'z') {
123+
newName += firstChar.toUpperCase();
124+
i++;
125+
break;
126+
}
127+
}
128+
129+
for (; i < name.length; i++) {
130+
const char = name.charAt(i);
131+
if ((char >= 'A' && char <= 'Z') ||
132+
(char >= 'a' && char <= 'z') ||
133+
(char >= '0' && char <= '9')) {
134+
newName += char;
135+
}
136+
}
137+
138+
return newName;
139+
}
140+
141+
/**
142+
* Returns true if the given name is a valid class name.
143+
*/
144+
export function isValidClassName(name: string): boolean {
145+
if (name) {
146+
return /^[A-Z][A-Za-z0-9]*$/.test(name);
147+
}
148+
return false;
149+
}
150+
151+
/**
152+
* Returns the module name for the given class name.
153+
*/
154+
export function classNameToModuleName(className: string): boolean {
155+
let moduleName = '';
156+
for (let i = 0; i < className.length; i++) {
157+
const char = className.charAt(i);
158+
if (char >= 'A' && char <= 'Z') {
159+
if (i > 0) {
160+
moduleName += '_';
161+
}
162+
moduleName += char.toLowerCase();
163+
} else {
164+
moduleName += char;
165+
}
166+
}
167+
return moduleName;
168+
}
169+
170+
/**
171+
* Returns the class name for the given module name.
172+
*/
173+
export function moduleNameToClassName(moduleName: string): boolean {
174+
let className = '';
175+
let nextCharUpper = true;
176+
for (let i = 0; i < moduleName.length; i++) {
177+
const char = moduleName.charAt(i);
178+
if (char !== '_') {
179+
className += nextCharUpper ? char.toUpperCase() : char;
180+
}
181+
nextCharUpper = (char === '_');
182+
}
183+
return className;
184+
}
185+
107186
/**
108187
* Returns true if the given name is a valid python module name.
109188
*/
@@ -180,6 +259,7 @@ export function newProjectContent(projectName: string): string {
180259
projectName: projectName,
181260
moduleName: projectName,
182261
dateModifiedMillis: 0,
262+
className: moduleNameToClassName(projectName),
183263
};
184264

185265
return startingBlocksToModuleContent(module, startingRobotBlocks);
@@ -195,6 +275,7 @@ export function newMechanismContent(projectName: string, mechanismName: string):
195275
projectName: projectName,
196276
moduleName: mechanismName,
197277
dateModifiedMillis: 0,
278+
className: moduleNameToClassName(mechanismName),
198279
};
199280

200281
return startingBlocksToModuleContent(module, startingMechanismBlocks);
@@ -210,6 +291,7 @@ export function newOpModeContent(projectName: string, opModeName: string): strin
210291
projectName: projectName,
211292
moduleName: opModeName,
212293
dateModifiedMillis: 0,
294+
className: moduleNameToClassName(opModeName),
213295
};
214296

215297
return startingBlocksToModuleContent(module, startingOpModeBlocks);
@@ -356,6 +438,7 @@ function _processModuleContentForDownload(
356438
projectName: projectName,
357439
moduleName: moduleName,
358440
dateModifiedMillis: 0,
441+
className: moduleNameToClassName(moduleName),
359442
};
360443

361444
// Clear out the python content and exported blocks.
@@ -455,6 +538,7 @@ export function _processUploadedModule(
455538
projectName: projectName,
456539
moduleName: moduleName,
457540
dateModifiedMillis: 0,
541+
className: moduleNameToClassName(moduleName),
458542
};
459543

460544
// Generate the python content and exported blocks.

0 commit comments

Comments
 (0)