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

Add hover cards #1067

Merged
merged 7 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 1 addition & 2 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,8 @@ module.exports = {
'spaced-comment': 'off',
'vue/attributes-order': ['error', {
order: [
'DEFINITION',
'LIST_RENDERING',
'CONDITIONALS',
['CONDITIONALS', 'DEFINITION'],
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is because I want to be able to specify v-if before :is. I feel like v-if should generally be the very first thing.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

'RENDER_MODIFIERS',
'GLOBAL',
['UNIQUE', 'SLOT'],
Expand Down
3 changes: 3 additions & 0 deletions src/assets/scss/_variables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ $padding-top-btn: 6px;
$font-size-dropdown-menu: $font-size-btn;
$min-width-dropdown-menu: 150px;

$padding-block-dl: 10px;

// Forms
$padding-top-form-control: 6px;

Expand Down Expand Up @@ -94,6 +96,7 @@ $color-subpanel-border-strong: #c3c3c3;
$ease-extreme-out: cubic-bezier(.05, .9, 0, 1);



////////////////////////////////////////////////////////////////////////////////
// COMPONENTS

Expand Down
5 changes: 2 additions & 3 deletions src/assets/scss/app.scss
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ p { @include text-block; }

dl > * {
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
padding-top: 10px;
padding-block: $padding-block-dl;

&:first-of-type { padding-top: 0; }

Expand Down Expand Up @@ -75,7 +74,7 @@ dl:not(.dl-horizontal) {
// down to the fact that .dl-horizontal uses a CSS grid, while other <dl>
// elements use flexbox.
.dl-horizontal {
$padding-between: 10px;
$padding-between: 12px;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will have a minor effect on EntityUploadHeaderErrors, but I think that's OK.

$dt-width: 160px + $padding-panel-body + $padding-between;

display: grid;
Expand Down
9 changes: 8 additions & 1 deletion src/components/app.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,9 @@ except according to the terms contained in the LICENSE file.
<template v-else-if="$route.meta.standalone">
<router-view/>
</template>

<div id="tooltips"></div>
<hover-cards/>
</div>
</template>

Expand All @@ -48,7 +50,12 @@ import { loadAsync } from '../util/load-async';

export default {
name: 'App',
components: { Alert, Navbar, FeedbackButton: defineAsyncComponent(loadAsync('FeedbackButton')) },
components: {
Alert,
HoverCards: defineAsyncComponent(loadAsync('HoverCards')),
Navbar,
FeedbackButton: defineAsyncComponent(loadAsync('FeedbackButton'))
},
inject: ['alert', 'config'],
setup() {
const { visiblyLoggedIn } = useSessions();
Expand Down
48 changes: 31 additions & 17 deletions src/components/audit/row.vue
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,9 @@ except according to the terms contained in the LICENSE file.
</td>
<td class="target">
<template v-if="target != null">
<router-link v-if="target.path != null" :to="target.path"
<component v-if="target.component != null" :is="target.component"
v-bind="target.props" v-tooltip.text/>
<router-link v-else-if="target.path != null" :to="target.path"
v-tooltip.text>
{{ target.title }}
</router-link>
Expand All @@ -47,7 +49,11 @@ except according to the terms contained in the LICENSE file.
</template>

<script>
import { pick } from 'ramda';

import ActorLink from '../actor-link.vue';
import DatasetLink from '../dataset/link.vue';
import FormLink from '../form/link.vue';
import DateTime from '../date-time.vue';
import Selectable from '../selectable.vue';

Expand Down Expand Up @@ -88,10 +94,13 @@ const acteeSpeciesByCategory = {
},
form: {
title: (actee) => (actee.name != null ? actee.name : actee.xmlFormId),
path: (actee, { primaryFormPath }) => primaryFormPath(actee)
component: FormLink,
props: (actee) => ({ form: actee })
},
dataset: {
title: (actee) => actee.name
title: (actee) => actee.name,
component: DatasetLink,
props: pick(['projectId', 'name'])
},
public_link: {
title: getDisplayName
Expand All @@ -107,7 +116,7 @@ acteeSpeciesByCategory.upgrade = acteeSpeciesByCategory.form;

export default {
name: 'AuditRow',
components: { ActorLink, DateTime, Selectable },
components: { ActorLink, DatasetLink, DateTime, FormLink, Selectable },
props: {
audit: {
type: Object,
Expand All @@ -116,8 +125,8 @@ export default {
},
setup() {
const { actionMessage } = useAudit();
const { projectPath, primaryFormPath, userPath } = useRoutes();
return { actionMessage, projectPath, primaryFormPath, userPath };
const { projectPath, userPath } = useRoutes();
return { actionMessage, projectPath, userPath };
},
computed: {
// When an audit log action has multiple parts/segments, we treat it as
Expand All @@ -144,18 +153,23 @@ export default {
if (this.category == null) return null;
const species = acteeSpeciesByCategory[this.category];
if (species == null) return null;
const { actee } = this.audit;

// purged actee (used purgedName as title)
if (actee.purgedAt != null)
return { title: actee.purgedName, purged: true };

const title = species.title(actee);
// soft-deleted actee (use species title but don't make a link)
if (actee.deletedAt != null) return { title, deleted: true };

const result = { title };
if (species.path != null) result.path = species.path(actee, this);
const { actee } = this.audit;
const deleted = actee.deletedAt != null;
const purged = actee.purgedAt != null;
const result = {
title: purged ? actee.purgedName : species.title(actee),
deleted,
purged
};
if (!(deleted || purged)) {
if (species.path != null) {
result.path = species.path(actee, this);
} else if (species.component != null) {
result.component = species.component;
result.props = species.props(actee);
}
}
return result;
},
details() {
Expand Down
44 changes: 44 additions & 0 deletions src/components/dataset/link.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!--
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.
-->

<!-- Specifying :key so that if `to` changes, the element will be replaced. If a
hover card is shown next to the element, it will be hidden. -->
<template>
<router-link ref="link" :key="to" :to="to">{{ name }}</router-link>
</template>

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

import useHoverCard from '../../composables/hover-card';
import useRoutes from '../../composables/routes';

defineOptions({
name: 'DatasetLink'
});
const props = defineProps({
projectId: {
type: [Number, String],
required: true
},
name: {
type: String,
required: true
}
});

const { datasetPath } = useRoutes();
const to = computed(() => datasetPath(props.projectId, props.name));

const link = ref(null);
useHoverCard(computed(() => link.value?.$el), 'dataset', () => props);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to pass the HTMLElement of the <router-link> to useHoverCard(). It looks like that's accessible via $el, though I don't see that documented in the Vue Router docs. Apparently $el is generally accessible from components that use the Options API. Components that use the Composition API can opt into providing access to the root element by using defineExpose().

</script>
9 changes: 4 additions & 5 deletions src/components/dataset/row.vue
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ except according to the terms contained in the LICENSE file.
<template>
<tr class="dataset-row">
<td class="name">
<router-link :to="datasetOverviewPage" v-tooltip.text>{{ dataset.name }}</router-link>
<dataset-link :project-id="dataset.projectId" :name="dataset.name"
v-tooltip.text/>
</td>
<td class="entities">
<span>{{ $n(dataset.entities, 'default') }}</span>
Expand All @@ -39,14 +40,15 @@ except according to the terms contained in the LICENSE file.
</template>

<script>
import DatasetLink from './link.vue';
import DateTime from '../date-time.vue';

import useRoutes from '../../composables/routes';
import { apiPaths } from '../../util/request';

export default {
name: 'DatasetRow',
components: { DateTime },
components: { DatasetLink, DateTime },
props: {
dataset: {
type: Object,
Expand All @@ -60,9 +62,6 @@ export default {
computed: {
href() {
return apiPaths.entities(this.dataset.projectId, this.dataset.name, '.csv');
},
datasetOverviewPage() {
return this.datasetPath(this.dataset.projectId, this.dataset.name);
}
}
};
Expand Down
3 changes: 1 addition & 2 deletions src/components/dataset/show.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ 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 id="dataset-show">
<breadcrumbs v-if="dataExists" :links="breadcrumbLinks"/>
Expand Down Expand Up @@ -84,7 +83,7 @@ export default {
const { tabPath, tabClass } = useTabs(datasetPath());
return {
project, dataset, ...resourceStates([project, dataset]),
projectPath, datasetPath, tabPath, tabClass, canRoute
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I searched for all uses of datasetPath() to see where to add DatasetLink. I noticed that this component doesn't use datasetPath() in the template, so it doesn't need to return it from setup().

projectPath, tabPath, tabClass, canRoute
};
},
computed: {
Expand Down
14 changes: 4 additions & 10 deletions src/components/dataset/summary/row.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,8 @@ except according to the terms contained in the LICENSE file.
<div class="row">
<div class="col-xs-6 dataset-name-wrap">
<div class="dataset-name text-overflow-ellipsis" v-tooltip.text>
<router-link v-if="!dataset.isNew" :to="datasetPath(projectId, dataset.name)" v-tooltip.text>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The parent element specifies v-tooltip.text, so I don't think this one needs to.

{{ dataset.name }}
</router-link>
<dataset-link v-if="!dataset.isNew" :project-id="projectId"
:name="dataset.name"/>
<template v-else>
{{ dataset.name }}
</template>
Expand Down Expand Up @@ -50,13 +49,12 @@ except according to the terms contained in the LICENSE file.
</template>

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

import useRoutes from '../../../composables/routes';

export default {
name: 'DatasetSummaryRow',
components: { I18nList },
components: { DatasetLink, I18nList },
props: {
dataset: {
type: Object,
Expand All @@ -67,10 +65,6 @@ export default {
required: true
}
},
setup() {
const { datasetPath } = useRoutes();
return { datasetPath };
},
data() {
return {
expanded: false
Expand Down
11 changes: 4 additions & 7 deletions src/components/entity/basic-details.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,9 @@ except according to the terms contained in the LICENSE file.
<div v-if="submission != null">
<dt>{{ $t('creatingSubmission') }}</dt>
<dd id="entity-basic-details-creating-submission">
<router-link v-if="submission.currentVersion != null"
:to="submissionPath(projectId, submission.xmlFormId, submission.instanceId)">
{{ submission.currentVersion.instanceName ?? submission.instanceId }}
</router-link>
<submission-link v-if="submission.currentVersion != null"
:project-id="projectId" :xml-form-id="submission.xmlFormId"
:submission="submission"/>
<template v-else>
<span class="icon-trash" v-tooltip.sr-only></span>
<span>{{ submission.instanceId }}</span>
Expand Down Expand Up @@ -55,8 +54,8 @@ import { inject, ref, watchEffect } from 'vue';
import ActorLink from '../actor-link.vue';
import DateTime from '../date-time.vue';
import PageSection from '../page/section.vue';
import SubmissionLink from '../submission/link.vue';

import useRoutes from '../../composables/routes';
import { useRequestData } from '../../request-data';

defineOptions({
Expand All @@ -83,8 +82,6 @@ watchEffect(() => {
submission.value = audit.details.source?.submission;
source.value = audit.details.source;
});

const { submissionPath } = useRoutes();
</script>

<style lang="scss">
Expand Down
Loading