Skip to content

Commit

Permalink
Show warnings in EntityUpload
Browse files Browse the repository at this point in the history
  • Loading branch information
matthew-white committed Apr 19, 2024
1 parent 8b32c47 commit 58d39fe
Show file tree
Hide file tree
Showing 11 changed files with 249 additions and 85 deletions.
42 changes: 0 additions & 42 deletions src/assets/css/bootstrap.css

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src/assets/scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ $color-danger: #de2a11;
$color-danger-lighter: #dd5454; // Between $color-danger and $color-danger-light
$color-danger-light: #ffe6e6;
$color-danger-dark: darken($color-danger, 10%);
$color-highlight: #faf1cd;

// Buttons and inputs
$color-action-foreground: #0c7bd1;
Expand Down
44 changes: 28 additions & 16 deletions src/assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ em:lang(ja) {
}
.tooltip-inner {
border-radius: 2px;
max-height: 100vh;
overflow-wrap: break-word;
padding-bottom: 2px;
// Allow the text to have line breaks.
Expand All @@ -124,6 +125,19 @@ em:lang(ja) {

[class^="icon-"] { vertical-align: -1px; }

.btn, a, .label {
.caret { margin-left: 6px; }

> [class^="icon-"]:first-child {
// There should not be white space between an icon and text content that
// follows it. For a link in particular, the underlining will be off if
// there is white space.
margin-right: $margin-right-icon;

+ .caret { margin-left: 0; }
}
}

/* Bootstrap has an .icon-bar class that is unrelated to IcoMoon, but our
IcoMoon styles end up applying to it, because our IcoMoon selectors select on
the "icon-" class name prefix. To resolve that, we copy the .icon-bar styles for
Expand All @@ -146,27 +160,18 @@ class. */
background-color: #888;
}

.title-icon{
.title-icon {
padding-left: 6px;
font-size: 24px;
color: #666;
}



////////////////////////////////////////////////////////////////////////////////
// LINKS AND BUTTONS

.btn, a {
.caret { margin-left: 6px; }

> [class^="icon-"]:first-child {
// There should not be white space between an icon and text content that
// follows it. For a link in particular, the underlining will be off if
// there is white space.
margin-right: $margin-right-icon;

+ .caret { margin-left: 0; }
}

&[aria-disabled="true"], &.disabled, fieldset[disabled] & {
cursor: not-allowed;
opacity: 0.65;
Expand Down Expand Up @@ -595,6 +600,13 @@ tr.uncommitted-change { background-color: #ffef67; }
> tbody > tr > td.info {
background-color: $color-info-light;
}

> thead > tr.highlight > th,
> thead > tr > th.highlight,
> tbody > tr.highlight > td,
> tbody > tr > td.highlight {
background-color: $color-highlight;
}
}

.data-loading {
Expand All @@ -620,9 +632,9 @@ tr.uncommitted-change { background-color: #ffef67; }
////////////////////////////////////////////////////////////////////////////////
// LABELS

.label-primary {
background-color: $color-action-background;
}
.label-default { background-color: #777; }
.label-primary { background-color: $color-action-background; }
.label-warning { background-color: $color-warning; }



Expand Down Expand Up @@ -878,7 +890,7 @@ becomes more complicated.
////////////////////////////////////////////////////////////////////////////////
// markRowsChanged(), markRowsDeleted()

[data-mark-rows-changed="true"] { background-color: #faf1cd; }
[data-mark-rows-changed="true"] { background-color: $color-highlight; }
[data-mark-rows-changed="false"] { transition: background-color 0.6s 6s; }

[data-mark-rows-deleted="true"] {
Expand Down
23 changes: 16 additions & 7 deletions src/components/entity/upload.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ except according to the terms contained in the LICENSE file.
<h1 class="panel-title">{{ $t('table.file') }}</h1>
</div>
<div class="panel-body">
<entity-upload-warnings v-if="warnings != null"
v-bind="warnings.details"/>
<entity-upload-warnings v-if="warnings != null && warnings.count !== 0"
v-bind="warnings.details" @rows="showWarningRows"/>
<entity-upload-table :ref="setTable(1)" :entities="csvSlice"
:row-index="csvRow" :page-size="csvPage.size"/>
<pagination v-if="csvEntities != null" v-model:page="csvPage.page"
Expand All @@ -60,8 +60,9 @@ except according to the terms contained in the LICENSE file.
</div>
<entity-upload-popup v-if="csvEntities != null"
:filename="fileMetadata.name" :count="csvEntities.length"
:awaiting-response="uploading" :progress="uploadProgress"
@clear="clearFile" @animationstart="animatePopup(true)"
:warnings="warnings.count" :awaiting-response="uploading"
:progress="uploadProgress" @clear="clearFile"
@animationstart="animatePopup(true)"
@animationend="animatePopup(false)"/>
<div class="modal-actions">
<button type="button" class="btn btn-primary"
Expand Down Expand Up @@ -252,7 +253,7 @@ const parseEntities = async (file, headerResults, signal) => {
if (results.data.length === 0) throw new Error(t('alert.noData'));
csvEntities.value = results.data;
fileMetadata.value = { name: file.name, size: file.size };
if (results.warnings.count !== 0) warnings.value = results.warnings;
warnings.value = results.warnings;
};
const selectFile = (file) => {
headerErrors.value = null;
Expand Down Expand Up @@ -307,6 +308,16 @@ watch(csvEntities, (value) => {
if (value == null) Object.assign(csvPage, { page: 0, size: defaultPageSize });
});

const tables = [null, null];
const setTable = (i) => (el) => { tables[i] = el; };
const showWarningRows = (range) => {
csvPage.page = Math.floor(range[0] / csvPage.size);
tables[1].highlightRows(range);
};
watch(csvEntities, (value) => {
if (value == null) tables[1].highlightRows([NaN, NaN]);
});

const { request, awaitingResponse: uploading } = useRequest();
const uploadProgress = ref(0);
const upload = () => {
Expand All @@ -328,8 +339,6 @@ watch(csvEntities, (value) => {
});

// Resize the last column of the tables.
const tables = [null, null];
const setTable = (i) => (el) => { tables[i] = el; };
const resizeLastColumn = () => {
for (const table of tables) table.resizeLastColumn();
};
Expand Down
15 changes: 15 additions & 0 deletions src/components/entity/upload/popup.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ except according to the terms contained in the LICENSE file.
</button>
</div>
<div id="entity-upload-popup-count">{{ $tcn('rowCount', count) }}</div>
<div v-show="warnings !== 0 && !awaitingResponse"
id="entity-upload-popup-warnings">
<span class="icon-warning"></span>{{ $tcn('count.warning', warnings) }}
</div>
<div v-show="awaitingResponse" id="entity-upload-popup-status">
<spinner inline/><span>{{ status }}</span>
</div>
Expand All @@ -44,6 +48,10 @@ const props = defineProps({
type: Number,
required: true
},
warnings: {
type: Number,
required: true
},
awaitingResponse: Boolean,
progress: {
type: Number,
Expand Down Expand Up @@ -80,6 +88,8 @@ const status = computed(() => (props.progress < 1
position: absolute;
right: 15px;
width: 305px;

.icon-warning { margin-right: $margin-right-icon; }
}

#entity-upload-popup-heading {
Expand All @@ -103,6 +113,11 @@ const status = computed(() => (props.progress < 1
}
}

#entity-upload-popup-warnings {
margin-bottom: 3px;
margin-top: 5px;
}

#entity-upload-popup-status {
margin-bottom: 5px;
margin-top: 15px;
Expand Down
12 changes: 9 additions & 3 deletions src/components/entity/upload/table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ except according to the terms contained in the LICENSE file.
</thead>
<tbody v-if="entities != null && entities.length !== 0"
:class="{ 'data-loading': awaitingResponse }">
<tr v-for="(entity, entityIndex) in entities" :key="entityIndex">
<tr v-for="(entity, entityIndex) in entities" :key="entityIndex"
:class="{ highlight: isHighlighted(rowIndex + entityIndex) }">
<td class="row-number">
{{ $n(rowIndex + entityIndex + 1, 'noGrouping') }}
</td>
Expand All @@ -39,7 +40,7 @@ except according to the terms contained in the LICENSE file.
</template>

<script setup>
import { nextTick, ref, watch } from 'vue';
import { nextTick, ref, shallowRef, watch } from 'vue';

import { px } from '../../../util/dom';
import { useRequestData } from '../../../request-data';
Expand Down Expand Up @@ -126,9 +127,14 @@ const resizeLastColumn = () => {
th.style.width = 'auto';
};

const highlightedRange = shallowRef([NaN, NaN]);
const highlightRows = (range) => { highlightedRange.value = range; };
const isHighlighted = (index) =>
index >= highlightedRange.value[0] && index <= highlightedRange.value[1];

const resetScroll = () => { container.value.scroll(0, 0); };

defineExpose({ resizeLastColumn, resetScroll });
defineExpose({ resizeLastColumn, highlightRows, resetScroll });
</script>

<style lang="scss">
Expand Down
76 changes: 76 additions & 0 deletions src/components/entity/upload/warning.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<!--
Copyright 2024 ODK Central Developers
See the NOTICE file at the top-level directory of this distribution and at
https://github.com/getodk/central-frontend/blob/master/NOTICE.

This file is part of ODK Central. It is subject to the license terms in
the LICENSE file found in the top-level directory of this distribution and at
https://www.apache.org/licenses/LICENSE-2.0. No part of ODK Central,
including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
-->
<template>
<div class="entity-upload-warning">
<span class="label label-warning">
<span class="icon-warning"></span>{{ $t('common.warning') }}
</span>
<slot></slot>
<template v-if="ranges != null">
<span>&nbsp;</span>
<i18n-list v-slot="{ value: [start, end] }" :list="ranges">
<a v-if="!Number.isNaN(start)" href="#"
@click.prevent="$emit('rows', [start - 1, end - 1])">
{{ formatRange(start, end) }}
</a>
<template v-else>&hellip;</template>
</i18n-list>
</template>
</div>
</template>

<script setup>
import I18nList from '../../i18n/list.vue';

import { useI18nUtils } from '../../../util/i18n';

defineOptions({
name: 'EntityUploadWarning'
});
defineProps({
ranges: Array,
overflow: Boolean
});
defineEmits(['rows']);

const { formatRange } = useI18nUtils();
</script>

<style lang="scss">
.entity-upload-warning {
background-color: #fff;
font-weight: bold;
padding: 4px 3px;

.label {
font-size: inherit;
margin-right: 10px;
}

.i18n-list {
font-weight: normal;
margin-left: 3px;
}

+ .entity-upload-warning { margin-top: 2px; }

$border-radius: 5px;
&:first-child {
border-top-left-radius: $border-radius;
border-top-right-radius: $border-radius;
}
&:last-child {
border-bottom-left-radius: $border-radius;
border-bottom-right-radius: $border-radius;
}
}
</style>
Loading

0 comments on commit 58d39fe

Please sign in to comment.