Skip to content

Commit 4119445

Browse files
yeion7CompuIves
authored andcommitted
Validate if a folder/file already exist (codesandbox#2827)
* add memoized getChildren * add validation
1 parent 34529a2 commit 4119445

File tree

5 files changed

+153
-148
lines changed

5 files changed

+153
-148
lines changed

packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/DirectoryChildren/ModuleEntry.js

Lines changed: 46 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -4,60 +4,52 @@ import getType from 'app/utils/get-type.ts';
44
import React from 'react';
55

66
import Entry from '../Entry';
7-
import validateTitle from '../validateTitle';
87

9-
class ModuleEntry extends React.PureComponent {
10-
validateTitle = (id, title) => {
11-
const { directories, modules } = this.props.store.editor.currentSandbox;
12-
return !!validateTitle(id, title, [...directories, ...modules]);
13-
};
14-
15-
render() {
16-
const {
17-
store,
18-
module,
19-
setCurrentModule,
20-
markTabsNotDirty,
21-
depth,
22-
renameModule,
23-
deleteEntry,
24-
discardModuleChanges,
25-
getModulePath,
26-
} = this.props;
27-
const { currentModuleShortid } = store.editor;
28-
const mainModuleId = store.editor.mainModule.id;
29-
30-
const isActive = module.shortid === currentModuleShortid;
31-
const isMainModule = module.id === mainModuleId;
32-
const type = getType(module.title);
33-
const hasError = module.errors.length;
34-
35-
const liveUsers = store.live.liveUsersByModule[module.shortid] || [];
36-
37-
const isNotSynced = module.savedCode && module.code !== module.savedCode;
38-
39-
return (
40-
<Entry
41-
id={module.id}
42-
shortid={module.shortid}
43-
title={module.title}
44-
rightColors={liveUsers.map(([a, b, c]) => `rgb(${a}, ${b}, ${c})`)}
45-
depth={depth + 1}
46-
active={isActive}
47-
type={type || 'function'}
48-
rename={renameModule}
49-
deleteEntry={deleteEntry}
50-
isNotSynced={isNotSynced}
51-
renameValidator={this.validateTitle}
52-
setCurrentModule={setCurrentModule}
53-
isMainModule={isMainModule}
54-
moduleHasError={hasError}
55-
markTabsNotDirty={markTabsNotDirty}
56-
discardModuleChanges={discardModuleChanges}
57-
getModulePath={getModulePath}
58-
/>
59-
);
60-
}
61-
}
8+
const ModuleEntry = ({
9+
store,
10+
module,
11+
setCurrentModule,
12+
markTabsNotDirty,
13+
depth,
14+
renameModule,
15+
deleteEntry,
16+
discardModuleChanges,
17+
getModulePath,
18+
renameValidator,
19+
}) => {
20+
const { currentModuleShortid } = store.editor;
21+
const mainModuleId = store.editor.mainModule.id;
22+
23+
const isActive = module.shortid === currentModuleShortid;
24+
const isMainModule = module.id === mainModuleId;
25+
const type = getType(module.title);
26+
const hasError = module.errors.length;
27+
28+
const liveUsers = store.live.liveUsersByModule[module.shortid] || [];
29+
30+
const isNotSynced = module.savedCode && module.code !== module.savedCode;
31+
32+
return (
33+
<Entry
34+
id={module.id}
35+
shortid={module.shortid}
36+
title={module.title}
37+
rightColors={liveUsers.map(([a, b, c]) => `rgb(${a}, ${b}, ${c})`)}
38+
depth={depth + 1}
39+
active={isActive}
40+
type={type || 'function'}
41+
rename={renameModule}
42+
deleteEntry={deleteEntry}
43+
isNotSynced={isNotSynced}
44+
renameValidator={renameValidator}
45+
setCurrentModule={setCurrentModule}
46+
isMainModule={isMainModule}
47+
moduleHasError={hasError}
48+
markTabsNotDirty={markTabsNotDirty}
49+
discardModuleChanges={discardModuleChanges}
50+
getModulePath={getModulePath}
51+
/>
52+
);
53+
};
6254

6355
export default inject('store')(observer(ModuleEntry));

packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/DirectoryChildren/index.js

Lines changed: 70 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -3,92 +3,83 @@ import { inject, observer } from 'app/componentConnectors';
33
import { sortBy } from 'lodash-es';
44
import * as React from 'react';
55

6-
import validateTitle from '../validateTitle';
76
import ModuleEntry from './ModuleEntry';
87
import DirectoryEntry from '..';
98

10-
class DirectoryChildren extends React.Component {
11-
validateTitle = (id, title) => {
12-
const { directories, modules } = this.props.store.editor.currentSandbox;
13-
return !!validateTitle(id, title, [...directories, ...modules]);
14-
};
9+
const DirectoryChildren = ({
10+
depth = 0,
11+
renameModule,
12+
setCurrentModule,
13+
parentShortid,
14+
deleteEntry,
15+
isInProjectView,
16+
markTabsNotDirty,
17+
store,
18+
discardModuleChanges,
19+
getModulePath,
20+
signals,
21+
renameValidator,
22+
}) => {
23+
const {
24+
id: sandboxId,
25+
modules,
26+
directories,
27+
template: sandboxTemplate,
28+
} = store.editor.currentSandbox;
29+
const { mainModule, currentModuleShortid } = store.editor;
30+
const mainModuleId = mainModule.id;
1531

16-
render() {
17-
const {
18-
depth = 0,
19-
renameModule,
20-
setCurrentModule,
21-
parentShortid,
22-
deleteEntry,
23-
isInProjectView,
24-
markTabsNotDirty,
25-
store,
26-
discardModuleChanges,
27-
getModulePath,
28-
} = this.props;
29-
30-
const {
31-
id: sandboxId,
32-
modules,
33-
directories,
34-
template: sandboxTemplate,
35-
} = store.editor.currentSandbox;
36-
const { mainModule, currentModuleShortid } = store.editor;
37-
const mainModuleId = mainModule.id;
38-
39-
return (
40-
<div>
41-
{sortBy(directories, 'title')
42-
.filter(x => x.directoryShortid === parentShortid)
43-
.filter(
44-
x =>
45-
!(
46-
x.directoryShortid == null &&
47-
HIDDEN_DIRECTORIES.includes(x.title)
48-
)
49-
)
50-
.map(dir => (
51-
<DirectoryEntry
52-
key={dir.id}
53-
siblings={[...directories, ...modules]}
54-
depth={depth + 1}
55-
signals={
56-
this.props
57-
.signals /* TODO: Just pass what is needed by the DragDrop */
58-
}
59-
id={dir.id}
60-
shortid={dir.shortid}
61-
title={dir.title}
62-
sandboxId={sandboxId}
63-
sandboxTemplate={sandboxTemplate}
64-
mainModuleId={mainModuleId}
65-
modules={modules}
66-
directories={directories}
67-
currentModuleShortid={currentModuleShortid}
68-
isInProjectView={isInProjectView}
69-
markTabsNotDirty={markTabsNotDirty}
70-
getModulePath={getModulePath}
71-
/>
72-
))}
73-
{sortBy(
74-
modules.filter(x => x.directoryShortid === parentShortid),
75-
'title'
76-
).map(m => (
77-
<ModuleEntry
78-
key={m.id}
79-
module={m}
80-
depth={depth}
81-
setCurrentModule={setCurrentModule}
32+
return (
33+
<div>
34+
{sortBy(directories, 'title')
35+
.filter(x => x.directoryShortid === parentShortid)
36+
.filter(
37+
x =>
38+
!(
39+
x.directoryShortid == null && HIDDEN_DIRECTORIES.includes(x.title)
40+
)
41+
)
42+
.map(dir => (
43+
<DirectoryEntry
44+
key={dir.id}
45+
siblings={[...directories, ...modules]}
46+
depth={depth + 1}
47+
signals={
48+
signals /* TODO: Just pass what is needed by the DragDrop */
49+
}
50+
id={dir.id}
51+
shortid={dir.shortid}
52+
title={dir.title}
53+
sandboxId={sandboxId}
54+
sandboxTemplate={sandboxTemplate}
55+
mainModuleId={mainModuleId}
56+
modules={modules}
57+
directories={directories}
58+
currentModuleShortid={currentModuleShortid}
59+
isInProjectView={isInProjectView}
8260
markTabsNotDirty={markTabsNotDirty}
83-
renameModule={renameModule}
84-
deleteEntry={deleteEntry}
85-
discardModuleChanges={discardModuleChanges}
8661
getModulePath={getModulePath}
8762
/>
8863
))}
89-
</div>
90-
);
91-
}
92-
}
64+
{sortBy(
65+
modules.filter(x => x.directoryShortid === parentShortid),
66+
'title'
67+
).map(m => (
68+
<ModuleEntry
69+
key={m.id}
70+
module={m}
71+
depth={depth}
72+
setCurrentModule={setCurrentModule}
73+
markTabsNotDirty={markTabsNotDirty}
74+
renameModule={renameModule}
75+
deleteEntry={deleteEntry}
76+
discardModuleChanges={discardModuleChanges}
77+
getModulePath={getModulePath}
78+
renameValidator={renameValidator}
79+
/>
80+
))}
81+
</div>
82+
);
83+
};
9384

9485
export default inject('store', 'signals')(observer(DirectoryChildren));

packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/index.js

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { reaction } from 'mobx';
55
import React from 'react';
66
import { DropTarget } from 'react-dnd';
77
import { NativeTypes } from 'react-dnd-html5-backend';
8+
import { getChildren } from '@codesandbox/common/lib/sandbox/modules';
89

910
import DirectoryChildren from './DirectoryChildren';
1011
import { EntryContainer, Opener, Overlay } from './elements';
@@ -168,29 +169,29 @@ class DirectoryEntry extends React.PureComponent {
168169
setOpen = open => this.setState({ open });
169170

170171
validateModuleTitle = (_, title) => {
171-
const { store, id } = this.props;
172-
const { directories, modules } = store.editor.currentSandbox;
173-
return validateTitle(id, title, [...directories, ...modules]);
172+
const { id } = this.props;
173+
174+
return validateTitle(id, title, this.getChildren());
174175
};
175176

176177
validateDirectoryTitle = (id, title) => {
177-
const { root, siblings } = this.props;
178+
const { root } = this.props;
178179
if (root) return false;
179180

180-
return validateTitle(id, title, siblings);
181+
return validateTitle(id, title, this.getChildren());
181182
};
182183

183184
getChildren = () => {
184-
const { shortid } = this.props;
185+
const {
186+
shortid,
187+
store: {
188+
editor: {
189+
currentSandbox: { modules, directories },
190+
},
191+
},
192+
} = this.props;
185193

186-
return [
187-
...this.props.store.editor.currentSandbox.modules.filter(
188-
m => m.directoryShortid === shortid
189-
),
190-
...this.props.store.editor.currentSandbox.directories.filter(
191-
d => d.directoryShortid === shortid
192-
),
193-
];
194+
return getChildren(modules, directories, shortid);
194195
};
195196

196197
setCurrentModule = moduleId => {
@@ -300,6 +301,7 @@ class DirectoryEntry extends React.PureComponent {
300301
depth={depth}
301302
renameModule={this.renameModule}
302303
parentShortid={shortid}
304+
renameValidator={this.validateModuleTitle}
303305
deleteEntry={this.deleteModule}
304306
setCurrentModule={this.setCurrentModule}
305307
markTabsNotDirty={this.markTabsNotDirty}

packages/app/src/app/pages/Sandbox/Editor/Workspace/Files/DirectoryEntry/validateTitle.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export default (id, title) => {
1+
export default (id, title, siblings = []) => {
22
if (title.length === 0) return 'Title cannot be empty';
33
if (/^[09azAZ_.]+$/.test(title)) {
44
// It has whitespaces
@@ -9,5 +9,13 @@ export default (id, title) => {
99
return "The title can't be more than 32 characters long";
1010
}
1111

12+
if (title.includes('/') || title.includes('\\')) {
13+
return "The title can't include slash or backslash";
14+
}
15+
16+
if (siblings.find(sibling => sibling.title === title && sibling.id !== id)) {
17+
return `A file or folder ${title} already exists at this location`;
18+
}
19+
1220
return null;
1321
};

packages/common/src/sandbox/modules.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,18 @@ export const getDirectoryPath = memoize(
239239
memoizeFunction
240240
);
241241

242+
export const getChildren = memoize(
243+
(
244+
modules: Array<Module> = [],
245+
directories: Array<Directory> = [],
246+
id: string
247+
) => [
248+
...directories.filter(d => d.directoryShortid === id),
249+
...modules.filter(m => m.directoryShortid === id),
250+
],
251+
memoizeFunction
252+
);
253+
242254
export const isMainModule = (
243255
module: Module,
244256
modules: Module[],

0 commit comments

Comments
 (0)