Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show conflicts in entity feed #892

Merged
merged 4 commits into from
Nov 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions src/assets/scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ $color-info-light: #d9edf7;
$color-info-dark: darken($color-info, 10%);
$color-warning: #f29e00;
$color-warning-light: #f5c93b;
$color-warning-dark: #e06f0b;
$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%);

Expand Down Expand Up @@ -80,6 +82,9 @@ $padding-top-table-data: 8px;
// Panels
$box-shadow-panel-main: 0 0 24px rgba(0, 0, 0, 0.25), 0 35px 115px rgba(0, 0, 0, 0.28);

// Navs
$hpadding-nav-tab: 8px;

$color-subpanel-background: #eee;
$color-subpanel-active: #ddd;
$color-subpanel-border: #e6e6e6;
Expand All @@ -92,6 +97,9 @@ $ease-extreme-out: cubic-bezier(.05, .9, 0, 1);
////////////////////////////////////////////////////////////////////////////////
// COMPONENTS

// FeedEntry
$hpadding-feed-entry: 15px;

// Modal
$padding-modal-body: 15px;
$padding-left-modal-header: 15px;
Expand Down
2 changes: 1 addition & 1 deletion src/assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -780,7 +780,7 @@ becomes more complicated.
border-bottom: 2px solid transparent;
border-radius: 0;
color: $color-text;
padding: 7px 8px 6px;
padding: 7px $hpadding-nav-tab 6px;
}

&:focus {
Expand Down
98 changes: 75 additions & 23 deletions src/components/entity/diff.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,89 @@ including this file, may be copied, modified, propagated, or distributed
except according to the terms contained in the LICENSE file.
-->
<template>
<div>
<diff-item v-for="key of entityVersion.serverDiff" :key="key" :path="[key]"
:old="propOrLabel(oldVersion, key)"
:new="propOrLabel(entityVersion, key)"/>
<div class="entity-diff" :class="conflictClass">
<entity-diff-head v-if="entityVersion.conflict != null" v-model="diffProp"/>
<entity-diff-table v-if="entityVersion.conflict != null || diff.length !== 0"
:diff="diff"/>
<p v-if="entityVersion.conflict != null && diff.length === 0"
class="empty-table-message">
{{ $t('noChange') }}
</p>
</div>
</template>

<script>
const propOrLabel = (version, key) =>
(key === 'label' ? version.label : version.data[key]);
</script>

<script setup>
import { computed } from 'vue';
import { computed, inject, ref } from 'vue';

import DiffItem from '../diff-item.vue';
import { useRequestData } from '../../request-data';
import EntityDiffHead from './diff/head.vue';
import EntityDiffTable from './diff/table.vue';

defineOptions({
name: 'EntityDiff'
});
const props = defineProps({
entityVersion: {
type: Object,
required: true
}
});
const entityVersion = inject('entityVersion');

const conflictClass = entityVersion.conflict != null
? `${entityVersion.conflict}-conflict`
: null;

// The component assumes that this data will exist when the component is
// created.
const { entityVersions } = useRequestData();
const oldVersion = computed(() =>
entityVersions[props.entityVersion.version - 2]);
const diffProp = ref('baseDiff');
const diff = computed(() => entityVersion[diffProp.value]);
</script>

<style lang="scss">
@import '../../assets/scss/variables';

$border-width: 1px;
.entity-diff {
border-left: $border-width solid transparent;
border-right: $border-width solid transparent;
&.hard-conflict, &.soft-conflict {
border-bottom: $border-width solid transparent;
border-top: 12px solid transparent;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
&.hard-conflict { border-color: $color-danger; }
&.soft-conflict { border-color: $color-warning-dark; }

.empty-table-message { margin-left: $hpadding-feed-entry; }
}

// The styles below are here rather than in EntityDiffHead and EntityDiffTable
// because they have to do with the alignment of the EntityDiff as a whole. Many
// of the styles below reference $border-width.

$tabs-indent: 3px;
.entity-diff-head {
$hpadding: $hpadding-feed-entry - $border-width;
padding-left: $hpadding;
padding-right: $hpadding;

// By itself, -$hpadding-nav-tab would align the text of the first tab with
// the text above it. However, we want it to be a little more indented than
// that.
.nav-tabs { margin-left: #{$tabs-indent - $hpadding-nav-tab}; }
}

.entity-diff-table {
// Align the text of the first column with the text of the first tab.
$padding-left: $hpadding-feed-entry - $border-width + $tabs-indent;
col:nth-child(1) {
// 150px is for the text (the property name).
width: #{$padding-left + 150px + $padding-right-table-data};
}
col:nth-child(3) { width: 30px; }

td:first-child { padding-left: $padding-left; }
td:last-child { padding-right: #{$hpadding-feed-entry - $border-width}; }
}
</style>

<i18n lang="json5">
{
"en": {
"noChange": "There are no changes to show."
}
}
</i18n>
158 changes: 158 additions & 0 deletions src/components/entity/diff/head.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
<!--
Copyright 2023 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-diff-head">
<div v-if="entityVersion.conflict === 'hard'">
<span class="icon-warning"></span>
<p>
<strong>{{ $t('common.conflict') }}&nbsp;</strong>
<span>{{ $t('introduction', { version: entityVersion.version - 1, baseVersion: entityVersion.baseVersion }) }}</span>
<sentence-separator/>
<span>{{ $t('hardConflict.description') }}</span>
</p>
</div>
<div v-else>
<span class="icon-info-circle"></span>
<p>
<strong>{{ $t('softConflict.title') }}&nbsp;</strong>
<span>{{ $t('introduction', { version: entityVersion.version - 1, baseVersion: entityVersion.baseVersion }) }}</span>
<sentence-separator/>
<span>{{ $t('softConflict.description') }}</span>
</p>
</div>
<ul class="nav nav-tabs">
<li v-for="[diff, version] of tabs" :key="diff"
:class="{ active: modelValue === diff }" role="presentation">
<a href="#" role="button" @click.prevent="change(diff)">
{{ $t(`tab.${diff}`) }}
<span class="updating">
{{ $t('tab.updating', { version: $t('common.versionShort', { version }) }) }}
</span>
</a>
</li>
</ul>
</div>
</template>

<script setup>
import { inject } from 'vue';

import SentenceSeparator from '../../sentence-separator.vue';

defineOptions({
name: 'EntityDiffHead'
});
const props = defineProps({
modelValue: {
type: String,
required: true
}
});
const emit = defineEmits(['update:modelValue']);

const entityVersion = inject('entityVersion');
const tabs = [
['baseDiff', entityVersion.baseVersion],
['serverDiff', entityVersion.version - 1]
];

const change = (value) => {
if (value !== props.modelValue) emit('update:modelValue', value);
};
</script>

<style lang="scss">
@import '../../../assets/scss/variables';

.entity-diff-head {
.entity-diff.hard-conflict & { background-color: $color-danger; }
.entity-diff.soft-conflict & { background-color: $color-warning-dark; }

> div {
color: #fff;
display: flex;
}

p {
line-height: 1.2;
margin-bottom: 18px;
margin-left: $margin-right-icon;
max-width: none;
}

.icon-warning, .icon-info-circle { font-size: 16px; }
strong { margin-right: 4px; }

li {
margin-bottom: 0;
margin-right: 3px;

a { font-weight: 600; }
a, &.active a {
&, &:hover, &:focus {
border-bottom: none;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
color: #fff;
padding-bottom: 5px;
padding-top: 6px;
}
}
&.active a {
&, &:hover, &:focus { background-color: $color-page-background; }
}
}
.entity-diff.hard-conflict & {
li.active a { color: $color-danger; }
.entity-feed-entry:hover & li:not(.active) a {
background-color: $color-danger-lighter;
}
}
.entity-diff.soft-conflict & {
li.active a { color: $color-warning-dark; }
.entity-feed-entry:hover & li:not(.active) a {
background-color: $color-warning;
}
}

.updating { margin-left: 2px; }
}
</style>

<i18n lang="json5">
{
"en": {
// {version} and {baseVersion} are version numbers.
"introduction": "This Submission update was applied to version {version} of this Entity, but it was created based on version {baseVersion}.",
"hardConflict": {
"description": "Other updates had already written to the same properties."
},
"softConflict": {
// An update to an Entity that was made at the same time as another update
"title": "Parallel Update",
"description": "No parallel update before this one touched the same properties."
},
"tab": {
// A comparison between two versions of an Entity, from the point of view
// of the data collector (the author)
"baseDiff": "Author’s View",
// A comparison between two versions of an Entity, from the point of view
// of the Central server
"serverDiff": "Central’s View",
// This text is shown for an update to an Entity. {version} is a short
// version identifier, for example, "v3". It is the version that the
// update was applied to.
"updating": "(updating {version})"
}
}
}
</i18n>
Loading