From cae1399350286853ccde8155f5111616d6fb78cc Mon Sep 17 00:00:00 2001 From: Vivek Date: Fri, 16 Feb 2024 22:17:58 +0530 Subject: [PATCH 1/7] backend done, frontend progress --- client/constants.js | 1 + client/modules/IDE/actions/project.js | 24 ++++ .../modules/IDE/components/Header/Toolbar.jsx | 27 ++++- client/modules/IDE/components/SketchList.jsx | 104 +++++++++++++++--- client/modules/IDE/reducers/project.js | 9 +- client/modules/IDE/reducers/projects.js | 5 + client/modules/User/components/Collection.jsx | 22 ++-- client/styles/components/_sketch-list.scss | 101 ++++++++++++++++- client/styles/components/_toolbar.scss | 48 ++++++-- .../addProjectToCollection.js | 2 +- .../collection.controller/createCollection.js | 2 +- .../collection.controller/listCollections.js | 2 +- .../removeProjectFromCollection.js | 2 +- .../collection.controller/updateCollection.js | 2 +- server/controllers/project.controller.js | 38 +++++++ .../project.controller/getProjectsForUser.js | 2 +- server/models/project.js | 6 +- server/routes/project.routes.js | 2 + 18 files changed, 347 insertions(+), 52 deletions(-) diff --git a/client/constants.js b/client/constants.js index 9c5f9ae353..bc27fadaba 100644 --- a/client/constants.js +++ b/client/constants.js @@ -41,6 +41,7 @@ export const DELETE_COLLECTION = 'DELETE_COLLECTION'; export const ADD_TO_COLLECTION = 'ADD_TO_COLLECTION'; export const REMOVE_FROM_COLLECTION = 'REMOVE_FROM_COLLECTION'; export const EDIT_COLLECTION = 'EDIT_COLLECTION'; +export const CHANGE_VISIBILITY = 'CHANGE_VISIBILITY'; export const DELETE_PROJECT = 'DELETE_PROJECT'; diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index 1d8943336b..60b5b63c76 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -410,3 +410,27 @@ export function deleteProject(id) { }); }; } + +export function changeVisibility(projectId, visibility) { + return (dispatch) => + apiClient + .patch('/project/visibility', { projectId, visibility }) + .then((response) => { + console.log(response.data); + const { visibility: newVisibility } = response.data; + + dispatch({ + type: ActionTypes.CHANGE_VISIBILITY, + payload: { visibility: response.data.visibility } + }); + + dispatch(setToastText(`Sketch is switched to ${newVisibility}`)); + dispatch(showToast(2000)); + }) + .catch((error) => { + dispatch({ + type: ActionTypes.ERROR, + error: error?.response?.data + }); + }); +} diff --git a/client/modules/IDE/components/Header/Toolbar.jsx b/client/modules/IDE/components/Header/Toolbar.jsx index f61f56ed0e..0480b05ddc 100644 --- a/client/modules/IDE/components/Header/Toolbar.jsx +++ b/client/modules/IDE/components/Header/Toolbar.jsx @@ -20,6 +20,8 @@ import PlayIcon from '../../../../images/play.svg'; import StopIcon from '../../../../images/stop.svg'; import PreferencesIcon from '../../../../images/preferences.svg'; import ProjectName from './ProjectName'; +import { changeVisibility } from '../../actions/project'; +import Button from '../../../../common/Button'; const Toolbar = (props) => { const { isPlaying, infiniteLoop, preferencesIsVisible } = useSelector( @@ -28,8 +30,22 @@ const Toolbar = (props) => { const project = useSelector((state) => state.project); const autorefresh = useSelector((state) => state.preferences.autorefresh); const dispatch = useDispatch(); - const { t } = useTranslation(); + const [visibility, setVisibility] = React.useState(project.visibility); + console.log(project); + const toggleVisibility = () => { + try { + setVisibility((prev) => (prev === 'Public' ? 'Private' : 'Public')); + dispatch( + changeVisibility( + project.id, + visibility === 'Public' ? 'Private' : 'Public' + ) + ); + } catch (error) { + console.log(error); + } + }; const playButtonClass = classNames({ 'toolbar__play-button': true, @@ -112,6 +128,15 @@ const Toolbar = (props) => { return null; })()} + {project?.owner && ( +
+ {visibility === 'Private' ? ( + + ) : ( + + )} +
+ )} + + + )} ); @@ -189,7 +245,11 @@ class SketchListRowBase extends React.Component { key={sketch.id} onClick={this.handleRowClick} > - {name} + + {this.state.newVisibility === 'Private' &&

Lock

} + {this.state.newVisibility} + {name} + {formatDateCell(sketch.createdAt, mobile)} {formatDateCell(sketch.updatedAt, mobile)} {this.renderDropdown()} @@ -204,7 +264,8 @@ SketchListRowBase.propTypes = { id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired, - updatedAt: PropTypes.string.isRequired + updatedAt: PropTypes.string.isRequired, + visibility: PropTypes.string }).isRequired, username: PropTypes.string.isRequired, user: PropTypes.shape({ @@ -216,6 +277,7 @@ SketchListRowBase.propTypes = { cloneProject: PropTypes.func.isRequired, changeProjectName: PropTypes.func.isRequired, onAddToCollection: PropTypes.func.isRequired, + changeVisibility: PropTypes.func.isRequired, mobile: PropTypes.bool, t: PropTypes.func.isRequired }; @@ -241,7 +303,6 @@ class SketchList extends React.Component { super(props); this.props.getProjects(this.props.username); this.props.resetSorting(); - this.state = { isInitialDataLoad: true }; @@ -354,6 +415,8 @@ class SketchList extends React.Component { }; render() { + const userIsOwner = this.props.user.username === this.props.username; + const username = this.props.username !== undefined ? this.props.username @@ -393,19 +456,23 @@ class SketchList extends React.Component { - {this.props.sketches.map((sketch) => ( - { - this.setState({ sketchToAddToCollection: sketch }); - }} - t={this.props.t} - /> - ))} + {this.props.sketches + .filter( + (sketch) => userIsOwner || sketch.visibility === 'Public' + ) + .map((sketch) => ( + { + this.setState({ sketchToAddToCollection: sketch }); + }} + t={this.props.t} + /> + ))} )} @@ -438,7 +505,8 @@ SketchList.propTypes = { id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, createdAt: PropTypes.string.isRequired, - updatedAt: PropTypes.string.isRequired + updatedAt: PropTypes.string.isRequired, + visibility: PropTypes.string }) ).isRequired, username: PropTypes.string, diff --git a/client/modules/IDE/reducers/project.js b/client/modules/IDE/reducers/project.js index 89a03529e6..b375b4f6e7 100644 --- a/client/modules/IDE/reducers/project.js +++ b/client/modules/IDE/reducers/project.js @@ -8,7 +8,8 @@ const initialState = () => { return { name: generatedName, updatedAt: '', - isSaving: false + isSaving: false, + visibility: 'Public' }; }; @@ -25,7 +26,8 @@ const project = (state, action) => { name: action.project.name, updatedAt: action.project.updatedAt, owner: action.owner, - isSaving: false + isSaving: false, + visibility: action.project.visibility }; case ActionTypes.SET_PROJECT: return { @@ -33,7 +35,8 @@ const project = (state, action) => { name: action.project.name, updatedAt: action.project.updatedAt, owner: action.owner, - isSaving: false + isSaving: false, + visibility: action.project.visibility }; case ActionTypes.RESET_PROJECT: return initialState(); diff --git a/client/modules/IDE/reducers/projects.js b/client/modules/IDE/reducers/projects.js index 5950a042fa..e2606cee80 100644 --- a/client/modules/IDE/reducers/projects.js +++ b/client/modules/IDE/reducers/projects.js @@ -6,6 +6,11 @@ const sketches = (state = [], action) => { return action.projects; case ActionTypes.DELETE_PROJECT: return state.filter((sketch) => sketch.id !== action.id); + case ActionTypes.CHANGE_VISIBILITY: + return state.map((sketch) => ({ + ...sketch, + visibility: action.payload.visibility + })); case ActionTypes.RENAME_PROJECT: { return state.map((sketch) => { if (sketch.id === action.payload.id) { diff --git a/client/modules/User/components/Collection.jsx b/client/modules/User/components/Collection.jsx index 939b2b851f..c363ad69c7 100644 --- a/client/modules/User/components/Collection.jsx +++ b/client/modules/User/components/Collection.jsx @@ -32,6 +32,8 @@ const CollectionItemRowBase = ({ const projectIsDeleted = item.isDeleted; + const projectIsPrivate = !isOwner && item.project.visibility === 'Private'; + const handleSketchRemove = () => { const name = projectIsDeleted ? 'deleted sketch' : item.project.name; @@ -44,13 +46,18 @@ const CollectionItemRowBase = ({ } }; - const name = projectIsDeleted ? ( - {t('Collection.SketchDeleted')} - ) : ( - - {item.project.name} - - ); + let name; + if (projectIsDeleted) { + name = {t('Collection.SketchDeleted')}; + } else if (projectIsPrivate) { + name = Project is Private; + } else { + name = ( + + {item.project.name} + + ); + } const sketchOwnerUsername = projectIsDeleted ? null @@ -90,6 +97,7 @@ CollectionItemRowBase.propTypes = { project: PropTypes.shape({ id: PropTypes.string.isRequired, name: PropTypes.string.isRequired, + visibility: PropTypes.string, user: PropTypes.shape({ username: PropTypes.string.isRequired }) diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index a8edbe5188..0e061eb742 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -1,5 +1,83 @@ @use "sass:math"; +.sketch-visibility { + text-align: center; +} + +sketch-visibility__title { + color: red; + text-decoration: underline; +} + +.sketch-visibility h1 { + font-size: 24px; + font-weight: bold; + color: #333; + /* Adjust color as needed */ + margin-bottom: 20px; +} + +.sketch-visibility hr { + border: none; + border-top: 1px solid rgba(255, 255, 255, 0.5); + /* Adjust color as needed */ + width: 80%; +} + +.sketch-visibility ul { + list-style-type: none; + padding: 0; + text-align: left; + margin-bottom: 20px; +} + +.sketch-visibility ul li { + position: relative; + padding-left: 20px; + /* Add padding for the text */ +} + +.sketch-visibility ul li::before { + content: "\2022"; + /* Bullet character */ + color: #333; + /* Adjust color as needed */ + display: inline-block; + width: 15px; + /* Increase the width to make the circle bigger */ + height: 15px; + /* Increase the height to make the circle bigger */ + border-radius: 50%; + /* Make it a circle */ + background-color: #333; + /* Adjust color as needed */ + position: absolute; + left: 0; + top: 50%; + transform: translateY(-50%); +} + +.sketch-visibility button { + display: block; + margin: 0 auto; + padding: 10px 20px; + font-size: 16px; + background-color: #007bff; + /* Adjust color as needed */ + color: #fff; + /* Adjust color as needed */ + border: none; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.3s ease; +} + +.sketch-visibility button:hover { + background-color: #0056b3; + /* Adjust color as needed */ +} + + .sketches-table-container { overflow-y: auto; max-width: 100%; @@ -40,14 +118,14 @@ background-color: getThemifyVariable("search-background-color") !important; } - > th { + >th { padding-left: 0; width: 100%; font-weight: bold; margin-bottom: #{math.div(6, $base-font-size)}rem; } - > td { + >td { padding-left: 0; width: 30%; font-size: #{math.div(14, $base-font-size)}rem; @@ -57,6 +135,13 @@ } } + .sketches-table__rowname { + display: flex; + gap: 5px; + justify-content: center; + align-items: center; + } + .sketch-list__dropdown-column { position: absolute; top: 0; @@ -75,6 +160,7 @@ max-height: 100%; border-spacing: 0; + & .sketch-list__dropdown-column { width: #{math.div(60, $base-font-size)}rem; position: relative; @@ -86,6 +172,7 @@ position: sticky; top: 0; z-index: 1; + @include themify() { background-color: getThemifyVariable("background-color"); } @@ -110,6 +197,7 @@ .sketches-table__header { border-bottom: 2px dashed transparent; padding: #{math.div(3, $base-font-size)}rem 0; + @include themify() { color: getThemifyVariable("inactive-text-color"); } @@ -137,11 +225,11 @@ } } -.sketches-table__row > th:nth-child(1) { +.sketches-table__row>th:nth-child(1) { padding-left: #{math.div(12, $base-font-size)}rem; } -.sketches-table__row > td { +.sketches-table__row>td { padding-left: #{math.div(8, $base-font-size)}rem; } @@ -151,12 +239,13 @@ } } -.sketches-table__row.is-deleted > * { +.sketches-table__row.is-deleted>* { font-style: italic; } .sketches-table thead { font-size: #{math.div(12, $base-font-size)}rem; + @include themify() { color: getThemifyVariable("inactive-text-color"); } @@ -179,4 +268,4 @@ text-align: center; font-size: #{math.div(16, $base-font-size)}rem; padding: #{math.div(42, $base-font-size)}rem 0; -} +} \ No newline at end of file diff --git a/client/styles/components/_toolbar.scss b/client/styles/components/_toolbar.scss index 00f2be6ec3..fad9fce7ef 100644 --- a/client/styles/components/_toolbar.scss +++ b/client/styles/components/_toolbar.scss @@ -7,23 +7,32 @@ justify-content: center; align-items: center; padding: 0 0 0 #{math.div(3, $base-font-size)}rem; + &--selected { @extend %toolbar-button--selected; } + &:disabled { cursor: auto; - & g, & path { + + & g, + & path { fill: getThemifyVariable('button-border-color'); } + &:hover { background-color: getThemifyVariable('toolbar-button-background-color'); - & g, & path { + + & g, + & path { fill: getThemifyVariable('button-border-color'); } } - } + } } + margin-right: #{math.div(15, $base-font-size)}rem; + span { padding-left: #{math.div(4, $base-font-size)}rem; display: flex; @@ -46,10 +55,12 @@ align-items: center; margin-right: #{math.div(15, $base-font-size)}rem; padding: 0; + &--selected { @extend %toolbar-button--selected; } } + span { display: flex; align-items: center; @@ -66,11 +77,14 @@ justify-content: center; align-items: center; padding: 0; + &--selected { @extend %toolbar-button--selected; } } + margin-left: auto; + & span { padding-left: #{math.div(1, $base-font-size)}rem; display: flex; @@ -83,8 +97,11 @@ .toolbar__logo { margin-right: #{math.div(30, $base-font-size)}rem; + @include themify() { - & g, & path { + + & g, + & path { fill: getThemifyVariable('logo-color'); } } @@ -94,6 +111,7 @@ padding: #{math.div(10, $base-font-size)}rem #{math.div(20, $base-font-size)}rem; display: flex; align-items: center; + @include themify() { border-bottom: 1px dashed map-get($theme-map, 'nav-border-color'); } @@ -102,13 +120,18 @@ .toolbar__project-name-container { margin-left: #{math.div(10, $base-font-size)}rem; padding-left: #{math.div(10, $base-font-size)}rem; - display: flex; - align-items: center; + display: flex; + align-items: center; + + section { + margin-left: 5px; + } } .toolbar .editable-input__label { @include themify() { color: getThemifyVariable('secondary-text-color'); + & path { fill: getThemifyVariable('secondary-text-color'); } @@ -121,6 +144,7 @@ .toolbar__project-owner { margin-left: #{math.div(5, $base-font-size)}rem; + @include themify() { color: getThemifyVariable('secondary-text-color'); } @@ -128,12 +152,15 @@ .toolbar__autorefresh-label { cursor: pointer; + @include themify() { color: getThemifyVariable('secondary-text-color'); + &:hover { color: getThemifyVariable('logo-color'); } } + margin-left: #{math.div(5, $base-font-size)}rem; font-size: #{math.div(12, $base-font-size)}rem; } @@ -143,9 +170,10 @@ align-items: center; } -.checkbox__autorefresh{ +.checkbox__autorefresh { cursor: pointer; - @include themify(){ - accent-color:getThemifyVariable('logo-color'); + + @include themify() { + accent-color: getThemifyVariable('logo-color'); } -} +} \ No newline at end of file diff --git a/server/controllers/collection.controller/addProjectToCollection.js b/server/controllers/collection.controller/addProjectToCollection.js index 74a4c3db38..532d6fd328 100644 --- a/server/controllers/collection.controller/addProjectToCollection.js +++ b/server/controllers/collection.controller/addProjectToCollection.js @@ -60,7 +60,7 @@ export default function addProjectToCollection(req, res) { { path: 'owner', select: ['id', 'username'] }, { path: 'items.project', - select: ['id', 'name', 'slug'], + select: ['id', 'name', 'slug', 'visibility'], populate: { path: 'user', select: ['username'] diff --git a/server/controllers/collection.controller/createCollection.js b/server/controllers/collection.controller/createCollection.js index 61838ccd87..0332bebbb3 100644 --- a/server/controllers/collection.controller/createCollection.js +++ b/server/controllers/collection.controller/createCollection.js @@ -24,7 +24,7 @@ export default function createCollection(req, res) { { path: 'owner', select: ['id', 'username'] }, { path: 'items.project', - select: ['id', 'name', 'slug'], + select: ['id', 'name', 'slug', 'visibility'], populate: { path: 'user', select: ['username'] diff --git a/server/controllers/collection.controller/listCollections.js b/server/controllers/collection.controller/listCollections.js index fb5c70cff8..89e37c0cdf 100644 --- a/server/controllers/collection.controller/listCollections.js +++ b/server/controllers/collection.controller/listCollections.js @@ -32,7 +32,7 @@ export default function listCollections(req, res) { { path: 'owner', select: ['id', 'username'] }, { path: 'items.project', - select: ['id', 'name', 'slug'], + select: ['id', 'name', 'slug', 'visibility'], populate: { path: 'user', select: ['username'] diff --git a/server/controllers/collection.controller/removeProjectFromCollection.js b/server/controllers/collection.controller/removeProjectFromCollection.js index 56a83df297..66cd5f3b58 100644 --- a/server/controllers/collection.controller/removeProjectFromCollection.js +++ b/server/controllers/collection.controller/removeProjectFromCollection.js @@ -41,7 +41,7 @@ export default function addProjectToCollection(req, res) { { path: 'owner', select: ['id', 'username'] }, { path: 'items.project', - select: ['id', 'name', 'slug'], + select: ['id', 'name', 'slug', 'visibility'], populate: { path: 'user', select: ['username'] diff --git a/server/controllers/collection.controller/updateCollection.js b/server/controllers/collection.controller/updateCollection.js index 5df1178d7d..9497b05228 100644 --- a/server/controllers/collection.controller/updateCollection.js +++ b/server/controllers/collection.controller/updateCollection.js @@ -43,7 +43,7 @@ export default function createCollection(req, res) { { path: 'owner', select: ['id', 'username'] }, { path: 'items.project', - select: ['id', 'name', 'slug'], + select: ['id', 'name', 'slug', 'visibility'], populate: { path: 'user', select: ['username'] diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js index 817384f82d..46bc5b3491 100644 --- a/server/controllers/project.controller.js +++ b/server/controllers/project.controller.js @@ -96,6 +96,7 @@ export function getProject(req, res) { $or: [{ _id: projectId }, { slug: projectId }] }) .populate('user', 'username') + .select('visibility') .exec((err, project) => { // eslint-disable-line if (err) { console.log(err); @@ -270,3 +271,40 @@ export function downloadProjectAsZip(req, res) { buildZip(project, req, res); }); } + +export async function changeProjectVisibility(req, res) { + try { + const { projectId, visibility: newVisibility } = req.body; + + const project = await Project.findOne({ + $or: [{ _id: projectId }, { slug: projectId }] + }); + + if (!project) { + return res + .status(404) + .json({ success: false, message: 'No project found.' }); + } + + if (newVisibility !== 'Private' && newVisibility !== 'Public') { + return res.status(400).json({ success: false, message: 'Invalid data.' }); + } + + const updatedProject = await Project.findByIdAndUpdate( + projectId, + { + visibility: newVisibility + }, + { + new: true + } + ); + const updatedProjectWithoutFiles = await Project.findById( + updatedProject._id + ).select('-files'); + + return res.status(200).json(updatedProjectWithoutFiles); + } catch (error) { + return res.status(500).json(error); + } +} diff --git a/server/controllers/project.controller/getProjectsForUser.js b/server/controllers/project.controller/getProjectsForUser.js index a811ecd55c..19ce606f50 100644 --- a/server/controllers/project.controller/getProjectsForUser.js +++ b/server/controllers/project.controller/getProjectsForUser.js @@ -23,7 +23,7 @@ const createCoreHandler = (mapProjectsToResponse) => async (req, res) => { } const projects = await Project.find({ user: user._id }) .sort('-createdAt') - .select('name files id createdAt updatedAt') + .select('name files id createdAt updatedAt visibility') .exec(); const response = mapProjectsToResponse(projects); res.json(response); diff --git a/server/models/project.js b/server/models/project.js index 4dfd25aeef..4b5d37d1a1 100644 --- a/server/models/project.js +++ b/server/models/project.js @@ -38,6 +38,10 @@ const projectSchema = new Schema( serveSecure: { type: Boolean, default: false }, files: { type: [fileSchema] }, _id: { type: String, default: shortid.generate }, + visibility: { + type: String, + enum: ['Private', 'Public'] + }, slug: { type: String } }, { timestamps: true, usePushEach: true } @@ -57,7 +61,7 @@ projectSchema.pre('save', function generateSlug(next) { if (!project.slug) { project.slug = slugify(project.name, '_'); } - + project.visibility = 'Public'; return next(); }); diff --git a/server/routes/project.routes.js b/server/routes/project.routes.js index 469eb43e92..0610f929ab 100644 --- a/server/routes/project.routes.js +++ b/server/routes/project.routes.js @@ -26,4 +26,6 @@ router.get('/:username/projects', ProjectController.getProjectsForUser); router.get('/projects/:project_id/zip', ProjectController.downloadProjectAsZip); +router.patch('/project/visibility', ProjectController.changeProjectVisibility); + export default router; From 907c057b03a9fb23452a192083b52da236e3a015 Mon Sep 17 00:00:00 2001 From: Vivek Date: Sat, 17 Feb 2024 14:25:22 +0530 Subject: [PATCH 2/7] frontend done --- .../modules/IDE/components/Header/Toolbar.jsx | 6 +- client/modules/IDE/components/SketchList.jsx | 83 +++++++++++------- client/modules/User/components/Collection.jsx | 4 +- client/styles/components/_sketch-list.scss | 85 ++++++------------- server/controllers/project.controller.js | 30 ++++++- .../project.controller/getProjectsForUser.js | 23 ++++- 6 files changed, 131 insertions(+), 100 deletions(-) diff --git a/client/modules/IDE/components/Header/Toolbar.jsx b/client/modules/IDE/components/Header/Toolbar.jsx index 0480b05ddc..e64a0734e8 100644 --- a/client/modules/IDE/components/Header/Toolbar.jsx +++ b/client/modules/IDE/components/Header/Toolbar.jsx @@ -28,11 +28,13 @@ const Toolbar = (props) => { (state) => state.ide ); const project = useSelector((state) => state.project); + const user = useSelector((state) => state.user); const autorefresh = useSelector((state) => state.preferences.autorefresh); const dispatch = useDispatch(); const { t } = useTranslation(); const [visibility, setVisibility] = React.useState(project.visibility); - console.log(project); + + const userIsOwner = user?.username === project.owner?.username; const toggleVisibility = () => { try { setVisibility((prev) => (prev === 'Public' ? 'Private' : 'Public')); @@ -128,7 +130,7 @@ const Toolbar = (props) => { return null; })()} - {project?.owner && ( + {userIsOwner && project.owner && (
{visibility === 'Private' ? ( diff --git a/client/modules/IDE/components/SketchList.jsx b/client/modules/IDE/components/SketchList.jsx index d757313e67..1f28ca1434 100644 --- a/client/modules/IDE/components/SketchList.jsx +++ b/client/modules/IDE/components/SketchList.jsx @@ -179,7 +179,6 @@ class SketchListRowBase extends React.Component { }; render() { - console.log(this.props.sketch.visibility); const { sketch, username, mobile } = this.props; const { renameOpen, renameValue } = this.state; let url = `/${username}/sketches/${sketch.id}`; @@ -188,7 +187,7 @@ class SketchListRowBase extends React.Component { } const title = (

- Make {this.props.sketch.name} + Make {this.props.sketch.name}{' '} {this.state.newVisibility === 'Private' ? 'Public' : 'Private'} @@ -215,18 +214,41 @@ class SketchListRowBase extends React.Component { closeOverlay={() => this.setState({ visibleDialogOpen: false })} >

-
- -
    -
  • The sketch will not be visible to others.
  • -
  • - Other users will not be able to fork, edit or look the sketch -
  • -
  • - You can always comeback and change the sketchs visibility - nonetheless -
  • -
+ {this.state.newVisibility === 'Public' ? ( +
    +
  • + Your sketch will stay private and will not be seen by + others. +
  • +
  • + Others will not be able to copy, change, or even see your + sketch. +
  • +
  • This keeps your work safe and private.
  • + +
  • + you can focus on being creative without worrying about + others seeing your work. +
  • +
  • + You can always come back and adjust who can see your sketch. +
  • +
+ ) : ( +
    +
  • Your sketch will be visible to everyone.
  • +
  • Others can copy, edit, or just check out your sketch.
  • + +
  • + This helps everyone share ideas and be more creative + together. +
  • +
  • + You can always change who can see your sketch whenever you + want. +
  • +
+ )}
- {userIsOwner && project.owner && ( -
- {visibility === 'Private' ? ( - - ) : ( - - )} -
- )} @@ -267,10 +268,8 @@ class SketchListRowBase extends React.Component { key={sketch.id} onClick={this.handleRowClick} > - - {this.state.newVisibility === 'Private' &&

Lock

} - {this.state.newVisibility} - {name} + + {this.state.newVisibility === 'Private' && } {name} {formatDateCell(sketch.createdAt, mobile)} {formatDateCell(sketch.updatedAt, mobile)} diff --git a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap index 051c95be19..3913517623 100644 --- a/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap +++ b/client/modules/IDE/components/__snapshots__/SketchList.unit.test.jsx.snap @@ -88,12 +88,14 @@ exports[` snapshot testing 1`] = ` - + testsketch1 diff --git a/client/modules/IDE/pages/IDEView.jsx b/client/modules/IDE/pages/IDEView.jsx index f4bab9db05..90e5fcccdb 100644 --- a/client/modules/IDE/pages/IDEView.jsx +++ b/client/modules/IDE/pages/IDEView.jsx @@ -1,5 +1,5 @@ import React, { useEffect, useRef, useState } from 'react'; -import { useLocation, Prompt, useParams } from 'react-router-dom'; +import { useLocation, Prompt, useParams, useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { useTranslation } from 'react-i18next'; import { Helmet } from 'react-helmet'; @@ -11,7 +11,6 @@ import PreviewFrame from '../components/PreviewFrame'; import Console from '../components/Console'; import Toast from '../components/Toast'; import { updateFileContent } from '../actions/files'; - import { autosaveProject, clearPersistedState, @@ -103,7 +102,7 @@ const IDEView = () => { const [sidebarSize, setSidebarSize] = useState(160); const [isOverlayVisible, setIsOverlayVisible] = useState(false); const [MaxSize, setMaxSize] = useState(window.innerWidth); - + const history = useHistory(); const cmRef = useRef({}); const autosaveIntervalRef = useRef(null); @@ -124,6 +123,13 @@ const IDEView = () => { } }, [dispatch, params, project.id]); + useEffect(() => { + if (!isUserOwner && project.visibility === 'Private') { + // TODO: we might want to have a 'Sorry, this sketch is private' page for this + history.push('/'); + } + }, [isUserOwner, project.visibility, history]); + const autosaveAllowed = isUserOwner && project.id && preferences.autosave; const shouldAutosave = autosaveAllowed && ide.unsavedChanges; diff --git a/client/styles/components/_sketch-list.scss b/client/styles/components/_sketch-list.scss index ca0e4bd727..003836f285 100644 --- a/client/styles/components/_sketch-list.scss +++ b/client/styles/components/_sketch-list.scss @@ -6,6 +6,13 @@ } } +.sketch-visibility__icons { + height: 30px; + width: 30px; + display: flex; + gap: 5px; +} + .sketch-visibility { padding: 30px; display: flex; @@ -45,6 +52,7 @@ + .sketches-table-container { overflow-y: auto; max-width: 100%; @@ -71,6 +79,8 @@ flex-direction: column; gap: #{math.div(12, $base-font-size)}rem; + + .sketches-table__row { margin: 0; position: relative; @@ -85,13 +95,14 @@ background-color: getThemifyVariable("search-background-color") !important; } - >th { - padding-left: 0; - width: 100%; + .sketches-table_name { + display: flex; + gap: 5px; font-weight: bold; - margin-bottom: #{math.div(6, $base-font-size)}rem; + align-items: center; } + >td { padding-left: 0; width: 30%; diff --git a/client/styles/components/_toolbar.scss b/client/styles/components/_toolbar.scss index fad9fce7ef..77f8aee691 100644 --- a/client/styles/components/_toolbar.scss +++ b/client/styles/components/_toolbar.scss @@ -117,15 +117,31 @@ } } +.lock-icon { + height: 60%; + width: 60%; +} + +.unlock-icon { + height: 60%; + width: 60%; +} + + .toolbar__project-name-container { margin-left: #{math.div(10, $base-font-size)}rem; padding-left: #{math.div(10, $base-font-size)}rem; display: flex; align-items: center; - section { - margin-left: 5px; + >section { + display: flex; + align-items: center; + justify-content: center; + height: 30px; + width: 30px; } + } .toolbar .editable-input__label { diff --git a/server/controllers/project.controller.js b/server/controllers/project.controller.js index 3e6bb38209..3732a76dd3 100644 --- a/server/controllers/project.controller.js +++ b/server/controllers/project.controller.js @@ -8,7 +8,6 @@ import slugify from 'slugify'; import Project from '../models/project'; import User from '../models/user'; import { resolvePathToFile } from '../utils/filePath'; -import { get404Sketch } from '../views/404Page'; import generateFileSystemSafeName from '../utils/generateFileSystemSafeName'; export { @@ -104,14 +103,6 @@ export function getProject(req, res) { .status(404) .send({ message: 'Project with that id does not exist' }); } - // TODO: most probably, this will require its own 'SketchIsPrivate' page in future. - if ( - project.visibility === 'Private' && - project.owner.username !== username - ) { - res.status(401); - return get404Sketch((html) => res.send(html)); - } return res.json(project); }); }); @@ -131,26 +122,6 @@ export function getProjectsForUserId(userId) { }); } -// eslint-disable-next-line consistent-return -// export async function getProjectsForUserId(userId) { -// try { -// const projects = await Project.find({ user: userId }) -// // .select('name files id createdAt updatedAt visibility owner.username') -// .sort('-createdAt'); -// // const user = await User.findById(userId); - -// // const filteredProjects = projects.filter((project) => { -// // const userIsOwner = project.owner.username === user.username; -// // return userIsOwner || project.visibility === 'Public'; -// // }); - -// return projects; -// } catch (error) { -// console.log(error); -// console.log(error); -// } -// } - export function getProjectAsset(req, res) { const projectId = req.params.project_id; Project.findOne({ $or: [{ _id: projectId }, { slug: projectId }] }) diff --git a/server/controllers/project.controller/getProjectsForUser.js b/server/controllers/project.controller/getProjectsForUser.js index 897ec70fa2..f2ad5e7003 100644 --- a/server/controllers/project.controller/getProjectsForUser.js +++ b/server/controllers/project.controller/getProjectsForUser.js @@ -11,7 +11,6 @@ const createCoreHandler = (mapProjectsToResponse) => async (req, res) => { try { const { username } = req.params; const currentUser = req.user?._id || ''; - console.log('current user', currentUser); if (!username) { res.status(422).json({ message: 'Username not provided' }); @@ -28,15 +27,20 @@ const createCoreHandler = (mapProjectsToResponse) => async (req, res) => { const projects = await Project.find({ user: user._id }) .sort('-createdAt') - .select('name files user id createdAt updatedAt visibility') + .select('name files id createdAt updatedAt visibility') .exec(); const publicProjectsOnly = projects.filter( (project) => project.visibility === 'Public' ); + if (!req.user) { + res.json(publicProjectsOnly); + return; + } + const response = mapProjectsToResponse( - currentUser !== user._id ? publicProjectsOnly : projects + !currentUser.equals(user._id) ? publicProjectsOnly : projects ); res.json(response); From ae08e56e428dc1fe3563bcc71f82d24455860164 Mon Sep 17 00:00:00 2001 From: Vivek Date: Sat, 4 May 2024 13:38:06 +0530 Subject: [PATCH 4/7] UI changes --- client/common/icons.jsx | 4 - client/images/lock.svg | 2 - client/images/lockgif.gif | Bin 5447 -> 0 bytes client/images/unlock.svg | 5 - client/modules/IDE/actions/project.js | 4 +- .../IDE/components/Header/MobileNav.jsx | 51 +++++- .../modules/IDE/components/Header/Toolbar.jsx | 40 +++-- client/modules/IDE/components/SketchList.jsx | 157 ++++++++---------- client/modules/User/components/Collection.jsx | 2 +- client/styles/components/_toggle.scss | 63 +++++++ client/styles/components/_toolbar.scss | 15 ++ client/styles/main.scss | 4 +- .../project.controller/getProjectsForUser.js | 28 ++-- server/models/project.js | 3 +- 14 files changed, 234 insertions(+), 144 deletions(-) delete mode 100644 client/images/lock.svg delete mode 100644 client/images/lockgif.gif delete mode 100644 client/images/unlock.svg create mode 100644 client/styles/components/_toggle.scss diff --git a/client/common/icons.jsx b/client/common/icons.jsx index 5fe685be9b..13d2e18092 100644 --- a/client/common/icons.jsx +++ b/client/common/icons.jsx @@ -25,8 +25,6 @@ import CircleInfo from '../images/circle-info.svg'; import Add from '../images/add.svg'; import Filter from '../images/filter.svg'; import Cross from '../images/cross.svg'; -import Lock from '../images/lock.svg'; -import UnLock from '../images/unlock.svg'; // HOC that adds the right web accessibility props // https://www.scottohara.me/blog/2019/05/22/contextual-images-svgs-and-a11y.html @@ -104,5 +102,3 @@ export const CircleFolderIcon = withLabel(CircleFolder); export const CircleInfoIcon = withLabel(CircleInfo); export const AddIcon = withLabel(Add); export const FilterIcon = withLabel(Filter); -export const LockIcon = withLabel(Lock); -export const UnlockIcon = withLabel(UnLock); diff --git a/client/images/lock.svg b/client/images/lock.svg deleted file mode 100644 index 6f4d4af4c8..0000000000 --- a/client/images/lock.svg +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/client/images/lockgif.gif b/client/images/lockgif.gif deleted file mode 100644 index b0a08e2cca80af283cee872819ffca95ecde7913..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5447 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 - - - - diff --git a/client/modules/IDE/actions/project.js b/client/modules/IDE/actions/project.js index 4516fa669d..e55bac1a6a 100644 --- a/client/modules/IDE/actions/project.js +++ b/client/modules/IDE/actions/project.js @@ -411,7 +411,7 @@ export function deleteProject(id) { }; } -export function changeVisibility(projectId, visibility) { +export function changeVisibility(projectId, projectName, visibility) { return (dispatch) => apiClient .patch('/project/visibility', { projectId, visibility }) @@ -423,7 +423,7 @@ export function changeVisibility(projectId, visibility) { payload: { visibility: response.data.visibility } }); - dispatch(setToastText(`Sketch is ${newVisibility}`)); + dispatch(setToastText(`The ${projectName} is now ${newVisibility}!`)); dispatch(showToast(2000)); }) .catch((error) => { diff --git a/client/modules/IDE/components/Header/MobileNav.jsx b/client/modules/IDE/components/Header/MobileNav.jsx index 9d81cf6d22..84344c6443 100644 --- a/client/modules/IDE/components/Header/MobileNav.jsx +++ b/client/modules/IDE/components/Header/MobileNav.jsx @@ -35,6 +35,7 @@ import { setLanguage } from '../../actions/preferences'; import Overlay from '../../../App/components/Overlay'; import ProjectName from './ProjectName'; import CollectionCreate from '../../../User/components/CollectionCreate'; +import { changeVisibility } from '../../actions/project'; const Nav = styled(NavBar)` background: ${prop('MobilePanel.default.background')}; @@ -75,6 +76,13 @@ const Title = styled.div` margin: 0; } + > section { + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + } + > h5 { font-size: ${remSize(13)}; font-weight: normal; @@ -203,6 +211,7 @@ const LanguageSelect = styled.div` const MobileNav = () => { const project = useSelector((state) => state.project); const user = useSelector((state) => state.user); + const dispatch = useDispatch(); const { t } = useTranslation(); @@ -228,19 +237,53 @@ const MobileNav = () => { } const title = useMemo(resolveTitle, [pageName, project.name]); + const userIsOwner = user?.username === project.owner?.username; const Logo = AsteriskIcon; + + const toggleVisibility = (e) => { + try { + const isChecked = e.target.checked; + + dispatch( + changeVisibility( + project.id, + project.name, + isChecked ? 'Private' : 'Public' + ) + ); + } catch (error) { + console.log(error); + } + }; return (