1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-08 10:22:46 +00:00

feat: metadata view permission (#6500)

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-08-06 14:32:15 +08:00
committed by GitHub
parent fd12baeea1
commit 6d0713ef00
22 changed files with 121 additions and 63 deletions

View File

@@ -19,7 +19,7 @@
"@seafile/sdoc-editor": "1.0.41", "@seafile/sdoc-editor": "1.0.41",
"@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.107", "@seafile/seafile-editor": "1.0.107",
"@seafile/sf-metadata-ui-component": "0.0.16", "@seafile/sf-metadata-ui-component": "0.0.17",
"@uiw/codemirror-extensions-langs": "^4.19.4", "@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4", "@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4", "chart.js": "2.9.4",
@@ -5092,9 +5092,9 @@
} }
}, },
"node_modules/@seafile/sf-metadata-ui-component": { "node_modules/@seafile/sf-metadata-ui-component": {
"version": "0.0.16", "version": "0.0.17",
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.16.tgz", "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.17.tgz",
"integrity": "sha512-f5yOmP2LPWdlJfhlM5RKu7hmmGlNxlV7V0qjP2XNne4/s0Z7elqeZ4ZxRKtSTZwAHtgyazHSJ93EqEwU67wQfg==", "integrity": "sha512-2m8bIAXxzw0ZOMFsGesh4lXQdoOL1/0ONP0pwFxEVWtyG6zRThiFL80OgqGKjluVlehxpfgOaTgIQYr6Wy6IEw==",
"dependencies": { "dependencies": {
"@seafile/seafile-calendar": "0.0.24", "@seafile/seafile-calendar": "0.0.24",
"@seafile/seafile-editor": "~1.0.102", "@seafile/seafile-editor": "~1.0.102",

View File

@@ -14,7 +14,7 @@
"@seafile/sdoc-editor": "1.0.41", "@seafile/sdoc-editor": "1.0.41",
"@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.107", "@seafile/seafile-editor": "1.0.107",
"@seafile/sf-metadata-ui-component": "0.0.16", "@seafile/sf-metadata-ui-component": "0.0.17",
"@uiw/codemirror-extensions-langs": "^4.19.4", "@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4", "@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4", "chart.js": "2.9.4",

View File

@@ -7,7 +7,9 @@ import { getValidFilters, CommonlyUsedHotkey } from '../../_basic';
import { gettext } from '../../../../utils/constants'; import { gettext } from '../../../../utils/constants';
import { FilterPopover } from '../popover'; import { FilterPopover } from '../popover';
const FilterSetter = ({ columns, const FilterSetter = ({
readOnly,
columns,
wrapperClass, wrapperClass,
filters: propsFilters, filters: propsFilters,
isNeedSubmit, isNeedSubmit,
@@ -67,6 +69,7 @@ const FilterSetter = ({ columns,
placement="bottom-end" placement="bottom-end"
filtersClassName={filtersClassName} filtersClassName={filtersClassName}
target={target} target={target}
readOnly={readOnly}
isNeedSubmit={isNeedSubmit} isNeedSubmit={isNeedSubmit}
columns={columns} columns={columns}
collaborators={collaborators} collaborators={collaborators}
@@ -83,6 +86,7 @@ const FilterSetter = ({ columns,
}; };
FilterSetter.propTypes = { FilterSetter.propTypes = {
readOnly: PropTypes.bool,
wrapperClass: PropTypes.string, wrapperClass: PropTypes.string,
filtersClassName: PropTypes.string, filtersClassName: PropTypes.string,
target: PropTypes.string, target: PropTypes.string,

View File

@@ -6,7 +6,7 @@ import { CommonlyUsedHotkey, getValidGroupbys, SUPPORT_GROUP_COLUMN_TYPES } from
import { gettext } from '../../utils'; import { gettext } from '../../utils';
import { GroupbysPopover } from '../popover'; import { GroupbysPopover } from '../popover';
const GroupbySetter = ({ columns: allColumns, groupbys: propsGroupbys, wrapperClass, target, modifyGroupbys }) => { const GroupbySetter = ({ columns: allColumns, readOnly, groupbys: propsGroupbys, wrapperClass, target, modifyGroupbys }) => {
const [isShowSetter, setShowSetter] = useState(false); const [isShowSetter, setShowSetter] = useState(false);
const columns = useMemo(() => { const columns = useMemo(() => {
@@ -56,6 +56,7 @@ const GroupbySetter = ({ columns: allColumns, groupbys: propsGroupbys, wrapperCl
/> />
{isShowSetter && ( {isShowSetter && (
<GroupbysPopover <GroupbysPopover
readOnly={readOnly}
groupbys={groupbys} groupbys={groupbys}
target={target} target={target}
placement="bottom-end" placement="bottom-end"
@@ -75,6 +76,7 @@ GroupbySetter.defaultProps = {
}; };
GroupbySetter.propTypes = { GroupbySetter.propTypes = {
readOnly: PropTypes.bool,
wrapperClass: PropTypes.string, wrapperClass: PropTypes.string,
columns: PropTypes.array, columns: PropTypes.array,
groupbys: PropTypes.array, // valid groupbys groupbys: PropTypes.array, // valid groupbys

View File

@@ -6,7 +6,7 @@ import { CommonlyUsedHotkey } from '../../_basic';
import { gettext } from '../../utils'; import { gettext } from '../../utils';
import { HideColumnPopover } from '../popover'; import { HideColumnPopover } from '../popover';
const HideColumnSetter = ({ columns, wrapperClass, target, hiddenColumns, modifyHiddenColumns }) => { const HideColumnSetter = ({ readOnly, columns, wrapperClass, target, hiddenColumns, modifyHiddenColumns }) => {
const [isShowSetter, setShowSetter] = useState(false); const [isShowSetter, setShowSetter] = useState(false);
const validHiddenColumns = useMemo(() => { const validHiddenColumns = useMemo(() => {
@@ -50,6 +50,7 @@ const HideColumnSetter = ({ columns, wrapperClass, target, hiddenColumns, modify
/> />
{isShowSetter && ( {isShowSetter && (
<HideColumnPopover <HideColumnPopover
readOnly={readOnly}
hiddenColumns={validHiddenColumns} hiddenColumns={validHiddenColumns}
target={target} target={target}
placement="bottom-end" placement="bottom-end"
@@ -63,6 +64,7 @@ const HideColumnSetter = ({ columns, wrapperClass, target, hiddenColumns, modify
}; };
HideColumnSetter.propTypes = { HideColumnSetter.propTypes = {
readOnly: PropTypes.bool,
wrapperClass: PropTypes.string, wrapperClass: PropTypes.string,
target: PropTypes.string, target: PropTypes.string,
hiddenColumns: PropTypes.array, hiddenColumns: PropTypes.array,

View File

@@ -6,7 +6,7 @@ import { getValidSorts, CommonlyUsedHotkey } from '../../_basic';
import { gettext } from '../../utils'; import { gettext } from '../../utils';
import { SortPopover } from '../popover'; import { SortPopover } from '../popover';
const SortSetter = ({ target, sorts: propsSorts, columns, isNeedSubmit, wrapperClass, modifySorts }) => { const SortSetter = ({ target, sorts: propsSorts, readOnly, columns, isNeedSubmit, wrapperClass, modifySorts }) => {
const [isShowSetter, setShowSetter] = useState(false); const [isShowSetter, setShowSetter] = useState(false);
const sorts = useMemo(() => { const sorts = useMemo(() => {
@@ -53,6 +53,7 @@ const SortSetter = ({ target, sorts: propsSorts, columns, isNeedSubmit, wrapperC
{isShowSetter && ( {isShowSetter && (
<SortPopover <SortPopover
isNeedSubmit={isNeedSubmit} isNeedSubmit={isNeedSubmit}
readOnly={readOnly}
target={target} target={target}
columns={columns} columns={columns}
sorts={sorts} sorts={sorts}
@@ -67,6 +68,7 @@ const SortSetter = ({ target, sorts: propsSorts, columns, isNeedSubmit, wrapperC
const propTypes = { const propTypes = {
readOnly: PropTypes.bool,
wrapperClass: PropTypes.string, wrapperClass: PropTypes.string,
target: PropTypes.string, target: PropTypes.string,
isNeedSubmit: PropTypes.bool, isNeedSubmit: PropTypes.bool,

View File

@@ -145,7 +145,7 @@ class FilterPopover extends Component {
}; };
render() { render() {
const { target, columns, placement } = this.props; const { readOnly, target, columns, placement } = this.props;
const { filters, filterConjunction } = this.state; const { filters, filterConjunction } = this.state;
const canAddFilter = columns.length > 0; const canAddFilter = columns.length > 0;
return ( return (
@@ -169,17 +169,19 @@ class FilterPopover extends Component {
deleteFilter={this.deleteFilter} deleteFilter={this.deleteFilter}
updateFilterConjunction={this.updateFilterConjunction} updateFilterConjunction={this.updateFilterConjunction}
collaborators={this.props.collaborators} collaborators={this.props.collaborators}
readOnly={false} readOnly={readOnly}
scheduleUpdate={scheduleUpdate} scheduleUpdate={scheduleUpdate}
isPre={this.props.isPre} isPre={this.props.isPre}
/> />
<CustomizeAddTool {!readOnly && (
className={`popover-add-tool ${canAddFilter ? '' : 'disabled'}`} <CustomizeAddTool
callBack={canAddFilter ? () => this.addFilter(scheduleUpdate) : () => {}} className={`popover-add-tool ${canAddFilter ? '' : 'disabled'}`}
footerName={gettext('Add filter')} callBack={canAddFilter ? () => this.addFilter(scheduleUpdate) : () => {}}
addIconClassName="popover-add-icon" footerName={gettext('Add filter')}
/> addIconClassName="popover-add-icon"
{this.isNeedSubmit() && ( />
)}
{!readOnly && this.isNeedSubmit() && (
<div className='sf-metadata-filter-popover-footer'> <div className='sf-metadata-filter-popover-footer'>
<Button className='mr-2' onClick={this.onClosePopover}>{gettext('Cancel')}</Button> <Button className='mr-2' onClick={this.onClosePopover}>{gettext('Cancel')}</Button>
<Button color="primary" disabled={this.state.isSubmitDisabled} onClick={this.onSubmitFilters}>{gettext('Submit')}</Button> <Button color="primary" disabled={this.state.isSubmitDisabled} onClick={this.onSubmitFilters}>{gettext('Submit')}</Button>
@@ -197,7 +199,7 @@ FilterPopover.propTypes = {
filtersClassName: PropTypes.string, filtersClassName: PropTypes.string,
target: PropTypes.string.isRequired, target: PropTypes.string.isRequired,
isNeedSubmit: PropTypes.bool, isNeedSubmit: PropTypes.bool,
isLocked: PropTypes.bool, readOnly: PropTypes.bool,
columns: PropTypes.array.isRequired, columns: PropTypes.array.isRequired,
filterConjunction: PropTypes.string, filterConjunction: PropTypes.string,
filters: PropTypes.array, filters: PropTypes.array,

View File

@@ -3,12 +3,12 @@ import PropTypes from 'prop-types';
import { SfFilterCalendar } from '@seafile/sf-metadata-ui-component'; import { SfFilterCalendar } from '@seafile/sf-metadata-ui-component';
import { getDateColumnFormat } from '../../../../utils/column-utils'; import { getDateColumnFormat } from '../../../../utils/column-utils';
const FilterCalendar = ({ value, filterColumn, isReadOnly, onChange }) => { const FilterCalendar = ({ value, filterColumn, readOnly, onChange }) => {
const format = getDateColumnFormat(filterColumn).trim(); const format = getDateColumnFormat(filterColumn).trim();
const lang = window.sfMetadataContext.getSetting('lang'); const lang = window.sfMetadataContext.getSetting('lang');
return ( return (
<SfFilterCalendar <SfFilterCalendar
isReadOnly={isReadOnly} isReadOnly={readOnly}
format={format} format={format}
lang={lang} lang={lang}
value={value} value={value}
@@ -22,7 +22,7 @@ FilterCalendar.propTypes = {
value: PropTypes.string.isRequired, value: PropTypes.string.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,
filterColumn: PropTypes.object.isRequired, filterColumn: PropTypes.object.isRequired,
isReadOnly: PropTypes.bool, readOnly: PropTypes.bool,
}; };
export default FilterCalendar; export default FilterCalendar;

View File

@@ -6,7 +6,7 @@ import { gettext } from '../../../../../../utils';
import './index.css'; import './index.css';
const CollaboratorFilter = ({ isLocked, filterIndex, filterTerm, collaborators, placeholder, filter_predicate, onSelectCollaborator }) => { const CollaboratorFilter = ({ readOnly, filterIndex, filterTerm, collaborators, placeholder, filter_predicate, onSelectCollaborator }) => {
const supportMultipleSelectOptions = useMemo(() => { const supportMultipleSelectOptions = useMemo(() => {
return [ return [
FILTER_PREDICATE_TYPE.HAS_ANY_OF, FILTER_PREDICATE_TYPE.HAS_ANY_OF,
@@ -80,7 +80,7 @@ const CollaboratorFilter = ({ isLocked, filterIndex, filterTerm, collaborators,
onSelectOption={onSelectCollaborator} onSelectOption={onSelectCollaborator}
options={options} options={options}
placeholder={placeholder} placeholder={placeholder}
isLocked={isLocked} readOnly={readOnly}
supportMultipleSelect={isSupportMultipleSelect} supportMultipleSelect={isSupportMultipleSelect}
searchable={true} searchable={true}
searchPlaceholder={gettext('Search collaborator')} searchPlaceholder={gettext('Search collaborator')}
@@ -96,7 +96,7 @@ CollaboratorFilter.propTypes = {
filter_predicate: PropTypes.string, filter_predicate: PropTypes.string,
collaborators: PropTypes.array, collaborators: PropTypes.array,
onSelectCollaborator: PropTypes.func, onSelectCollaborator: PropTypes.func,
isLocked: PropTypes.bool, readOnly: PropTypes.bool,
placeholder: PropTypes.string, placeholder: PropTypes.string,
}; };

View File

@@ -25,6 +25,7 @@ import { DELETED_OPTION_BACKGROUND_COLOR, DELETED_OPTION_TIPS } from '../../../.
import './index.css'; import './index.css';
const propTypes = { const propTypes = {
readOnly: PropTypes.bool,
index: PropTypes.number.isRequired, index: PropTypes.number.isRequired,
filter: PropTypes.object.isRequired, filter: PropTypes.object.isRequired,
filterColumn: PropTypes.object.isRequired, filterColumn: PropTypes.object.isRequired,
@@ -245,6 +246,7 @@ class FilterItem extends React.Component {
}; };
getInputComponent = (type) => { getInputComponent = (type) => {
const { readOnly } = this.props;
const { filterTerm } = this.state; const { filterTerm } = this.state;
if (type === 'text') { if (type === 'text') {
return ( return (
@@ -252,13 +254,16 @@ class FilterItem extends React.Component {
value={filterTerm} value={filterTerm}
onChange={this.onFilterTermTextChanged} onChange={this.onFilterTermTextChanged}
autoFocus={false} autoFocus={false}
disabled={readOnly}
className='text-truncate' className='text-truncate'
/> />
); );
} else if (type === 'checkbox') { } else if (type === 'checkbox') {
const { readOnly } = this.props;
return ( return (
<input <input
type="checkbox" type="checkbox"
disabled={readOnly}
checked={filterTerm} checked={filterTerm}
onChange={this.onFilterTermCheckboxChanged} onChange={this.onFilterTermCheckboxChanged}
/> />
@@ -267,7 +272,7 @@ class FilterItem extends React.Component {
}; };
renderConjunction = () => { renderConjunction = () => {
const { index, filterConjunction, conjunctionOptions } = this.props; const { index, readOnly, filterConjunction, conjunctionOptions } = this.props;
switch (index) { switch (index) {
case 0: { case 0: {
return null; return null;
@@ -276,6 +281,7 @@ class FilterItem extends React.Component {
const activeConjunction = FilterItemUtils.getActiveConjunctionOption(filterConjunction); const activeConjunction = FilterItemUtils.getActiveConjunctionOption(filterConjunction);
return ( return (
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={activeConjunction} value={activeConjunction}
options={conjunctionOptions} options={conjunctionOptions}
onSelectOption={this.onSelectConjunction} onSelectOption={this.onSelectConjunction}
@@ -356,7 +362,7 @@ class FilterItem extends React.Component {
}; };
renderFilterTerm = (filterColumn) => { renderFilterTerm = (filterColumn) => {
const { index, filter, collaborators } = this.props; const { index, filter, collaborators, readOnly } = this.props;
const { type } = filterColumn; const { type } = filterColumn;
const { filter_term, filter_predicate, filter_term_modifier } = filter; const { filter_term, filter_predicate, filter_term_modifier } = filter;
// predicate is empty or not empty // predicate is empty or not empty
@@ -381,6 +387,7 @@ class FilterItem extends React.Component {
if (filter_term_modifier === 'exact_date') { if (filter_term_modifier === 'exact_date') {
return ( return (
<FilterCalendar <FilterCalendar
readOnly={readOnly}
onChange={this.onFilterExactDateChanged} onChange={this.onFilterExactDateChanged}
value={this.state.filterTerm} value={this.state.filterTerm}
filterColumn={filterColumn} filterColumn={filterColumn}
@@ -409,6 +416,7 @@ class FilterItem extends React.Component {
const creators = collaborators; const creators = collaborators;
return ( return (
<CollaboratorFilter <CollaboratorFilter
readOnly={readOnly}
filterIndex={index} filterIndex={index}
filterTerm={filter_term || []} filterTerm={filter_term || []}
collaborators={creators} collaborators={creators}
@@ -444,6 +452,7 @@ class FilterItem extends React.Component {
return ( return (
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
className="sf-metadata-selector-single-select" className="sf-metadata-selector-single-select"
value={selectedOptionDom} value={selectedOptionDom}
options={dataOptions || []} options={dataOptions || []}
@@ -461,6 +470,7 @@ class FilterItem extends React.Component {
const allCollaborators = this.getAllCollaborators(); const allCollaborators = this.getAllCollaborators();
return ( return (
<CollaboratorFilter <CollaboratorFilter
readOnly={readOnly}
filterIndex={index} filterIndex={index}
filterTerm={filter_term || []} filterTerm={filter_term || []}
filter_predicate={filter_predicate} filter_predicate={filter_predicate}
@@ -501,7 +511,7 @@ class FilterItem extends React.Component {
render() { render() {
const { filterPredicateOptions, filterTermModifierOptions } = this; const { filterPredicateOptions, filterTermModifierOptions } = this;
const { filter, filterColumn, filterColumnOptions } = this.props; const { filter, filterColumn, filterColumnOptions, readOnly } = this.props;
const { filter_predicate, filter_term_modifier } = filter; const { filter_predicate, filter_term_modifier } = filter;
const activeColumn = FilterItemUtils.generatorColumnOption(filterColumn); const activeColumn = FilterItemUtils.generatorColumnOption(filterColumn);
const activePredicate = FilterItemUtils.generatorPredicateOption(filter_predicate); const activePredicate = FilterItemUtils.generatorPredicateOption(filter_predicate);
@@ -521,9 +531,11 @@ class FilterItem extends React.Component {
return ( return (
<div className="filter-item"> <div className="filter-item">
<div className="delete-filter" onClick={this.onDeleteFilter}> {!readOnly && (
<Icon iconName="fork-number"/> <div className="delete-filter" onClick={this.onDeleteFilter}>
</div> <Icon iconName="fork-number"/>
</div>
)}
<div className="condition"> <div className="condition">
<div className="filter-conjunction"> <div className="filter-conjunction">
{this.renderConjunction()} {this.renderConjunction()}
@@ -531,6 +543,7 @@ class FilterItem extends React.Component {
<div className="filter-container"> <div className="filter-container">
<div className="filter-column"> <div className="filter-column">
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={activeColumn} value={activeColumn}
options={filterColumnOptions} options={filterColumnOptions}
onSelectOption={this.onSelectColumn} onSelectOption={this.onSelectColumn}
@@ -541,6 +554,7 @@ class FilterItem extends React.Component {
</div> </div>
<div className={`filter-predicate ml-2 ${_isCheckboxColumn ? 'filter-checkbox-predicate' : ''}`}> <div className={`filter-predicate ml-2 ${_isCheckboxColumn ? 'filter-checkbox-predicate' : ''}`}>
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={activePredicate} value={activePredicate}
options={filterPredicateOptions} options={filterPredicateOptions}
onSelectOption={this.onSelectPredicate} onSelectOption={this.onSelectPredicate}
@@ -549,6 +563,7 @@ class FilterItem extends React.Component {
{isDateColumn(filterColumn) && isNeedShowTermModifier && ( {isDateColumn(filterColumn) && isNeedShowTermModifier && (
<div className="filter-term-modifier ml-2"> <div className="filter-term-modifier ml-2">
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={activeTermModifier} value={activeTermModifier}
options={filterTermModifierOptions} options={filterTermModifierOptions}
onSelectOption={this.onSelectTermModifier} onSelectOption={this.onSelectTermModifier}

View File

@@ -67,6 +67,10 @@
line-height: 20px; line-height: 20px;
} }
.filters-list .sf-metadata-select.disabled .selected-option-show {
width: 100%;
}
.filters-list .sf-metadata-select .selected-option { .filters-list .sf-metadata-select .selected-option {
width: auto; width: auto;
overflow-x: auto; overflow-x: auto;
@@ -147,7 +151,7 @@
border-color: rgb(179, 179, 179); border-color: rgb(179, 179, 179);
} }
.filters-list .filter-term input.disabled:hover { .filters-list .filter-term input:disabled:hover {
border-color: rgba(0, 40, 100, 0.12); border-color: rgba(0, 40, 100, 0.12);
} }

View File

@@ -12,7 +12,7 @@ import FilterItem from './filter-item';
import './index.css'; import './index.css';
const propTypes = { const propTypes = {
isLocked: PropTypes.bool, readOnly: PropTypes.bool,
className: PropTypes.string, className: PropTypes.string,
filters: PropTypes.array, filters: PropTypes.array,
columns: PropTypes.array.isRequired, columns: PropTypes.array.isRequired,
@@ -81,13 +81,13 @@ class FiltersList extends Component {
}; };
renderFilterItem = (filter, index, errMsg, filterColumn) => { renderFilterItem = (filter, index, errMsg, filterColumn) => {
const { filterConjunction, value } = this.props; const { readOnly, filterConjunction, value } = this.props;
const conjunctionOptions = this.getConjunctionOptions(); const conjunctionOptions = this.getConjunctionOptions();
const columnOptions = this.getColumnOptions(); const columnOptions = this.getColumnOptions();
return ( return (
<FilterItem <FilterItem
key={index} key={index}
isLocked={this.props.isLocked} readOnly={readOnly}
index={index} index={index}
filter={filter} filter={filter}
errMsg={errMsg} errMsg={errMsg}

View File

@@ -60,7 +60,7 @@ const dropCollect = (connect, monitor) => ({
*/ */
const GroupbyItem = memo(({ const GroupbyItem = memo(({
isOver, isDragging, canDrop, connectDragSource, connectDragPreview, connectDropTarget, isOver, isDragging, canDrop, connectDragSource, connectDragPreview, connectDropTarget,
showDragBtn, index, groupby, columns, onDelete, onUpdate showDragBtn, index, readOnly, groupby, columns, onDelete, onUpdate
}) => { }) => {
const column = useMemo(() => { const column = useMemo(() => {
return getColumnByKey(columns, groupby.column_key); return getColumnByKey(columns, groupby.column_key);
@@ -162,12 +162,15 @@ const GroupbyItem = memo(({
{ 'group-can-drop': isOver && canDrop && !isDragging } { 'group-can-drop': isOver && canDrop && !isDragging }
)} )}
> >
<div className="delete-groupby" onClick={deleteGroupby} aria-label={gettext('Delete')}> {!readOnly && (
<Icon iconName="fork-number"/> <div className="delete-groupby" onClick={deleteGroupby} aria-label={gettext('Delete')}>
</div> <Icon iconName="fork-number"/>
</div>
)}
<div className="condition"> <div className="condition">
<div className="groupby-column"> <div className="groupby-column">
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={selectedColumn} value={selectedColumn}
options={columnsOptions} options={columnsOptions}
onSelectOption={selectColumn} onSelectOption={selectColumn}
@@ -179,6 +182,7 @@ const GroupbyItem = memo(({
{isShowGroupCountType(column) && ( {isShowGroupCountType(column) && (
<div className="groupby-count-type"> <div className="groupby-count-type">
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={selectedCountType} value={selectedCountType}
onSelectOption={selectCountType} onSelectOption={selectCountType}
options={countTypeOptions} options={countTypeOptions}
@@ -188,6 +192,7 @@ const GroupbyItem = memo(({
<div className="groupby-predicate"> <div className="groupby-predicate">
{(!column.key || SORT_COLUMN_OPTIONS.includes(column.type)) && ( {(!column.key || SORT_COLUMN_OPTIONS.includes(column.type)) && (
<CustomizeSelect <CustomizeSelect
readOnly={readOnly}
value={selectedSortType} value={selectedSortType}
options={sortOptions} options={sortOptions}
onSelectOption={selectSortType} onSelectOption={selectSortType}
@@ -195,7 +200,7 @@ const GroupbyItem = memo(({
)} )}
</div> </div>
</div> </div>
{showDragBtn && connectDragSource( {!readOnly && showDragBtn && connectDragSource(
<div className="groupby-drag"> <div className="groupby-drag">
<Icon iconName="drag" /> <Icon iconName="drag" />
</div> </div>
@@ -217,6 +222,7 @@ const GroupbyItem = memo(({
GroupbyItem.propTypes = { GroupbyItem.propTypes = {
index: PropTypes.number, index: PropTypes.number,
readOnly: PropTypes.bool,
groupby: PropTypes.object, groupby: PropTypes.object,
columns: PropTypes.array, columns: PropTypes.array,
onDelete: PropTypes.func, onDelete: PropTypes.func,

View File

@@ -6,15 +6,16 @@ import html5DragDropContext from '../../../../../../pages/wiki2/wiki-nav/html5Dr
import { gettext } from '../../../../utils'; import { gettext } from '../../../../utils';
import GroupbyItem from './groupby-item'; import GroupbyItem from './groupby-item';
const Groupbys = ({ groupbys, columns, onDelete, onUpdate, onMove }) => { const Groupbys = ({ readOnly, groupbys, columns, onDelete, onUpdate, onMove }) => {
const isEmpty = useMemo(() => { const isEmpty = useMemo(() => {
if (!Array.isArray(groupbys) || groupbys.length === 0) return true; if (!Array.isArray(groupbys) || groupbys.length === 0) return true;
return false; return false;
}, [groupbys]); }, [groupbys]);
const showDragBtn = useMemo(() => { const showDragBtn = useMemo(() => {
if (readOnly) return false;
if (!Array.isArray(groupbys) || groupbys.length === 0) return false; if (!Array.isArray(groupbys) || groupbys.length === 0) return false;
return groupbys.length > 1; return groupbys.length > 1;
}, [groupbys]); }, [readOnly, groupbys]);
return ( return (
<div className={classnames('groupbys-list', { 'empty-groupbys-container': isEmpty })}> <div className={classnames('groupbys-list', { 'empty-groupbys-container': isEmpty })}>
@@ -24,6 +25,7 @@ const Groupbys = ({ groupbys, columns, onDelete, onUpdate, onMove }) => {
<GroupbyItem <GroupbyItem
key={index} key={index}
index={index} index={index}
readOnly={readOnly}
showDragBtn={showDragBtn} showDragBtn={showDragBtn}
groupby={groupby} groupby={groupby}
columns={columns} columns={columns}

View File

@@ -13,7 +13,7 @@ import Groupbys from './groupbys';
import './index.css'; import './index.css';
const GroupbysPopover = ({ groupbys: propsGroupBys, hidePopover, onChange, target, placement, columns }) => { const GroupbysPopover = ({ groupbys: propsGroupBys, readOnly, hidePopover, onChange, target, placement, columns }) => {
const [groupbys, setGroupbys] = useState(propsGroupBys); const [groupbys, setGroupbys] = useState(propsGroupBys);
const isSelectOpenRef = useState(false); const isSelectOpenRef = useState(false);
const popoverRef = useRef(null); const popoverRef = useRef(null);
@@ -107,8 +107,8 @@ const GroupbysPopover = ({ groupbys: propsGroupBys, hidePopover, onChange, targe
boundariesElement={document.body} boundariesElement={document.body}
> >
<div ref={popoverRef} onClick={onPopoverInsideClick} className="sf-metadata-groupbys"> <div ref={popoverRef} onClick={onPopoverInsideClick} className="sf-metadata-groupbys">
<Groupbys groupbys={groupbys} columns={columns} onDelete={deleteGroup} onUpdate={updateGroup} onMove={moveGroupbys} /> <Groupbys readOnly={readOnly} groupbys={groupbys} columns={columns} onDelete={deleteGroup} onUpdate={updateGroup} onMove={moveGroupbys} />
{(groupbys.length < MAX_GROUP_LEVEL) && ( {!readOnly && (groupbys.length < MAX_GROUP_LEVEL) && (
<CustomizeAddTool <CustomizeAddTool
className="popover-add-tool" className="popover-add-tool"
callBack={addGroupby} callBack={addGroupby}
@@ -128,6 +128,7 @@ const GroupbysPopover = ({ groupbys: propsGroupBys, hidePopover, onChange, targe
}; };
GroupbysPopover.propTypes = { GroupbysPopover.propTypes = {
readOnly: PropTypes.bool,
target: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.node]), target: PropTypes.oneOfType([PropTypes.string, PropTypes.object, PropTypes.node]),
groupbys: PropTypes.array, groupbys: PropTypes.array,
columns: PropTypes.array, columns: PropTypes.array,

View File

@@ -2,16 +2,19 @@ import React, { useCallback } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Icon, Switch } from '@seafile/sf-metadata-ui-component'; import { Icon, Switch } from '@seafile/sf-metadata-ui-component';
import { COLUMNS_ICON_CONFIG } from '../../../../_basic'; import { COLUMNS_ICON_CONFIG } from '../../../../_basic';
import classNames from 'classnames';
const HideColumnItem = ({ column, isHidden, onChange }) => { const HideColumnItem = ({ readOnly, column, isHidden, onChange }) => {
const update = useCallback(() => { const update = useCallback(() => {
if (readOnly) return;
onChange(column.key); onChange(column.key);
}, [column, onChange]); }, [readOnly, column, onChange]);
return ( return (
<div className="hide-column-item"> <div className={classNames('hide-column-item', { 'disabled': readOnly })}>
<Switch <Switch
disabled={readOnly}
checked={isHidden} checked={isHidden}
placeholder={( placeholder={(
<> <>
@@ -27,6 +30,7 @@ const HideColumnItem = ({ column, isHidden, onChange }) => {
}; };
HideColumnItem.propTypes = { HideColumnItem.propTypes = {
readOnly: PropTypes.bool,
isHidden: PropTypes.bool, isHidden: PropTypes.bool,
column: PropTypes.object.isRequired, column: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired, onChange: PropTypes.func.isRequired,

View File

@@ -4,7 +4,7 @@ import classnames from 'classnames';
import { gettext } from '../../../../utils'; import { gettext } from '../../../../utils';
import HideColumn from './hide-column'; import HideColumn from './hide-column';
const HiddenColumns = ({ columns, hiddenColumns, onChange }) => { const HiddenColumns = ({ readOnly, columns, hiddenColumns, onChange }) => {
const isEmpty = useMemo(() => { const isEmpty = useMemo(() => {
if (!Array.isArray(columns) || columns.length === 0) return true; if (!Array.isArray(columns) || columns.length === 0) return true;
return false; return false;
@@ -17,6 +17,7 @@ const HiddenColumns = ({ columns, hiddenColumns, onChange }) => {
return ( return (
<HideColumn <HideColumn
key={column.key} key={column.key}
readOnly={readOnly}
isHidden={hiddenColumns.includes(column.key)} isHidden={hiddenColumns.includes(column.key)}
column={column} column={column}
onChange={onChange} onChange={onChange}
@@ -28,6 +29,7 @@ const HiddenColumns = ({ columns, hiddenColumns, onChange }) => {
}; };
HiddenColumns.propTypes = { HiddenColumns.propTypes = {
readOnly: PropTypes.bool,
hiddenColumns: PropTypes.array, hiddenColumns: PropTypes.array,
columns: PropTypes.array, columns: PropTypes.array,
onChange: PropTypes.func, onChange: PropTypes.func,

View File

@@ -57,10 +57,10 @@
.sf-metadata-hide-columns-popover .hide-columns-list .hide-column-item:hover { .sf-metadata-hide-columns-popover .hide-columns-list .hide-column-item:hover {
background: #f5f5f5; background: #f5f5f5;
cursor: pointer;
} }
.sf-metadata-hide-columns-popover .hide-columns-list .hide-column-item:hover * { .sf-metadata-hide-columns-popover .hide-columns-list .hide-column-item:not(.disabled):hover
.sf-metadata-hide-columns-popover .hide-columns-list .hide-column-item:not(.disabled):hover * {
cursor: pointer; cursor: pointer;
} }

View File

@@ -9,7 +9,7 @@ import { getEventClassName, gettext } from '../../../utils';
import './index.css'; import './index.css';
const HideColumnPopover = ({ hidePopover, onChange, target, placement, columns, hiddenColumns: oldHiddenColumns }) => { const HideColumnPopover = ({ hidePopover, onChange, readOnly, target, placement, columns, hiddenColumns: oldHiddenColumns }) => {
const [searchValue, setSearchValue] = useState(''); const [searchValue, setSearchValue] = useState('');
const [hiddenColumns, setHiddenColumns] = useState(oldHiddenColumns); const [hiddenColumns, setHiddenColumns] = useState(oldHiddenColumns);
const displayColumns = useMemo(() => { const displayColumns = useMemo(() => {
@@ -104,8 +104,8 @@ const HideColumnPopover = ({ hidePopover, onChange, target, placement, columns,
<div className="sf-metadata-hide-columns-search-container"> <div className="sf-metadata-hide-columns-search-container">
<SearchInput placeholder={gettext('Search column')} onKeyDown={onKeyDown} onChange={onChangeSearch} autoFocus={true}/> <SearchInput placeholder={gettext('Search column')} onKeyDown={onKeyDown} onChange={onChangeSearch} autoFocus={true}/>
</div> </div>
<HiddenColumns columns={displayColumns} hiddenColumns={hiddenColumns} onChange={hideColumn} /> <HiddenColumns readOnly={readOnly} columns={displayColumns} hiddenColumns={hiddenColumns} onChange={hideColumn} />
{!searchValue && ( {!readOnly && !searchValue && (
<div className="sf-metadata-hide-columns-operations"> <div className="sf-metadata-hide-columns-operations">
<div className="sf-metadata-hide-columns-operation px-2" onClick={hideAll} aria-label={gettext('Hide all')}>{gettext('Hide all')}</div> <div className="sf-metadata-hide-columns-operation px-2" onClick={hideAll} aria-label={gettext('Hide all')}>{gettext('Hide all')}</div>
<div className="sf-metadata-hide-columns-operation px-2" onClick={showAll} aria-label={gettext('Show all')}>{gettext('Show all')}</div> <div className="sf-metadata-hide-columns-operation px-2" onClick={showAll} aria-label={gettext('Show all')}>{gettext('Show all')}</div>
@@ -118,6 +118,7 @@ const HideColumnPopover = ({ hidePopover, onChange, target, placement, columns,
}; };
HideColumnPopover.propTypes = { HideColumnPopover.propTypes = {
readOnly: PropTypes.bool,
placement: PropTypes.string.isRequired, placement: PropTypes.string.isRequired,
target: PropTypes.string.isRequired, target: PropTypes.string.isRequired,
hiddenColumns: PropTypes.array.isRequired, hiddenColumns: PropTypes.array.isRequired,

View File

@@ -18,19 +18,19 @@ import './index.css';
const SORT_TYPES = [SORT_TYPE.UP, SORT_TYPE.DOWN]; const SORT_TYPES = [SORT_TYPE.UP, SORT_TYPE.DOWN];
const propTypes = { const propTypes = {
readOnly: PropTypes.bool,
target: PropTypes.string.isRequired, target: PropTypes.string.isRequired,
isNeedSubmit: PropTypes.bool, isNeedSubmit: PropTypes.bool,
sorts: PropTypes.array, sorts: PropTypes.array,
columns: PropTypes.array.isRequired, columns: PropTypes.array.isRequired,
onSortComponentToggle: PropTypes.func, onSortComponentToggle: PropTypes.func,
update: PropTypes.func, update: PropTypes.func,
readonly: PropTypes.bool,
}; };
class SortPopover extends Component { class SortPopover extends Component {
static defaultProps = { static defaultProps = {
readonly: false, readOnly: false,
}; };
constructor(props) { constructor(props) {
@@ -183,7 +183,7 @@ class SortPopover extends Component {
renderSortItem = (column, sort, index) => { renderSortItem = (column, sort, index) => {
let { name, type } = column; let { name, type } = column;
const { readonly } = this.props; const { readOnly } = this.props;
let selectedColumn = { let selectedColumn = {
label: ( label: (
<Fragment> <Fragment>
@@ -200,7 +200,7 @@ class SortPopover extends Component {
return ( return (
<div key={'sort-item-' + index} className="sort-item"> <div key={'sort-item-' + index} className="sort-item">
{!readonly && {!readOnly &&
<div className="delete-sort" onClick={(event) => this.deleteSort(event, index)}> <div className="delete-sort" onClick={(event) => this.deleteSort(event, index)}>
<Icon iconName="fork-number"/> <Icon iconName="fork-number"/>
</div> </div>
@@ -208,7 +208,7 @@ class SortPopover extends Component {
<div className="condition"> <div className="condition">
<div className="sort-column"> <div className="sort-column">
<CustomizeSelect <CustomizeSelect
isLocked={readonly} readOnly={readOnly}
value={selectedColumn} value={selectedColumn}
onSelectOption={(value) => this.onSelectColumn(value, index)} onSelectOption={(value) => this.onSelectColumn(value, index)}
options={this.columnsOptions} options={this.columnsOptions}
@@ -219,7 +219,7 @@ class SortPopover extends Component {
</div> </div>
<div className="sort-predicate ml-2"> <div className="sort-predicate ml-2">
<CustomizeSelect <CustomizeSelect
isLocked={readonly} readOnly={readOnly}
value={selectedSortType} value={selectedSortType}
onSelectOption={(value) => this.onSelectSortType(value, index)} onSelectOption={(value) => this.onSelectSortType(value, index)}
options={this.sortTypeOptions} options={this.sortTypeOptions}
@@ -235,7 +235,7 @@ class SortPopover extends Component {
}; };
render() { render() {
const { target, readonly } = this.props; const { target, readOnly } = this.props;
const { sorts } = this.state; const { sorts } = this.state;
const isEmpty = isSortsEmpty(sorts); const isEmpty = isSortsEmpty(sorts);
return ( return (
@@ -255,7 +255,7 @@ class SortPopover extends Component {
this.renderSortsList() this.renderSortsList()
} }
</div> </div>
{!readonly && {!readOnly &&
<CustomizeAddTool <CustomizeAddTool
callBack={this.addSort} callBack={this.addSort}
footerName={gettext('Add sort')} footerName={gettext('Add sort')}
@@ -263,7 +263,7 @@ class SortPopover extends Component {
addIconClassName="popover-add-icon" addIconClassName="popover-add-icon"
/> />
} }
{(this.isNeedSubmit() && !readonly) && ( {(this.isNeedSubmit() && !readOnly) && (
<div className='sf-metadata-sort-popover-footer'> <div className='sf-metadata-sort-popover-footer'>
<Button className='mr-2' onClick={this.onClosePopover}>{gettext('Cancel')}</Button> <Button className='mr-2' onClick={this.onClosePopover}>{gettext('Cancel')}</Button>
<Button color="primary" disabled={this.state.isSubmitDisabled} onClick={this.onSubmitSorts}>{gettext('Submit')}</Button> <Button color="primary" disabled={this.state.isSubmitDisabled} onClick={this.onSubmitSorts}>{gettext('Submit')}</Button>

View File

@@ -63,6 +63,8 @@ const ViewToolBar = ({ metadataViewId }) => {
if (!view) return null; if (!view) return null;
const readOnly = !window.sfMetadataContext.canModifyView(view);
return ( return (
<div <div
className="sf-metadata-tool" className="sf-metadata-tool"
@@ -73,6 +75,7 @@ const ViewToolBar = ({ metadataViewId }) => {
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-filter" wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-filter"
filtersClassName="sf-metadata-filters" filtersClassName="sf-metadata-filters"
target="sf-metadata-filter-popover" target="sf-metadata-filter-popover"
readOnly={readOnly}
filterConjunction={view.filter_conjunction} filterConjunction={view.filter_conjunction}
filters={view.filters} filters={view.filters}
columns={availableColumns} columns={availableColumns}
@@ -82,6 +85,7 @@ const ViewToolBar = ({ metadataViewId }) => {
<SortSetter <SortSetter
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-sort" wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-sort"
target="sf-metadata-sort-popover" target="sf-metadata-sort-popover"
readOnly={readOnly}
sorts={view.sorts} sorts={view.sorts}
columns={viewColumns} columns={viewColumns}
modifySorts={modifySorts} modifySorts={modifySorts}
@@ -89,6 +93,7 @@ const ViewToolBar = ({ metadataViewId }) => {
<GroupbySetter <GroupbySetter
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-groupby" wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-groupby"
target="sf-metadata-groupby-popover" target="sf-metadata-groupby-popover"
readOnly={readOnly}
columns={viewColumns} columns={viewColumns}
groupbys={view.groupbys} groupbys={view.groupbys}
modifyGroupbys={modifyGroupbys} modifyGroupbys={modifyGroupbys}
@@ -96,6 +101,7 @@ const ViewToolBar = ({ metadataViewId }) => {
<HideColumnSetter <HideColumnSetter
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-hide-column" wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-hide-column"
target="sf-metadata-hide-column-popover" target="sf-metadata-hide-column-popover"
readOnly={readOnly}
columns={viewColumns.slice(1)} columns={viewColumns.slice(1)}
hiddenColumns={view.hidden_columns || []} hiddenColumns={view.hidden_columns || []}
modifyHiddenColumns={modifyHiddenColumns} modifyHiddenColumns={modifyHiddenColumns}

View File

@@ -130,6 +130,11 @@ class Context {
return true; return true;
}; };
canModifyView = (view) => {
if (this.permission === 'r') return false;
return true;
};
getCollaboratorFromCache(email) { getCollaboratorFromCache(email) {
return this.collaboratorsCache[email]; return this.collaboratorsCache[email];
} }