diff --git a/Dockerfile b/Dockerfile
index 6fafe4a9e..6041bc988 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -18,10 +18,10 @@ ARG BUILD_ID=unknown
ENV BUILD_ID=${BUILD_ID}
RUN echo "Build with BUILD_ID: $BUILD_ID"
-RUN npm run lint
-RUN npm run test:ci
-RUN npm run build
-RUN npm prune --production
+RUN npm run lint && \
+ npm run test:ci && \
+ npm run build && \
+ npm prune --production
FROM node:20.10.0-alpine
diff --git a/Jenkinsfile b/Jenkinsfile
index 1f9e095ac..eb7cdf353 100644
--- a/Jenkinsfile
+++ b/Jenkinsfile
@@ -4,11 +4,13 @@ final String BRANCH = params.BRANCH
final boolean NOTIFY_SLACK_ON_FAILURE = params.NOTIFY_SLACK_ON_FAILURE
final boolean NOTIFY_SLACK_ON_SUCCESS = params.NOTIFY_SLACK_ON_SUCCESS
-final String BRANCH_NAME = BRANCH ? BRANCH.tokenize('/').last() : ""
+final String CLEAN_BRANCH_NAME = BRANCH ? BRANCH.replaceAll("[\\.\\_\\#]", "-").tokenize('/').last() : ""
+
final String DEPLOY_DIR = 'build/deploy'
final String TEST_COVERAGE_FILE = 'output/coverage/junit.xml'
final String IMAGE_NAME = "campudus/grud-frontend"
+final String IMAGE_TAG = CLEAN_BRANCH_NAME && CLEAN_BRANCH_NAME != "master" ? CLEAN_BRANCH_NAME : "latest"
final String ARCHIVE_FILENAME_DIST = "grud-frontend-dist.tar.gz"
final GString DOCKER_BASE_IMAGE_TAG = "build-${BUILD_NUMBER}"
@@ -85,8 +87,7 @@ pipeline {
--label "GIT_COMMIT_DATE=${GIT_COMMIT_DATE}" \
--label "BUILD_DATE=${BUILD_DATE}" \
-t ${IMAGE_NAME}:${DOCKER_BASE_IMAGE_TAG}-${GIT_HASH} \
- ${BRANCH_NAME ? "-t ${IMAGE_NAME}:${BRANCH_NAME}" : ""} \
- -t ${IMAGE_NAME}:latest \
+ -t ${IMAGE_NAME}:${IMAGE_TAG} \
-f Dockerfile --rm .
"""
}
@@ -102,16 +103,7 @@ pipeline {
steps {
withDockerRegistry([credentialsId: "dockerhub", url: ""]) {
sh "docker push ${IMAGE_NAME}:${DOCKER_BASE_IMAGE_TAG}-${GIT_HASH}"
-
- script {
- if (BRANCH_NAME) {
- sh "docker push ${IMAGE_NAME}:${BRANCH_NAME}"
- }
-
- if (!BRANCH_NAME || BRANCH_NAME == 'master') {
- sh "docker push ${IMAGE_NAME}:latest"
- }
- }
+ sh "docker push ${IMAGE_NAME}:${IMAGE_TAG}"
}
}
}
@@ -122,7 +114,12 @@ pipeline {
wrap([$class: 'BuildUser']) {
script {
if (NOTIFY_SLACK_ON_SUCCESS) {
- slackOk(channel: SLACK_CHANNEL, message: BRANCH ? "BRANCH=${BRANCH}" : "")
+ final String logParams = [
+ BRANCH ? "BRANCH=${BRANCH}" : null,
+ "image: ${IMAGE_NAME}:${IMAGE_TAG}",
+ ].minus(null).join(' ')
+
+ slackOk(channel: SLACK_CHANNEL, message: "${logParams}")
}
}
}
@@ -132,7 +129,11 @@ pipeline {
wrap([$class: 'BuildUser']) {
script {
if (NOTIFY_SLACK_ON_FAILURE) {
- slackError(channel: SLACK_CHANNEL, message: BRANCH ? "BRANCH=${BRANCH}" : "")
+ final String logParams = [
+ BRANCH ? "BRANCH=${BRANCH}" : null,
+ ].minus(null).join(' ')
+
+ slackError(channel: SLACK_CHANNEL, message: "${logParams}")
}
}
}
diff --git a/setupTests.js b/setupTests.js
index b4ca069d2..0b244d8c7 100644
--- a/setupTests.js
+++ b/setupTests.js
@@ -1,3 +1,8 @@
// babel polyfill: https://babeljs.io/docs/en/babel-polyfill
import "core-js/stable";
import "regenerator-runtime/runtime";
+
+// https://github.com/kutlugsahin/react-smooth-dnd/issues/48
+Object.defineProperty(global, "Node", {
+ value: { firstElementChild: "firstElementChild" }
+});
diff --git a/src/app/archivedRows/ToggleArchivedRowsButton.jsx b/src/app/archivedRows/ToggleArchivedRowsButton.jsx
index 873da7e44..902dbb73b 100644
--- a/src/app/archivedRows/ToggleArchivedRowsButton.jsx
+++ b/src/app/archivedRows/ToggleArchivedRowsButton.jsx
@@ -1,19 +1,29 @@
-import React, { useCallback } from "react";
+import React, { useCallback, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import Action from "../redux/actionCreators";
import { selectShowArchivedState } from "../redux/reducers/tableView";
const ToggleArchivedRowsButton = ({ table }) => {
+ // prevent potential massive reload of archived rows
+ const [mustFetchArchivedRows, setMustFetchArchivedRows] = useState(true);
+
const showArchived = useSelector(selectShowArchivedState);
const dispatch = useDispatch();
- const className = `filter-wrapper ${showArchived ? "active" : ""}`;
+ const className = `filter-wrapper ${showArchived ? "has-filter" : ""}`;
const toggleArchivedRows = useCallback(() => {
+ if (mustFetchArchivedRows) {
+ dispatch(Action.loadAllRows(table.id, true));
+ setMustFetchArchivedRows(false);
+ }
dispatch(Action.setShowArchivedRows(table, !showArchived));
- }, [showArchived]);
+ }, [showArchived, mustFetchArchivedRows]);
return (
-
diff --git a/src/app/archivedRows/helpers.js b/src/app/archivedRows/helpers.js
index 3c67bfe24..29e7b867b 100644
--- a/src/app/archivedRows/helpers.js
+++ b/src/app/archivedRows/helpers.js
@@ -1,12 +1,3 @@
-// eslint-dsable-next-line
-import { flip, memoize } from "lodash";
+export const isRowArchived = row => Boolean(row.archived);
-// eslint-disable-next-line
-const memoizeWith = flip(memoize);
-
-export const isRowArchived = memoizeWith(
- row => row.id,
- () => Math.random() < 0.5
-); // TODO: implement once we decided on the data type
-
-export const isLinkArchived = _ => Math.random() < 0.5; // TODO: implement once we decided on the data type
+export const isLinkArchived = link => Boolean(link.archived);
diff --git a/src/app/components/cells/Cell.jsx b/src/app/components/cells/Cell.jsx
index 1842b5d75..fc7e941da 100644
--- a/src/app/components/cells/Cell.jsx
+++ b/src/app/components/cells/Cell.jsx
@@ -129,7 +129,8 @@ class Cell extends React.Component {
!f.isEqual(
getRelevantCellProps(this.props.cell),
getRelevantCellProps(nextProps.cell)
- )
+ ) ||
+ cell.row.archived !== nextCell.row.archived
);
};
diff --git a/src/app/components/cells/link/LinkOverlay.jsx b/src/app/components/cells/link/LinkOverlay.jsx
index f0facad22..5752b07e2 100644
--- a/src/app/components/cells/link/LinkOverlay.jsx
+++ b/src/app/components/cells/link/LinkOverlay.jsx
@@ -30,6 +30,7 @@ import withCachedLinks from "./LinkOverlayCache.jsx";
import { isTaxonomyTable } from "../../taxonomy/taxonomy";
import store from "../../../redux/store";
import { buildClassName } from "../../../helpers/buildClassName";
+import { isRowArchived } from "../../../archivedRows/helpers";
const MAIN_BUTTON = 0;
const LINK_BUTTON = 1;
const LINKED_ITEMS = 0;
@@ -247,13 +248,14 @@ class LinkOverlay extends PureComponent {
}
};
- return (
+ return row && cell ? (
+ ) : (
+ undefined
);
};
diff --git a/src/app/components/cells/link/LinkOverlayCache.jsx b/src/app/components/cells/link/LinkOverlayCache.jsx
index 54448ee59..6e66a2004 100644
--- a/src/app/components/cells/link/LinkOverlayCache.jsx
+++ b/src/app/components/cells/link/LinkOverlayCache.jsx
@@ -49,16 +49,21 @@ const withCachedLinks = Component => props => {
.then(
// row response -> link label format
f.compose(
- f.map(({ id, values }) => ({ id, value: f.first(values) })),
+ f.map(({ values, ...foreignRow }) => ({
+ ...foreignRow,
+ value: f.first(values)
+ })),
f.prop("rows")
)
)
.then(foreignRows => {
// update display values in state
- const loadedDisplayValues = foreignRows.map(({ id, value }) => ({
- id,
- values: [getDisplayValue(column.toColumn, value)]
- }));
+ const loadedDisplayValues = foreignRows.map(
+ ({ value, ...foreignRow }) => ({
+ ...foreignRow,
+ values: [getDisplayValue(column.toColumn, value)]
+ })
+ );
actions.addDisplayValues({
displayValues: [
{ tableId: column.toTable, values: loadedDisplayValues }
diff --git a/src/app/components/cells/text/TextEditOverlay.jsx b/src/app/components/cells/text/TextEditOverlay.jsx
index 732809a88..aca6058e6 100644
--- a/src/app/components/cells/text/TextEditOverlay.jsx
+++ b/src/app/components/cells/text/TextEditOverlay.jsx
@@ -1,22 +1,34 @@
-import React from "react";
-import { compose, lifecycle, withStateHandlers } from "recompose";
import i18n from "i18next";
+import React, { useState } from "react";
import {
columnHasMaxLength,
columnHasMinLength,
- isTextTooShort,
+ getTextLength,
isTextTooLong,
- getTextLength
+ isTextTooShort
} from "../../../helpers/limitTextLength";
const TextEditOverlay = props => {
const {
- editedValue,
- setValue,
- saveEdits,
- readOnly,
- cell: { column }
+ actions,
+ cell,
+ cell: { column, value },
+ langtag,
+ readOnly
} = props;
+
+ const [editedValue, setEditedValue] = useState(
+ (column.multilanguage ? value[langtag] : value) ?? ""
+ );
+ const saveEdits = () => {
+ const newValue = column.multilanguage
+ ? { ...value, [langtag]: editedValue }
+ : editedValue;
+
+ console.log("saveEdits", cell, value, newValue);
+ actions.changeCellValue({ cell, oldValue: value, newValue });
+ };
+
const { minLength, maxLength } = column;
const [clickedOutside, setClickedOutside] = React.useState(false);
const minLengthText = columnHasMinLength(column)
@@ -40,7 +52,7 @@ const TextEditOverlay = props => {
if (isTextTooLong(column, value)) {
return;
}
- setValue(evt);
+ setEditedValue(value);
};
const onBlur = () => {
if (shouldCatchOutsideClick) {
@@ -75,38 +87,4 @@ const TextEditOverlay = props => {
);
};
-const enhance = compose(
- withStateHandlers(
- ({ cell, value, langtag }) => ({
- editedValue: cell.column.multilanguage ? value[langtag] : value
- }),
- {
- setValue: () => event => ({ editedValue: event.target.value }),
- saveEdits: (state, props) => () => {
- const { editedValue } = state;
- const { langtag, cell, value, actions } = props;
- const { column, row, table } = cell;
-
- const newValue = column.multilanguage
- ? { [langtag]: editedValue }
- : editedValue;
-
- actions.changeCellValue({
- oldValue: value,
- newValue,
- tableId: table.id,
- columnId: column.id,
- rowId: row.id,
- cell
- });
- }
- }
- ),
- lifecycle({
- componentWillUnmount() {
- this.props.saveEdits();
- }
- })
-);
-
-export default enhance(TextEditOverlay);
+export default TextEditOverlay;
diff --git a/src/app/components/contextMenu/RowContextMenu.jsx b/src/app/components/contextMenu/RowContextMenu.jsx
index 7e8ab914c..76c1cefc1 100644
--- a/src/app/components/contextMenu/RowContextMenu.jsx
+++ b/src/app/components/contextMenu/RowContextMenu.jsx
@@ -23,6 +23,7 @@ import {
getAnnotation,
removeTranslationNeeded,
setCellAnnotation,
+ setRowArchived,
setRowFinal
} from "../../helpers/annotationHelper";
import { canConvert } from "../../helpers/cellValueConverter";
@@ -284,6 +285,21 @@ class RowContextMenu extends React.Component {
setRowFinal({ table, row, value: valueToSet });
};
+ setArchived = archived => () => {
+ const {
+ langtag,
+ cell: { row, table }
+ } = this.props;
+ setRowArchived({ table, row, archived });
+ if (archived) {
+ this.props.actions.toggleCellSelection({
+ select: false,
+ langtag,
+ tableId: table.id
+ });
+ }
+ };
+
setFinalItem = () => {
if (!canUserEditRowAnnotations(this.props.cell)) {
return null;
@@ -298,6 +314,23 @@ class RowContextMenu extends React.Component {
return this.mkItem(this.setFinal(!final), label, "lock");
};
+ setArchivedItem = () => {
+ if (!canUserEditRowAnnotations(this.props.cell)) {
+ return null;
+ } else {
+ const {
+ t,
+ cell: {
+ row: { archived }
+ }
+ } = this.props;
+ const label = t(
+ archived ? "archived.unset-archived" : "archived.set-archived"
+ );
+ return this.mkItem(this.setArchived(!archived), label, "archive");
+ }
+ };
+
openLinksFilteredItem = () => {
const { cell, langtag } = this.props;
if (cell.kind !== ColumnKinds.link || f.isEmpty(cell.value)) {
@@ -414,6 +447,7 @@ class RowContextMenu extends React.Component {
{this.mkItem(showDependency, "show_dependency", "code-fork")}
{this.mkItem(showTranslations, "show_translation", "flag")}
{this.setFinalItem()}
+ {this.setArchivedItem()}
{isDeletingRowAllowed || isDuplicatingRowAllowed ? (
) : null}
diff --git a/src/app/components/history/diffItems/FlagDiff.jsx b/src/app/components/history/diffItems/FlagDiff.jsx
index 976eb6145..43e17ccf5 100644
--- a/src/app/components/history/diffItems/FlagDiff.jsx
+++ b/src/app/components/history/diffItems/FlagDiff.jsx
@@ -1,7 +1,6 @@
-import React from "react";
-import f from "lodash/fp";
import i18n from "i18next";
-
+import f from "lodash/fp";
+import React from "react";
import { ifElse, when } from "../../../helpers/functools";
const FlagDiff = props => {
@@ -21,6 +20,9 @@ const FlagDiff = props => {
revision.value || revision.valueType
);
+ const translationKey =
+ value === "archived" ? "table:archived:is-archived" : `table:${value}`;
+
return (
{
event
}
>
- {i18n.t(`table:${value}`)}
+ {i18n.t(translationKey)}
);
};
diff --git a/src/app/components/overlay/EntityView/EntityViewBody.jsx b/src/app/components/overlay/EntityView/EntityViewBody.jsx
index be5a5c9e5..b54e7b982 100644
--- a/src/app/components/overlay/EntityView/EntityViewBody.jsx
+++ b/src/app/components/overlay/EntityView/EntityViewBody.jsx
@@ -21,6 +21,7 @@ import TranslationPopup from "../../entityView/TranslationPopup";
import View from "../../entityView/RowView";
import columnFilter from "./columnFilter";
import getDisplayValue from "../../../helpers/getDisplayValue";
+import { isRowArchived } from "../../../archivedRows/helpers";
const CLOSE_POPUP_DELAY = 200; // milliseconds
const SHAKE_DURATION = 800;
@@ -293,20 +294,29 @@ class EntityViewBody extends Component {
};
renderUnlockBar = row => {
- if (!isLocked(row)) {
- return null;
- }
const buttonClass = classNames("button", { shake: this.state.shaking });
+ const rowIsArchived = isRowArchived(row);
+ const rowIsLocked = isLocked(row);
+ const unlock = this.unlockRowTemporary;
+ const barTitle = rowIsArchived
+ ? "table:archived.is-archived"
+ : "table:row-is-locked";
+ const buttonAction = "table:unlock-row";
+
return (
-
-
-
-
{i18n.t("table:row-is-locked")}
+ (rowIsArchived || rowIsLocked) && (
+
+
+
+ {i18n.t(barTitle)}
+
+ {!rowIsArchived && rowIsLocked && (
+
+ {i18n.t(buttonAction)}
+
+ )}
-
- {i18n.t("table:unlock-row")}
-
-
+ )
);
};
diff --git a/src/app/components/table/RowFilters.js b/src/app/components/table/RowFilters.js
index 6d3bbbd2a..9b7df58d7 100644
--- a/src/app/components/table/RowFilters.js
+++ b/src/app/components/table/RowFilters.js
@@ -487,22 +487,25 @@ const completeRowInformation = (columns, table, rows, allDisplayValues) => {
})
);
- return rows.map(({ id, cells, values, annotations, final }, rowIndex) => ({
- rowIndex,
- id,
- annotations,
- final,
- values: cells.map((cell, colIndex) => ({
- ...cell,
- displayValue:
- cell.kind === ColumnKinds.link
- ? values[colIndex].map(link =>
- getLinkDisplayValue(columns[colIndex].toTable)(link.id)
- )
- : f.get([rowIndex, "values", colIndex], tableDisplayValues),
- value: values[colIndex]
- }))
- }));
+ return rows.map(
+ ({ id, cells, values, annotations, final, archived }, rowIndex) => ({
+ rowIndex,
+ id,
+ annotations,
+ final,
+ archived,
+ values: cells.map((cell, colIndex) => ({
+ ...cell,
+ displayValue:
+ cell.kind === ColumnKinds.link
+ ? values[colIndex].map(link =>
+ getLinkDisplayValue(columns[colIndex].toTable)(link.id)
+ )
+ : f.get([rowIndex, "values", colIndex], tableDisplayValues),
+ value: values[colIndex]
+ }))
+ })
+ );
};
export default getFilteredRows;
diff --git a/src/app/components/tableView/TableView.jsx b/src/app/components/tableView/TableView.jsx
index c7689836e..a9c48b85e 100644
--- a/src/app/components/tableView/TableView.jsx
+++ b/src/app/components/tableView/TableView.jsx
@@ -312,9 +312,7 @@ class TableView extends PureComponent {
columnActions={columnActions}
columnOrdering={columnOrdering}
/>
- ) : (
-
- )}
+ ) : null}
", message);
-// Sentry.captureException(error);
-// showDialog({
-// type: "warning",
-// context: i18n.t("common:error"),
-// title: i18n.t("table:error_occured_hl"),
-// heading,
-// message,
-// actions: { neutral: [i18n.t("common:ok"), null] }
-// });
-// }
-
const getAnnotation = (annotation, cell) => {
const cellAnnotations = cell.annotations;
const getFlag = ann => f.prop([ann.value], cellAnnotations);
@@ -102,6 +88,10 @@ const setRowFinal = ({ table, row, value = true }) => {
setRowAnnotation({ table, row, flagName: "final", flagValue: value });
};
+const setRowArchived = ({ table, row, archived = true }) => {
+ setRowAnnotation({ table, row, flagName: "archived", flagValue: archived });
+};
+
const setRowAnnotation = ({ table, row, flagName, flagValue }) => {
store.dispatch(setRowFlag({ table, row, flagName, flagValue }));
};
@@ -156,6 +146,7 @@ export {
getAnnotation,
extractAnnotations,
refreshAnnotations,
+ setRowArchived,
setRowAnnotation,
setCellAnnotation,
setRowFinal,
diff --git a/src/app/helpers/apiHelper.js b/src/app/helpers/apiHelper.js
index 1201c1792..305773f31 100644
--- a/src/app/helpers/apiHelper.js
+++ b/src/app/helpers/apiHelper.js
@@ -2,27 +2,13 @@ import f from "lodash/fp";
import fetch from "cross-fetch";
import request from "superagent";
-import { doto } from "./functools.js";
import { getLogin, noAuthNeeded } from "./authenticate";
import apiUrl from "./apiUrl";
const buildURL = apiRoute => apiUrl(apiRoute);
const paramsToString = params =>
- f.isEmpty(params)
- ? ""
- : doto(
- params,
- f.toPairs,
- f.map(([param, value]) =>
- f.isArray(value)
- ? value.map(v => `${param}=${v}`).join("&")
- : `${param}=${value}`
- ),
- f.join("&"),
- f.concat("?"),
- f.join("")
- );
+ f.isEmpty(params) ? "" : `?${new URLSearchParams(params).toString()}`;
/**
* Make a promisified cross-browser-compatible XHR-request, using
diff --git a/src/app/redux/actionCreators.jsx b/src/app/redux/actionCreators.jsx
index 874b496b9..f1298af79 100644
--- a/src/app/redux/actionCreators.jsx
+++ b/src/app/redux/actionCreators.jsx
@@ -232,8 +232,8 @@ const checkIfSelectedCellExists = (dispatch, tableId, state) => {
}
};
-const loadAllRows = tableId => async (dispatch, getState) => {
- await Row.loadAllRows(tableId)(dispatch);
+const loadAllRows = (tableId, ...params) => async (dispatch, getState) => {
+ await Row.loadAllRows(tableId, ...params)(dispatch);
checkIfSelectedCellExists(dispatch, tableId, getState());
};
diff --git a/src/app/redux/actions/cellActions.js b/src/app/redux/actions/cellActions.js
index 4b28a5273..a3824e039 100644
--- a/src/app/redux/actions/cellActions.js
+++ b/src/app/redux/actions/cellActions.js
@@ -222,7 +222,7 @@ const dispatchCellValueChange = action => (dispatch, getState) => {
const isMultiLanguage =
column.multilanguage && (f.isPlainObject(newValue) || f.isNil(newValue));
- const update = calculateCellUpdate(action);
+ const update = calculateCellUpdate({ ...cell, ...action });
if (f.isNil(update)) {
return Promise.resolve();
}
diff --git a/src/app/redux/actions/rowActions.js b/src/app/redux/actions/rowActions.js
index 41dad6468..abfeee5e1 100644
--- a/src/app/redux/actions/rowActions.js
+++ b/src/app/redux/actions/rowActions.js
@@ -93,18 +93,24 @@ export const safelyDuplicateRow = ({
}
};
-export const loadAllRows = tableId => async dispatch => {
+export const loadAllRows = (tableId, archived = false) => async dispatch => {
const PARALLELL_CHUNKS = 4;
const ROWS_PER_CHUNK = 500;
const INITIAL_ROWS = 30;
+ const preloadParam = {
+ offset: 0,
+ limit: INITIAL_ROWS,
+ archived
+ };
+
const buildParams = (allRows, rowsPerRequest) => {
if (allRows <= rowsPerRequest) {
- return [{ offset: INITIAL_ROWS, limit: allRows }];
+ return [{ ...preloadParam, offset: INITIAL_ROWS, limit: allRows }];
}
return f.compose(
f.map(offset => {
- return { offset, limit: rowsPerRequest };
+ return { ...preloadParam, offset, limit: rowsPerRequest };
}),
f.rangeStep(rowsPerRequest, INITIAL_ROWS)
)(allRows % rowsPerRequest !== 0 ? allRows + 1 : allRows);
@@ -120,11 +126,6 @@ export const loadAllRows = tableId => async dispatch => {
return paginatedRows;
};
- const preloadParam = {
- offset: 0,
- limit: INITIAL_ROWS
- };
-
const {
page: { totalSize }
} = await loadPaginatedRows(preloadParam);
diff --git a/src/app/redux/reducers/rows.js b/src/app/redux/reducers/rows.js
index c2233559c..70956e928 100644
--- a/src/app/redux/reducers/rows.js
+++ b/src/app/redux/reducers/rows.js
@@ -98,6 +98,7 @@ export const rowValuesToCells = (table, columns) => rows => {
return {
id: row.id,
final: row.final,
+ archived: row.archived,
annotations: row.annotations,
values: row.values,
cells: row.values.map((cellValue, idx) =>
@@ -168,25 +169,39 @@ const deleteRow = (action, completeState) => {
const addRows = (completeState, state, action) => {
const columns = f.prop(["columns", action.tableId, "data"], completeState);
const table = f.prop(["tables", "data", action.tableId], completeState);
+ const newRows = rowValuesToCells(table, columns)(action.rows);
const temp = f.update(
[action.tableId, "data"],
- arr => insert(arr, rowValuesToCells(table, columns)(action.rows)),
+ mergeRows(newRows || []),
state
);
return temp;
};
-const insert = (prev, rows) => {
- const firstElement = f.first(rows);
- const index = f.sortedIndexBy(f.get("id"), firstElement, prev);
- if (index === 0) {
- return rows;
- } else {
- return f.concat(
- f.concat(f.slice(0, index, prev), rows),
- f.slice(index, prev.length, prev)
- );
+// JavaScript would be very slow and/or run out of stack if we'd implement that
+// cleanly.
+const mergeRows = a => b => {
+ const rowsA = f.sortBy("id", a);
+ const rowsB = f.sortBy("id", b);
+ const rows = [];
+
+ while (rowsA.length > 0 || rowsB.length > 0) {
+ const headA = rowsA[0];
+ const headB = rowsB[0];
+ switch (true) {
+ case headA && !headB:
+ return f.uniqBy("id", rows.concat(rowsA)); // recursion base
+ case headB && !headA:
+ return f.uniqBy("id", rows.concat(rowsB)); // recursion base
+ case headA.id < headB.id:
+ rows.push(rowsA.shift()); // multiple mutation, simulate recursion
+ break; // recur
+ case headA.id >= headB.id:
+ rows.push(rowsB.shift()); // multiple mutation, simulate recursion
+ break; // recur
+ }
}
+ return f.uniqBy("id", rows); // remote return and/or recursion base
};
const setCellValue = (state, action, completeState, isRollback = false) => {
diff --git a/src/locales/de/table.json b/src/locales/de/table.json
index cc09cb7aa..39cefffdc 100644
--- a/src/locales/de/table.json
+++ b/src/locales/de/table.json
@@ -73,6 +73,11 @@
"unlock_toast": "Zum Entsperren 2x klicken/Enter",
"set_all_rows_final": "Ganze Tabelle \"Final\" markieren"
},
+ "archived": {
+ "is-archived": "Datensatz ist achiviert",
+ "set-archived": "Archivieren",
+ "unset-archived": "Zurückholen"
+ },
"translations": {
"dialog_headline": "Änderung einer mehrsprachigen Zelle",
"dialog_question": "Sie haben einen bereits übersetzen Inhalt verändert. Möchten Sie die Übersetzer der anderen Sprachen informieren?\nWählen Sie \"Nein\" falls Sie bspw. nur einen Rechtschreibfehler korrigiert haben.",
diff --git a/src/locales/en/table.json b/src/locales/en/table.json
index 433ef7fec..3d6f568bf 100644
--- a/src/locales/en/table.json
+++ b/src/locales/en/table.json
@@ -73,6 +73,12 @@
"unlock_toast": "2x left click or enter key to unlock",
"set_all_rows_final": "Mark the whole table as \"final\""
},
+
+ "archived": {
+ "is-archived": "Data set is archived",
+ "set-archived": "Archive",
+ "unset-archived": "Retrieve from archive"
+ },
"translations": {
"dialog_headline": "Multi language cell modified",
"dialog_question": "You modified content which had already been translated. Would you like to inform other languages' translators?\nChoose \"no\" if you just fixed a mistake.",
diff --git a/src/scss/cells/cell.scss b/src/scss/cells/cell.scss
index cdddc55d3..768afd92a 100644
--- a/src/scss/cells/cell.scss
+++ b/src/scss/cells/cell.scss
@@ -301,6 +301,13 @@
&.archived {
background: $color-archived-row-bg;
color: $color-archived-row-fg;
+
+ &.in-selected-row {
+ background: darken($color-archived-row-bg, 5%);
+ .cell-content {
+ background: darken($color-archived-row-bg, 5%);
+ }
+ }
}
}
diff --git a/src/scss/cells/metaCell.scss b/src/scss/cells/metaCell.scss
index b084869c4..e24f48b0d 100644
--- a/src/scss/cells/metaCell.scss
+++ b/src/scss/cells/metaCell.scss
@@ -93,5 +93,8 @@
&.archived {
background: $color-archived-row-bg;
color: $color-archived-row-fg;
+ &.in-selected-row {
+ background: darken($color-archived-row-bg, 5%);
+ }
}
}
diff --git a/src/scss/filter.scss b/src/scss/filter.scss
index 4d49e25dd..803656a02 100644
--- a/src/scss/filter.scss
+++ b/src/scss/filter.scss
@@ -3,14 +3,11 @@
.filter-wrapper {
display: flex;
-
margin-top: 10px;
margin-right: 20px;
outline-style: none;
-
-
&.active {
.filter-popup-button {
@include header-button-active();
@@ -82,7 +79,8 @@
background-color: $color-primary-contrast-text;
padding: 0;
- .filter-mode-popup .item, .item a {
+ .filter-mode-popup .item,
+ .item a {
&.active {
color: $color-primary-contrast-text;
background-color: $color-primary;
@@ -96,7 +94,8 @@
}
}
- .filter-row, .sort-row {
+ .filter-row,
+ .sort-row {
$button-radius: 30px;
overflow: visible;
width: 100%;
@@ -106,7 +105,6 @@
grid-template-columns: $button-radius 3fr 2fr 5fr $button-radius;
grid-column-gap: 8px;
-
.filter-array-button {
border-radius: 50%;
background-color: transparent;
@@ -129,7 +127,6 @@
top: 50%;
transform: translate(-50%, -50%);
}
-
}
.filter-input {
@@ -140,7 +137,8 @@
@include border-radius(3px);
width: 200px;
- &:focus, &:active {
+ &:focus,
+ &:active {
border-color: $color-primary-lighter;
}
@@ -201,6 +199,12 @@
}
}
+ .button__toggle-archived-rows {
+ i {
+ margin: 0;
+ }
+ }
+
.description-row {
.info {
color: $color-text-light-grey;
@@ -230,7 +234,6 @@
}
}
}
-
}
.filter-popup__button {
@@ -258,10 +261,10 @@
background-color: $color-primary-contrast-text;
padding: 8px 20px;
- color: $color-primary-text;;
+ color: $color-primary-text;
&:hover {
- background-color: $color-hover-background
+ background-color: $color-hover-background;
}
}
@@ -298,7 +301,8 @@
}
.filter-wrapper .filter-popup {
- .filter-popup__save-link-button, .filter-popup__toggle-list-button {
+ .filter-popup__save-link-button,
+ .filter-popup__toggle-list-button {
background-color: transparent;
border: none;
color: $color-text-light-grey;