Skip to content

Commit 7618f85

Browse files
authored
Merge pull request #3034 from vivekbopaliya/feat/privatesketch
Private Sketch feat.
2 parents 40b1b0b + a19ae27 commit 7618f85

36 files changed

+878
-244
lines changed

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,4 @@ ENV NODE_ENV=production
2828
COPY package.json package-lock.json index.js ./
2929
RUN npm install --production
3030
COPY --from=build $APP_HOME/dist ./dist
31-
CMD ["npm", "run", "start:prod"]
31+
CMD ["npm", "run", "start:prod"]

client/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ export const DELETE_COLLECTION = 'DELETE_COLLECTION';
4141
export const ADD_TO_COLLECTION = 'ADD_TO_COLLECTION';
4242
export const REMOVE_FROM_COLLECTION = 'REMOVE_FROM_COLLECTION';
4343
export const EDIT_COLLECTION = 'EDIT_COLLECTION';
44+
export const CHANGE_VISIBILITY = 'CHANGE_VISIBILITY';
45+
export const SET_PROJECT_VISIBILITY = 'SET_PROJECT_VISIBILITY';
4446

4547
export const DELETE_PROJECT = 'DELETE_PROJECT';
4648

client/images/checkmark.svg

Lines changed: 13 additions & 0 deletions
Loading

client/images/earth.svg

Lines changed: 14 additions & 0 deletions
Loading

client/images/lock.svg

Lines changed: 3 additions & 0 deletions
Loading

client/modules/IDE/actions/project.js

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ export function setProject(project) {
2424
type: ActionTypes.SET_PROJECT,
2525
project,
2626
files: project.files,
27-
owner: project.user
27+
owner: project.user,
28+
visibility: project.visibility
2829
};
2930
}
3031

@@ -410,3 +411,50 @@ export function deleteProject(id) {
410411
});
411412
};
412413
}
414+
export function changeVisibility(projectId, projectName, visibility) {
415+
return (dispatch, getState) => {
416+
const state = getState();
417+
418+
apiClient
419+
.patch('/project/visibility', { projectId, visibility })
420+
.then((response) => {
421+
if (response.status === 200) {
422+
const { visibility: newVisibility, updatedAt } = response.data;
423+
424+
dispatch({
425+
type: ActionTypes.CHANGE_VISIBILITY,
426+
payload: {
427+
id: response.data.id,
428+
visibility: newVisibility
429+
}
430+
});
431+
432+
if (state.project.id === response.data.id) {
433+
dispatch({
434+
type: ActionTypes.SET_PROJECT_VISIBILITY,
435+
visibility: newVisibility,
436+
updatedAt
437+
});
438+
439+
dispatch({
440+
type: ActionTypes.SET_PROJECT_NAME,
441+
name: response.data.name
442+
});
443+
444+
dispatch(
445+
setToastText(
446+
`${projectName} is now ${newVisibility.toLowerCase()}`
447+
)
448+
);
449+
dispatch(showToast(2000));
450+
}
451+
}
452+
})
453+
.catch((error) => {
454+
dispatch({
455+
type: ActionTypes.ERROR,
456+
error: error?.response?.data
457+
});
458+
});
459+
};
460+
}

client/modules/IDE/components/Header/MobileNav.jsx

Lines changed: 42 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { setLanguage } from '../../actions/preferences';
3535
import Overlay from '../../../App/components/Overlay';
3636
import ProjectName from './ProjectName';
3737
import CollectionCreate from '../../../User/components/CollectionCreate';
38+
import { changeVisibility } from '../../actions/project';
3839

3940
const Nav = styled(Menubar)`
4041
background: ${prop('MobilePanel.default.background')};
@@ -75,6 +76,13 @@ const Title = styled.div`
7576
margin: 0;
7677
}
7778
79+
> section {
80+
display: flex;
81+
align-items: center;
82+
justify-content: center;
83+
gap: 5px;
84+
}
85+
7886
> h5 {
7987
font-size: ${remSize(13)};
8088
font-weight: normal;
@@ -212,6 +220,7 @@ const MobileMenuItem = ({ children, ...props }) => (
212220
const MobileNav = () => {
213221
const project = useSelector((state) => state.project);
214222
const user = useSelector((state) => state.user);
223+
const dispatch = useDispatch();
215224

216225
const { t } = useTranslation();
217226

@@ -237,21 +246,48 @@ const MobileNav = () => {
237246
}
238247

239248
const title = useMemo(resolveTitle, [pageName, project.name]);
240-
249+
const userIsOwner = user?.username === project.owner?.username;
241250
const Logo = AsteriskIcon;
251+
252+
const showPrivacyToggle =
253+
project?.owner && title === project.name && userIsOwner;
254+
const showOwner = project?.owner && title === project.name && !userIsOwner;
255+
256+
const toggleVisibility = (e) => {
257+
try {
258+
const isChecked = e.target.checked;
259+
dispatch(
260+
changeVisibility(
261+
project.id,
262+
project.name,
263+
isChecked ? 'Private' : 'Public'
264+
)
265+
);
266+
} catch (error) {
267+
console.log(error);
268+
}
269+
};
242270
return (
243271
<Nav>
244272
<LogoContainer>
245273
<Logo />
246274
</LogoContainer>
247275
<Title>
248-
<h1>{title === project.name ? <ProjectName /> : title}</h1>
249-
{project?.owner && title === project.name && (
250-
<Link to={`/${project.owner.username}/sketches`}>
251-
by {project?.owner?.username}
252-
</Link>
276+
<h1>{title === project?.name ? <ProjectName /> : title}</h1>
277+
{showPrivacyToggle && (
278+
<main className="toolbar__makeprivate">
279+
<p>Private</p>
280+
<input
281+
className="toolbar__togglevisibility"
282+
type="checkbox"
283+
onChange={toggleVisibility}
284+
defaultChecked={project.visibility === 'Private'}
285+
/>
286+
</main>
253287
)}
288+
{showOwner && <h5>by {project?.owner?.username}</h5>}
254289
</Title>
290+
255291
{/* check if the user is in login page */}
256292
{pageName === 'login' || pageName === 'signup' ? (
257293
// showing the CrossIcon

client/modules/IDE/components/Header/Toolbar.jsx

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React from 'react';
1+
import React, { useState, useEffect, useCallback } from 'react';
22
import classNames from 'classnames';
33
import PropTypes from 'prop-types';
44
import { useTranslation } from 'react-i18next';
@@ -15,22 +15,26 @@ import {
1515
setGridOutput,
1616
setTextOutput
1717
} from '../../actions/preferences';
18-
1918
import PlayIcon from '../../../../images/play.svg';
2019
import StopIcon from '../../../../images/stop.svg';
2120
import PreferencesIcon from '../../../../images/preferences.svg';
2221
import ProjectName from './ProjectName';
2322
import VersionIndicator from '../VersionIndicator';
23+
import VisibilityDropdown from '../../../User/components/VisibilityDropdown';
24+
import { changeVisibility } from '../../actions/project';
2425

2526
const Toolbar = (props) => {
2627
const { isPlaying, infiniteLoop, preferencesIsVisible } = useSelector(
2728
(state) => state.ide
2829
);
2930
const project = useSelector((state) => state.project);
31+
const user = useSelector((state) => state.user);
3032
const autorefresh = useSelector((state) => state.preferences.autorefresh);
3133
const dispatch = useDispatch();
32-
3334
const { t } = useTranslation();
35+
const userIsOwner = user?.username === project.owner?.username;
36+
37+
const showVisibilityDropdown = project?.owner && userIsOwner;
3438

3539
const playButtonClass = classNames({
3640
'toolbar__play-button': true,
@@ -45,6 +49,13 @@ const Toolbar = (props) => {
4549
'toolbar__preferences-button--selected': preferencesIsVisible
4650
});
4751

52+
const handleVisibilityChange = useCallback(
53+
(sketchId, sketchName, newVisibility) => {
54+
dispatch(changeVisibility(sketchId, sketchName, newVisibility));
55+
},
56+
[changeVisibility]
57+
);
58+
4859
return (
4960
<div className="toolbar">
5061
<button
@@ -98,24 +109,34 @@ const Toolbar = (props) => {
98109
{t('Toolbar.Auto-refresh')}
99110
</label>
100111
</div>
112+
101113
<div className="toolbar__project-name-container">
102114
<ProjectName />
103-
{(() => {
104-
if (project.owner) {
105-
return (
106-
<p className="toolbar__project-project.owner">
107-
{t('Toolbar.By')}{' '}
108-
<Link to={`/${project.owner.username}/sketches`}>
109-
{project.owner.username}
110-
</Link>
111-
</p>
112-
);
113-
}
114-
return null;
115-
})()}
115+
116+
{showVisibilityDropdown && (
117+
<div className="toolbar__visibility">
118+
<VisibilityDropdown
119+
sketch={project}
120+
onVisibilityChange={handleVisibilityChange}
121+
/>
122+
</div>
123+
)}
124+
125+
{/* ✅ Still show owner if not you */}
126+
{project?.owner && !userIsOwner && (
127+
<p className="toolbar__project-owner">
128+
{t('Toolbar.By')}{' '}
129+
<Link to={`/${project.owner.username}/sketches`}>
130+
{project.owner.username}
131+
</Link>
132+
</p>
133+
)}
134+
135+
<VersionIndicator />
116136
</div>
117-
<VersionIndicator />
137+
118138
<div style={{ flex: 1 }} />
139+
119140
<button
120141
className={preferencesButtonClass}
121142
onClick={() => dispatch(openPreferences())}

client/modules/IDE/components/Header/__snapshots__/Nav.unit.test.jsx.snap

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,22 @@ exports[`Nav renders dashboard version for mobile 1`] = `
162162
margin: 0;
163163
}
164164
165+
.c2 > section {
166+
display: -webkit-box;
167+
display: -webkit-flex;
168+
display: -ms-flexbox;
169+
display: flex;
170+
-webkit-align-items: center;
171+
-webkit-box-align: center;
172+
-ms-flex-align: center;
173+
align-items: center;
174+
-webkit-box-pack: center;
175+
-webkit-justify-content: center;
176+
-ms-flex-pack: center;
177+
justify-content: center;
178+
gap: 5px;
179+
}
180+
165181
.c2 > h5 {
166182
font-size: 1.0833333333333333rem;
167183
font-weight: normal;
@@ -757,6 +773,22 @@ exports[`Nav renders editor version for mobile 1`] = `
757773
margin: 0;
758774
}
759775
776+
.c2 > section {
777+
display: -webkit-box;
778+
display: -webkit-flex;
779+
display: -ms-flexbox;
780+
display: flex;
781+
-webkit-align-items: center;
782+
-webkit-box-align: center;
783+
-ms-flex-align: center;
784+
align-items: center;
785+
-webkit-box-pack: center;
786+
-webkit-justify-content: center;
787+
-ms-flex-pack: center;
788+
justify-content: center;
789+
gap: 5px;
790+
}
791+
760792
.c2 > h5 {
761793
font-size: 1.0833333333333333rem;
762794
font-weight: normal;

client/modules/IDE/components/SketchList.jsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
/* eslint-disable max-len */
12
import PropTypes from 'prop-types';
23
import classNames from 'classnames';
34
import React, { useEffect, useState, useMemo, useCallback } from 'react';
@@ -13,9 +14,9 @@ import getSortedSketches from '../selectors/projects';
1314
import Loader from '../../App/components/loader';
1415
import Overlay from '../../App/components/Overlay';
1516
import AddToCollectionList from './AddToCollectionList';
16-
import SketchListRowBase from './SketchListRowBase';
1717
import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
1818
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
19+
import SketchListRowBase from './SketchListRowBase';
1920

2021
const SketchList = ({
2122
user,
@@ -118,6 +119,8 @@ const SketchList = ({
118119
[sorting, getButtonLabel, toggleDirectionForField, t]
119120
);
120121

122+
const userIsOwner = user.username === username;
123+
121124
return (
122125
<article className="sketches-table-container">
123126
<Helmet>
@@ -145,6 +148,7 @@ const SketchList = ({
145148
context: mobile ? 'mobile' : ''
146149
})
147150
)}
151+
{userIsOwner && renderFieldHeader('visibility', 'Visibility')}
148152
<th scope="col"></th>
149153
</tr>
150154
</thead>
@@ -187,7 +191,8 @@ SketchList.propTypes = {
187191
id: PropTypes.string.isRequired,
188192
name: PropTypes.string.isRequired,
189193
createdAt: PropTypes.string.isRequired,
190-
updatedAt: PropTypes.string.isRequired
194+
updatedAt: PropTypes.string.isRequired,
195+
visibility: PropTypes.string
191196
})
192197
).isRequired,
193198
username: PropTypes.string,

0 commit comments

Comments
 (0)