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 && ( + + )}
- -
+ ) ); }; 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;