Skip to content
Open
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
11 changes: 8 additions & 3 deletions src/components/Form/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
<script>
import FormLocales from './FormLocales.vue';
import FormPage from './FormPage.vue';
import {shouldShowField} from './formHelpers';
import {shouldShowField, requireWhen} from './formHelpers';
import Icon from '@/components/Icon/Icon.vue';

export default {
Expand All @@ -88,6 +88,11 @@ export default {
FormPage,
Icon,
},
provide() {
return {
requireWhen: (isRequired) => requireWhen(isRequired, this.fields),
};
},
props: {
/** Used by a parent component, such as `Container`, to identify events emitted from the form and update the form props when necessary. */
id: String,
Expand Down Expand Up @@ -379,8 +384,8 @@ export default {
let errors = {};
this.fields.forEach((field) => {
if (
!field.isRequired ||
!shouldShowField(field, this.fields, this.groups)
!shouldShowField(field, this.fields, this.groups) ||
!requireWhen(field.isRequired, this.fields)
) {
return;
}
Expand Down
8 changes: 6 additions & 2 deletions src/components/Form/FormFieldLabel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
<template v-else>
{{ label }}
</template>
<span v-if="isRequired" class="pkpFormFieldLabel__required">
<span
v-if="requireWhen ? requireWhen(isRequired) : isRequired"
class="pkpFormFieldLabel__required"
>
<span class="aria-hidden">*</span>
<span class="-screenReader">{{ requiredLabel }}</span>
</span>
Expand All @@ -17,14 +20,15 @@
<script>
export default {
name: 'FormFieldLabel',
inject: ['requireWhen', null],
props: {
labelId: String,
controlId: String,
label: String,
localeLabel: String,
multilingualLabel: String,
isRequired: {
type: Boolean,
type: [Boolean, Array],
default: false,
},
requiredLabel: String,
Expand Down
5 changes: 3 additions & 2 deletions src/components/Form/fields/FieldBase.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ export default {
},
/** Whether or not this field should be presented for each supported language. */
isMultilingual: Boolean,
/** Whether or not a value for this field should be required. */
isRequired: Boolean,
/** Whether or not a value for this field should be required.
* You can also pass an array to require a specific value or multiple values. Similar as below 'showWhen'. */
isRequired: [Boolean, Array],
/** The `name` of another field which should have a truthy value before this field is shown.
* You can also pass an array to require a specific value or multiple values. For example, `['primaryLocale', 'en_US']` would hide this field unless the `primaryLocale` field had a value of `en_US`. Or `['primaryLocale', ['en_US', 'fr_CA']]` to check for multiple values. */
showWhen: [String, Array],
Expand Down
17 changes: 17 additions & 0 deletions src/components/Form/formHelpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,20 @@ export function shouldShowField(field, fields, groups) {
shouldShowGroup(group, fields) && shouldShowFieldWithinGroup(field, fields)
);
}

/**
* Require when a field value
*
* @param {Boolean|Array} requireValue Require value
* @param {Object} fields All form fields
* @return {Boolean}
*/

export function requireWhen(requireValue, fields) {
if (!Array.isArray(requireValue)) return requireValue;

const [whenFieldName, whenValues] = requireValue;
const val = fields.find((field) => field.name === whenFieldName)?.value;

return whenValues.includes(val);
}
14 changes: 7 additions & 7 deletions src/components/Form/mocks/field-options-authors-role.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
export default {
name: 'userGroupId',
name: 'contributorRoles',
component: 'field-options',
label: "Contributor's role",
type: 'radio',
isRequired: false,
value: 14,
label: "Contributor's roles",
type: 'checkbox',
isRequired: true,
value: [1],
groupId: 'default',
options: [
{value: 14, label: 'Author'},
{value: 15, label: 'Translator'},
{value: 1, label: 'Author'},
{value: 2, label: 'Translator'},
],
};
30 changes: 24 additions & 6 deletions src/components/Form/mocks/form-contributors.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,24 @@ export default {
method: 'POST',
action: '/submissions/9/publications/__publicationId__/contributors',
fields: [
{
name: 'contributorType',
component: 'field-options',
label: 'Contributor Type',
groupId: 'default',
isRequired: true,
isMultilingual: false,
isInert: false,
value: 'PERSON',
type: 'radio',
isOrderable: false,
allowOnlySorting: false,
options: [
{value: 'PERSON', label: 'Person'},
{value: 'ORGANIZATION', label: 'Organization or group'},
{value: 'ANONYMOUS', label: 'Anonymous'},
],
},
{
name: 'givenName',
component: 'field-text',
Expand Down Expand Up @@ -113,20 +131,20 @@ export default {
prefix: '',
},
{
name: 'userGroupId',
name: 'contributorRoles',
component: 'field-options',
label: "Contributor's role",
groupId: 'default',
isRequired: false,
isRequired: true,
isMultilingual: false,
isInert: false,
value: 14,
type: 'radio',
value: [1],
type: 'checkbox',
isOrderable: false,
allowOnlySorting: false,
options: [
{value: 14, label: 'Author'},
{value: 15, label: 'Translator'},
{value: 1, label: 'Author'},
{value: 2, label: 'Translator'},
],
},
{
Expand Down
11 changes: 9 additions & 2 deletions src/components/ListPanel/contributors/ContributorsListPanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,11 @@
<template #item-title="{item}">
<div class="whitespace-normal text-justify">
{{ item.fullName }}
<Badge v-if="item.userGroupName">
{{ localize(item.userGroupName) }}
<Badge
v-for="contributorRole in item.contributorRoles"
:key="contributorRole.id"
>
{{ localize(contributorRole.name) }}
</Badge>
</div>
</template>
Expand Down Expand Up @@ -399,6 +402,10 @@ export default {
} else if (field.name === 'affiliations') {
field.authorId = author['id'];
field.value = author[field.name];
} else if (field.name === 'contributorRoles') {
field.value = author[field.name].map(
(contributorRole) => contributorRole.id,
);
} else if (Object.keys(author).includes(field.name)) {
field.value = author[field.name];
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<template>
<DialogBody :is-loading="isLoading">
<div
v-strip-unsafe-html="
t('manager.contributorRoles.alert.delete.message.body', {
identifier: contributorRoleIdentifier,
})
"
></div>

<FieldText
class="mt-8"
size="large"
:value="inputValue"
@input="inputValue = $event.target.value"
/>
<template #actions>
<PkpButton
:disabled="inputValue !== contributorRoleIdentifier"
is-primary
@click="confirmInput"
>
{{ t('common.confirmDelete') }}
</PkpButton>
<PkpButton :is-warnable="true" @click="closeModal">
{{ t('common.cancel') }}
</PkpButton>
</template>
</DialogBody>
</template>

<script setup>
import PkpButton from '@/components/Button/Button.vue';
import DialogBody from '@/components/Modal/DialogBody.vue';
import FieldText from '@/components/Form/fields/FieldText.vue';
import {useLocalize} from '@/composables/useLocalize';
import {ref} from 'vue';

const {t} = useLocalize();

const inputValue = ref('');
const isLoading = ref(false);
const props = defineProps({
/**
* Function to be executed when the user confirms the deletion.
*/
onConfirm: {
type: Function,
required: true,
},
/**
* Function to be executed when the dialog is closed.
*/
onClose: {
type: Function,
required: false,
},
/**
* Identifier of the contributor role to be deleted. Displayed in the dialog message
* and used as the required input for confirming the deletion.
*/
contributorRoleIdentifier: {
type: String,
required: true,
},
});

/**
* Function to confirm the deletion of the role. It checks if the input value
* matches the role identifier and then calls the onConfirm function.
*/
async function confirmInput() {
if (inputValue.value !== props.contributorRoleIdentifier) {
return;
}

isLoading.value = true;
await props.onConfirm();
closeModal();
}

function closeModal() {
props.onClose?.();
}
</script>
70 changes: 70 additions & 0 deletions src/managers/ContributorRoleManager/ContributorRoleManager.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<template>
<PkpTable>
<template #label>{{ t('manager.contributorRoles.title') }}</template>
<template #top-controls>
<PkpButton @click="contributorRoleManagerStore.roleAdd">
{{ t('manager.contributorRoles.add') }}
</PkpButton>
</template>
<TableHeader>
<TableColumn>
<span>
{{ t('manager.contributorRoles.name') }}
</span>
</TableColumn>
<TableColumn>
<span>
{{ t('manager.contributorRoles.identifier') }}
</span>
</TableColumn>
<TableColumn>
<span class="sr-only">{{ t('common.moreActions') }}</span>
</TableColumn>
</TableHeader>

<TableBody>
<TableRow
v-for="role in contributorRoleManagerStore.roles"
:key="role.id"
>
<TableCell :is-row-header="true">
<span>
{{ localize(role.name) }}
</span>
</TableCell>
<TableCell>
<span>
{{ role.contributorRoleIdentifier }}
</span>
</TableCell>
<TableCell>
<DropdownActions
:label="t('common.moreActions')"
button-variant="ellipsis"
:actions="contributorRoleManagerStore.getItemActions()"
@action="
(actionName) => contributorRoleManagerStore[actionName](role)
"
/>
</TableCell>
</TableRow>
</TableBody>
</PkpTable>
</template>

<script setup>
import PkpTable from '@/components/Table/Table.vue';
import TableHeader from '@/components/Table/TableHeader.vue';
import TableColumn from '@/components/Table/TableColumn.vue';
import TableBody from '@/components/Table/TableBody.vue';
import TableRow from '@/components/Table/TableRow.vue';
import TableCell from '@/components/Table/TableCell.vue';
import DropdownActions from '@/components/DropdownActions/DropdownActions.vue';
import PkpButton from '@/components/Button/Button.vue';
import {localize} from '@/utils/i18n.js';
import {useContributorRoleManagerStore} from '@/managers/ContributorRoleManager/contributorRoleManagerStore.js';

const props = defineProps({});

const contributorRoleManagerStore = useContributorRoleManagerStore(props);
</script>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<template>
<div>
<SideModalBody>
<template #title>
{{ title }}
</template>
<SideModalLayoutBasic>
<PkpForm
ref="editRole"
class="contributorRoles__contributorRoleForm"
v-bind="form"
@set="set"
@success="
(...args) => {
$emit('contributorRoleSaved', ...args);
onSuccess();
}
"
></PkpForm>
</SideModalLayoutBasic>
</SideModalBody>
</div>
</template>

<script setup>
import SideModalBody from '@/components/Modal/SideModalBody.vue';
import SideModalLayoutBasic from '@/components/Modal/SideModalLayoutBasic.vue';
import PkpForm from '@/components/Form/Form.vue';
import {useForm} from '@/composables/useForm';

const props = defineProps({
title: {type: String, required: true},
formProps: {ype: Object, required: true},
onSuccess: {type: Function, required: true},
});

const {set, form} = useForm(props.formProps);

defineEmits(['contributorRoleSaved']);
</script>
Loading