Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
108 changes: 106 additions & 2 deletions client/modules/IDE/components/SketchList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ import { connect } from 'react-redux';
import { useTranslation } from 'react-i18next';
import { bindActionCreators } from 'redux';
import * as ProjectsActions from '../actions/projects';
import * as ProjectActions from '../actions/project';
import * as CollectionsActions from '../actions/collections';
import * as ToastActions from '../actions/toast';
import * as SortingActions from '../actions/sorting';
import { getConfig } from '../../../utils/getConfig';
import getSortedSketches from '../selectors/projects';
import Loader from '../../App/components/loader';
import Overlay from '../../App/components/Overlay';
Expand All @@ -18,6 +20,8 @@ import ArrowUpIcon from '../../../images/sort-arrow-up.svg';
import ArrowDownIcon from '../../../images/sort-arrow-down.svg';
import SketchListRowBase from './SketchListRowBase';

const ROOT_URL = getConfig('API_URL');

const SketchList = ({
user,
getProjects,
Expand All @@ -27,12 +31,72 @@ const SketchList = ({
sorting,
toggleDirectionForField,
resetSorting,
mobile
mobile,
changeVisibility,
deleteProject
}) => {
const [isInitialDataLoad, setIsInitialDataLoad] = useState(true);
const [sketchToAddToCollection, setSketchToAddToCollection] = useState(null);
const [selectedSketches, setSelectedSketches] = useState([]);
const { t } = useTranslation();

const handleSelectSketch = (sketchId) => {
setSelectedSketches((prev) =>
prev.includes(sketchId)
? prev.filter((id) => id !== sketchId)
: [...prev, sketchId]
);
};

const handleSelectAll = () => {
if (selectedSketches.length === sketches.length) {
setSelectedSketches([]);
} else {
setSelectedSketches(sketches.map((sketch) => sketch.id));
}
};

const clearSelection = () => setSelectedSketches([]);

const handleBatchVisibilityChange = (visibility) => {
if (!visibility) return;
selectedSketches.forEach((id) => {
const sketch = sketches.find((s) => s.id === id);
if (sketch) {
changeVisibility(id, sketch.name, visibility, t);
}
});
clearSelection();
};

const handleBatchDownload = () => {
// TODO: implement batch download
selectedSketches.forEach((id) => {
const sketch = sketches.find((s) => s.id === id);
if (sketch) {
const downloadLink = document.createElement('a');
downloadLink.href = `${ROOT_URL}/projects/${id}/zip`;
downloadLink.download = `${sketch.name}.zip`;
document.body.appendChild(downloadLink);
downloadLink.click();
document.body.removeChild(downloadLink);
}
});
};

const handleBatchDelete = () => {
if (
window.confirm(
t('SketchList.BatchDeleteConfirmation', {
count: selectedSketches.length
})
)
) {
selectedSketches.forEach((id) => deleteProject(id));
clearSelection();
}
};

useEffect(() => {
getProjects(username);
resetSorting();
Expand Down Expand Up @@ -128,13 +192,45 @@ const SketchList = ({
</Helmet>
{renderLoader()}
{renderEmptyTable()}
{selectedSketches.length > 0 && (
<div className="sketch-list-toolbar">
<span>
{t('SketchList.SelectedCount', { count: selectedSketches.length })}
</span>
<button onClick={clearSelection}>
{t('SketchList.ClearSelection')}
</button>
<button onClick={handleBatchDownload}>
{t('SketchList.BatchDownload')}
</button>
<button onClick={handleBatchDelete}>
{t('SketchList.BatchDelete')}
</button>
<select onChange={(e) => handleBatchVisibilityChange(e.target.value)}>
<option value="">{t('SketchList.ChangeVisibility')}</option>
<option value="Public">{t('Visibility.Public.Label')}</option>
<option value="Private">{t('Visibility.Private.Label')}</option>
</select>
</div>
)}
{hasSketches() && (
<table
className="sketches-table"
summary={t('SketchList.TableSummary')}
>
<thead>
<tr>
<th scope="col">
<input
type="checkbox"
checked={
selectedSketches.length === sketches.length &&
sketches.length > 0
}
onChange={handleSelectAll}
aria-label={t('SketchList.SelectAll')}
/>
</th>
{renderFieldHeader('name', t('SketchList.HeaderName'))}
{renderFieldHeader(
'createdAt',
Expand Down Expand Up @@ -162,6 +258,8 @@ const SketchList = ({
user={user}
username={username}
onAddToCollection={() => setSketchToAddToCollection(sketch)}
selected={selectedSketches.includes(sketch.id)}
onSelect={() => handleSelectSketch(sketch.id)}
t={t}
/>
))}
Expand Down Expand Up @@ -204,6 +302,8 @@ SketchList.propTypes = {
field: PropTypes.string.isRequired,
direction: PropTypes.string.isRequired
}).isRequired,
changeVisibility: PropTypes.func.isRequired,
deleteProject: PropTypes.func.isRequired,
mobile: PropTypes.bool
};

Expand All @@ -229,7 +329,11 @@ function mapDispatchToProps(dispatch) {
ProjectsActions,
CollectionsActions,
ToastActions,
SortingActions
SortingActions,
{
changeVisibility: ProjectActions.changeVisibility,
deleteProject: ProjectActions.deleteProject
}
),
dispatch
);
Expand Down
18 changes: 16 additions & 2 deletions client/modules/IDE/components/SketchListRowBase.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,9 @@ const SketchListRowBase = ({
changeVisibility,
t,
mobile,
onAddToCollection
onAddToCollection,
selected,
onSelect
}) => {
const [renameOpen, setRenameOpen] = useState(false);
const [renameValue, setRenameValue] = useState(sketch.name);
Expand Down Expand Up @@ -122,6 +124,14 @@ const SketchListRowBase = ({

return (
<tr className="sketches-table__row">
<td>
<input
type="checkbox"
checked={selected}
onChange={onSelect}
aria-label={`Select ${sketch.name}`}
/>
</td>
<th scope="row">{name}</th>
<td>{formatDateCell(sketch.createdAt, mobile)}</td>
<td>{formatDateCell(sketch.updatedAt, mobile)}</td>
Expand Down Expand Up @@ -179,12 +189,16 @@ SketchListRowBase.propTypes = {
showShareModal: PropTypes.func.isRequired,
changeVisibility: PropTypes.func.isRequired,
onAddToCollection: PropTypes.func.isRequired,
selected: PropTypes.bool,
onSelect: PropTypes.func,
mobile: PropTypes.bool,
t: PropTypes.func.isRequired
};

SketchListRowBase.defaultProps = {
mobile: false
mobile: false,
selected: false,
onSelect: () => {}
};

function mapDispatchToPropsSketchListRow(dispatch) {
Expand Down
50 changes: 35 additions & 15 deletions client/styles/components/_nav.scss
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
@include themify() {
border-bottom: 1px dashed map-get($theme-map, 'nav-border-color');
}

// padding-left: #{math.div(20, $base-font-size)}rem;
}

.nav__menubar {
display: flex;
flex-direction: row;
width:100%;
width: 100%;
justify-content: space-between;
}

Expand Down Expand Up @@ -57,11 +58,11 @@
}

// base focus styles
.nav__item button:focus {
.nav__item button:focus {
@include themify() {
background-color: getThemifyVariable('nav-hover-color');
}

.nav__item-header {
@include themify() {
color: getThemifyVariable('button-hover-color');
Expand All @@ -73,18 +74,20 @@
@include themify() {
fill: getThemifyVariable('button-hover-color');
}
}
}
}


.nav__dropdown-item {

& button:focus,
& a:focus {
@include themify() {
color: getThemifyVariable('button-hover-color');
background-color: getThemifyVariable('nav-hover-color');
}
}

& button:focus .nav__keyboard-shortcut,
& a:focus .nav__keyboard-shortcut {
@include themify() {
Expand All @@ -93,6 +96,7 @@
}

&.nav__dropdown-item--disabled {

& button,
& a,
& button:hover,
Expand Down Expand Up @@ -131,8 +135,9 @@
color: getThemifyVariable('button-hover-color');
}
}

& g, & path {

& g,
& path {
@include themify() {
fill: getThemifyVariable('nav-hover-color');
}
Expand All @@ -144,38 +149,48 @@
fill: getThemifyVariable('button-hover-color');
}
}

.nav__back-icon g,
.nav__back-icon path {
@include themify() {
fill: getThemifyVariable('button-hover-color');
}
}
}

.nav__item-header:hover {
@include themify() {
color: getThemifyVariable('nav-hover-color');
}
& g, & path {

& g,
& path {
@include themify() {
fill: getThemifyVariable('nav-hover-color');
}
}
}

.nav__item-header-triangle {
margin-left: #{math.div(5, $base-font-size)}rem;
margin-left: #{math.div(5, $base-font-size)}rem;
}

.nav__dropdown {
@include themify() {
color: getThemifyVariable('nav-hover-color');
}
color: getThemifyVariable('nav-hover-color');
}
}

.nav__item-header-triangle {
margin-left: #{math.div(5, $base-font-size)}rem;
margin-left: #{math.div(5, $base-font-size)}rem;
}

.nav__dropdown {
@extend %dropdown-open-left;
display: none;
max-height: 60vh;
overflow-y: auto;

.nav__item--open & {
display: flex;
}
Expand Down Expand Up @@ -211,6 +226,7 @@
// }

.nav__dropdown-item {

& button,
& a {
width: 100%;
Expand Down Expand Up @@ -240,8 +256,8 @@

}

.svg__logo g path{
.svg__logo g path {

@include themify() {
// Set internal color of the logo;
fill: getThemifyVariable('logo-background-color');
Expand All @@ -266,15 +282,19 @@
}

.nav__back-icon {
& g, & path {

& g,
& path {
opacity: 1;

@include themify() {
fill: getThemifyVariable('inactive-text-color');
}
}

margin-right: #{math.div(5, $base-font-size)}rem;
}

.nav__back-link {
display: flex;
}
}
Loading