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 1 commit
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
$padding-left-nav-tab: 7px;

$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: $padding-left-nav-tab 8px 6px;
}

&:focus {
Expand Down
87 changes: 64 additions & 23 deletions src/components/entity/diff.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,37 +10,78 @@ 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 :diff="diff"/>
matthew-white marked this conversation as resolved.
Show resolved Hide resolved
<p v-if="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 EntityDiffTable from './diff/table.vue';
import EntityDiffHead from './diff/head.vue';

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

// 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 conflictClass = entityVersion.conflict != null
? `${entityVersion.conflict}-conflict`
: null;

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: $border-width solid transparent;
border-top-left-radius: 6px;
border-top-right-radius: 6px;
}
&.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 because they
// reference $border-width.

.entity-diff-head {
$hpadding: #{$hpadding-feed-entry - $border-width};
padding-left: $hpadding;
padding-right: $hpadding;
}

.entity-diff-table {
col:nth-child(1) {
// 150px for the text (the property name)
width: #{$hpadding-feed-entry - $border-width + 150px + $padding-right-table-data};
}
col:nth-child(3) { width: 30px; }

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

<i18n lang="json5">
{
"en": {
"noChange": "There are no changes to show."
}
}
</i18n>
161 changes: 161 additions & 0 deletions src/components/entity/diff/head.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
<!--
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="$emit('update:modelValue', 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: 'EntityDiffTabs'
});
defineProps({
modelValue: {
type: String,
required: true
}
});
defineEmits(['update:modelValue']);

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

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

.entity-diff-head {
color: #fff;
padding-top: 12px;

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

> div { 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; }
// Align the text of the first tab with the text above and below it.
ul { margin-left: -$padding-left-nav-tab; }

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

a { font-weight: 600; }
a, &.active a {
&, &:hover, &:focus {
border-bottom: none;
border-top: 1px solid transparent;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
color: #fff;
}
}
a {
&, &:hover, &:focus { background-color: transparent; }
}
&.active a {
&, &:hover, &:focus { background-color: $color-page-background; }
}
}
.entity-diff.hard-conflict & {
li.active a {
&, &:hover, &:focus { color: $color-danger; }
}
.entity-feed-entry:hover & li:not(.active) a {
background-color: $color-danger-lighter;
}
}
.entity-diff.soft-conflict & {
li.active a {
&, &:hover, &:focus { 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": {
"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 above a comparison of two versions of an Entity.
// {version} is a short version identifier, for example, "v3".
"updating": "(updating {version})"
}
}
}
</i18n>
Loading