Merge pull request #744 from tainacan/feature/734

add new modal to create preset collections
This commit is contained in:
Vinícius Nunes Medeiros 2022-11-14 08:29:19 -03:00 committed by GitHub
commit daa0a67aed
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 372 additions and 59 deletions

View File

@ -3,7 +3,7 @@
style="min-height: initial; position: relative"
class="tainacan-cards-container">
<ul
v-if="collections.length <= 0 && !isLoading && $userCaps.hasCapability('tnc_rep_edit_collections')"
v-if="!$adminOptions.hideHomeCollectionCreateNewButton && collections.length <= 0 && !isLoading && $userCaps.hasCapability('tnc_rep_edit_collections')"
class="new-collection-menu">
<li>
<router-link
@ -74,7 +74,7 @@
</li>
</ul>
<ul v-if="collections.length > 0 && !isLoading">
<li v-if="$userCaps.hasCapability('tnc_rep_edit_collections')">
<li v-if="!$adminOptions.hideHomeCollectionCreateNewButton && $userCaps.hasCapability('tnc_rep_edit_collections')">
<router-link
tag="a"
:to="$routerHelper.getNewCollectionPath()"
@ -147,7 +147,7 @@
class="tainacan-card"
:class="{ 'always-visible-collections': $adminOptions.homeCollectionsPerPage }">
<ul class="menu-list">
<li v-if="!$adminOptions.hideHomeCollectionItemsButton">
<li>
<router-link
tag="a"
:to="{ path: $routerHelper.getCollectionItemsPath(collection.id, '') }"

View File

@ -0,0 +1,279 @@
<template>
<div
aria-labelledby="collection-creation-title"
autofocus
role="dialog"
tabindex="-1"
aria-modal
class="tainacan-modal-content"
style="width: auto"
ref="collectionCreationModal">
<header class="tainacan-modal-title">
<h2
id="collection-creation-title"
v-if="selectedEstrategy == 'mappers'">
{{ $i18n.get('label_create_collection_from_mapper') }}
</h2>
<h2
id="collection-creation-title"
v-else-if="selectedEstrategy == 'presets'">
{{ $i18n.get('label_create_collection_from_preset') }}
</h2>
<h2
id="collection-creation-title"
v-else>
{{ $i18n.get('label_create_collection') }}
</h2>
<a
@click="selectedEstrategy = hasPresetsHook ? undefined : 'mappers'"
v-if="(hasPresetsHook && selectedEstrategy != undefined) || (!hasPresetsHook && selectedEstrategy == 'mappers')"
class="back-link">
{{ $i18n.get('back') }}
</a>
<hr>
</header>
<section class="tainacan-form">
<div
v-if="selectedEstrategy == undefined"
class="collection-creation-options-container">
<button
class="collection-creation-option"
aria-role="listitem"
@click="selectedEstrategy = 'mappers'">
<h3>{{ $i18n.get('label_from_a_mapper') }}</h3>
<p>{{ $i18n.get('info_create_collection_from_mapper') }}</p>
</button>
<button
class="collection-creation-option"
aria-role="listitem"
@click="selectedEstrategy = 'presets'">
<h3>{{ $i18n.get('label_using_a_preset') }}</h3>
<p>{{ $i18n.get('info_create_collection_from_preset') }}</p>
</button>
</div>
<div
v-if="selectedEstrategy == 'mappers'"
class="collection-creation-options-container"
role="list">
<button
class="collection-creation-option"
@click="$router.push($routerHelper.getNewMappedCollectionPath(metadatumMapper.slug)); $parent.close();"
:key="metadatumMapper.slug"
v-for="metadatumMapper in metadatumMappers"
v-if="metadatumMapper.metadata != false"
aria-role="listitem">
<h3>{{ metadatumMapper.name }}</h3>
<p>{{ metadatumMapper.description }}</p>
</button>
</div>
<div
v-if="selectedEstrategy == 'presets'"
class="collection-creation-options-container"
role="list">
<button
class="collection-creation-option"
@click="onNewCollectionPreset(collectionPreset)"
:key="collectionPreset.slug"
v-for="collectionPreset in getPresetsHook"
aria-role="listitem">
<h3>{{ collectionPreset.name }}</h3>
<p>{{ collectionPreset.description }}</p>
</button>
</div>
<b-loading
:is-full-page="false"
:active.sync="isLoadingMetadatumMappers"
:can-cancel="false"/>
<b-loading
:is-full-page="false"
:active.sync="isCreatingCollectionPreset"
:can-cancel="false"/>
<footer class="field is-grouped form-submit">
<div class="control">
<button
class="button is-outlined"
type="button"
@click="$parent.close()">{{ $i18n.get('close') }}</button>
</div>
</footer>
</section>
</div>
</template>
<script>
import { mapActions, mapGetters } from 'vuex';
import axios from 'axios';
import { tainacanErrorHandler } from '../../js/axios';
export default {
name: 'CollectionCreationModal',
data(){
return {
selectedEstrategy: 'mappers',
isLoadingMetadatumMappers: true,
collectionPresets: [],
isCreatingCollectionPreset: false
}
},
computed: {
metadatumMappers() {
return this.getMetadatumMappers();
},
hasPresetsHook() {
if (wp !== undefined && wp.hooks !== undefined)
return wp.hooks.hasFilter(`tainacan_collections_presets`);
return false;
},
getPresetsHook() {
if (wp !== undefined && wp.hooks !== undefined)
return wp.hooks.applyFilters(`tainacan_collections_presets`, this.collectionPresets);
return this.collectionPresets;
},
},
watch: {
hasPresetsHook: {
handler() {
this.selectedEstrategy = this.hasPresetsHook ? undefined : 'mappers';
},
immediate: true
}
},
mounted() {
this.isLoadingMetadatumTypes = true;
this.fetchMetadatumMappers()
.then(() => {
this.isLoadingMetadatumMappers = false;
})
.catch(() => {
this.isLoadingMetadatumMappers = false;
});
if (this.$refs.collectionCreationModal)
this.$refs.collectionCreationModal.focus()
},
methods: {
...mapActions('metadata', [
'fetchMetadatumMappers'
]),
...mapGetters('metadata', [
'getMetadatumMappers'
]),
onNewCollectionPreset(collectionPreset) {
this.isCreatingCollectionPreset = true;
axios.post(collectionPreset.endpoint)
.then(() => {
const successMessage = typeof collectionPreset.onSuccess === 'function' ? collectionPreset.onSuccess() : this.$i18n.get('label_preset_success');
this.$buefy.snackbar.open({
message: successMessage,
type: 'is-success',
position: 'is-bottom-right',
pauseOnHover: true,
duration: 3500,
queue: false
});
this.isCreatingCollectionPreset = false;
this.$router.push(this.$routerHelper.getCollectionsPath());
this.$parent.close();
})
.catch((error) =>{
if (typeof collectionPreset.onError === 'function') {
const errorMessage = collectionPreset.onError();
this.$buefy.snackbar.open({
message: errorMessage,
type: 'is-danger',
position: 'is-bottom-right',
pauseOnHover: true,
duration: 3500,
queue: false
});
} else {
tainacanErrorHandler(error);
}
this.isCreatingCollectionPreset = false;
});
}
}
}
</script>
<style lang="scss" scoped>
.tainacan-modal-title {
margin-bottom: 24px;
h2 {
margin-bottom: 0;
}
.back-link {
color: var(--tainacan-secondary);
cursor: pointer;
}
}
.collection-creation-options-container {
display: flex;
flex-wrap: wrap;
gap: 24px;
p {
font-size: 1em;
color: var(--tainacan-gray5);
padding: 0em 1.25em;
margin-top: 0.75em;
margin-bottom: 0;
}
.collection-creation-option {
border: 1px solid var(--tainacan-input-border-color);
background-color: var(--tainacan-background-color);
text-align: left;
padding: 15px;
cursor: pointer;
flex-basis: calc(50% - 12px);
flex-grow: 1;
font-size: 1em;
transition: border 0.3s ease;
@media screen and (max-width: 768px) {
max-width: 100%;
margin: 12px;
}
h3 {
color: var(--tainacan-heading-color);
font-size: 1em !important;
font-weight: 500;
padding: 0em 0.5em;
margin: 0;
}
p {
font-size: 0.75em;
color: var(--tainacan-gray5);
padding: 0em 0.5em;
margin-bottom: 0;
}
&:hover {
border: 1px solid var(--tainacan-gray5);
}
}
}
</style>

View File

@ -27,10 +27,15 @@
tag="a"
to="/"
:aria-label="$i18n.get('label_plugin_home_page')">
<h1>
<img
class="tainacan-logo"
alt="Tainacan Logo"
:src="logoHeader">
<span
v-if="$adminOptions.tainacanHeaderExtraLabel"
v-text="$adminOptions.tainacanHeaderExtraLabel" />
</h1>
</router-link>
</div>
</div>
@ -198,11 +203,25 @@
}
.logo-area {
height: $header-height;
width: 10em;
min-width: 10em;
cursor: pointer;
&:focus {
box-shadow: none;
h1 {
display: flex;
align-items: center;
white-space: nowrap;
margin: 0px;
span {
color: var(--tainacan-blue5);
text-decoration-color: var(--tainacan-blue5);
text-transform: uppercase;
overflow: hidden;
text-overflow: ellipsis;
font-weight: bold;
font-size: 1.25rem;
margin: 0px 0.5rem 0px 0.75rem;
}
}
.tainacan-logo {
height: 1.5em;

View File

@ -2,7 +2,7 @@
<div
id="tainacan-repository-subheader"
class="level secondary-page"
:class="{'is-menu-compressed': isMenuCompressed, 'is-repository-level' : isRepositoryLevel}">
:class="{'is-menu-compressed': isMenuCompressed, 'is-menu-hidden': $adminOptions.hidePrimaryMenu, 'is-repository-level' : isRepositoryLevel}">
<div
v-if="$adminOptions.hideCollectionSubheader"
@ -198,6 +198,9 @@ export default {
&.is-menu-compressed {
padding-left: calc((var(--tainacan-one-column) - 2.083333333px) + 50px);
}
&.is-menu-hidden {
padding-left: calc(var(--tainacan-one-column) - 2.083333333px) !important;
}
h1 {
font-size: 1.125em;

View File

@ -567,11 +567,13 @@ AdminOptionsHelperPlugin.install = function (Vue, options = {}) {
* hideHomeCollectionFiltersButton
* hideHomeCollectionActivitiesButton
* hideHomeCollectionThemeCollectionButton
* hideHomeCollectionCreateNewButton
* showHomeCollectionCreateItemButton // Default is false
* homeCollectionsPerPage // Default is 9
* homeCollectionsOrderBy // Default is 'modified'
* homeCollectionsOrder // Default is 'desc'
* hideTainacanHeader
* tainacanHeaderExtraLabel // Adds a textual label aside the Tainacan Logo.
* hideTainacanHeaderHomeButton
* hideTainacanHeaderSearchInput
* hideTainacanHeaderAdvancedSearch
@ -594,6 +596,8 @@ AdminOptionsHelperPlugin.install = function (Vue, options = {}) {
* hideRepositorySubheaderExportButton
* hideCollectionSubheader
* hideCollectionsListCreationDropdown
* hideItemsListPageTitle
* hideItemsListMultipleSelection
* hideItemsListSelection

View File

@ -9,8 +9,8 @@ const i18nGet = function (key) {
return (string !== undefined && string !== null && string !== '' ) ? string : "ERROR: Invalid i18n key!";
};
const tainacanErrorHandler = function(error) {
console.log(error)
export const tainacanErrorHandler = function(error) {
console.error(error)
if (error.response && error.response.status) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
@ -65,10 +65,10 @@ const tainacanErrorHandler = function(error) {
// The request was made but no response was received
// `error.request` is an instance of XMLHttpRequest in the browser and an instance of
// http.ClientRequest in node.js
console.log('Tainacan Error Handler: ', error.request);
console.error('Tainacan Error Handler: ', error.request);
} else {
// Something happened in setting up the request that triggered an Error
console.log('Tainacan Error Handler: ', error.message);
console.error('Tainacan Error Handler: ', error.message);
}
return Promise.reject(error);
}
@ -80,6 +80,11 @@ export const tainacan = axios.create({
if (tainacan_plugin.nonce) {
tainacan.defaults.headers.common['X-WP-Nonce'] = tainacan_plugin.nonce;
}
if (tainacan_plugin.admin_request_options) {
Object.keys(tainacan_plugin.admin_request_options).forEach(requestOption => {
tainacan.defaults.headers[requestOption] = tainacan_plugin.admin_request_options[requestOption];
});
}
tainacan.interceptors.response.use(
(response) => response,
(error) => tainacanErrorHandler(error)
@ -101,4 +106,4 @@ export const CancelToken = axios.CancelToken;
export const isCancel = axios.isCancel;
export const all = axios.all;
export default { tainacan, wp, CancelToken, isCancel, all};
export default { tainacan, wp, CancelToken, isCancel, all, tainacanErrorHandler};

View File

@ -7,7 +7,7 @@
<!-- New Collection button -->
<div
v-if="$userCaps.hasCapability('tnc_rep_edit_collections')"
v-if="!$adminOptions.hideCollectionsListCreationDropdown && $userCaps.hasCapability('tnc_rep_edit_collections')"
class="header-item">
<b-dropdown
aria-role="list"
@ -31,17 +31,15 @@
<small class="is-small">{{ $i18n.get('info_choose_your_metadata') }}</small>
</router-link>
</b-dropdown-item>
<b-dropdown-item
:key="metadatum_mapper.slug"
v-for="metadatum_mapper in metadatum_mappers"
v-if="metadatum_mapper.metadata != false"
aria-role="listitem">
<router-link
:id="'a-create-collection-' + metadatum_mapper.slug"
<b-dropdown-item aria-role="listitem">
<div
id="a-preset-collection"
tag="div"
:to="{ path: $routerHelper.getNewMappedCollectionPath(metadatum_mapper.slug) }">
{{ $i18n.get(metadatum_mapper.name) }}
</router-link>
@click="onOpenCollectionCreationModal">
{{ $i18n.get('label_preset_collections') }}
<br>
<small class="is-small">{{ $i18n.get('info_preset_collections') }}</small>
</div>
</b-dropdown-item>
<b-dropdown-item aria-role="listitem">
<div
@ -261,9 +259,8 @@
{{ $i18n.get('info_no_collections_' + statusOption.slug) }}
</p>
<div v-if="$userCaps.hasCapability('tnc_rep_edit_collections') && status == undefined || status == ''">
<div v-if="!$adminOptions.hideCollectionsListCreationDropdown && $userCaps.hasCapability('tnc_rep_edit_collections') && status == undefined || status == ''">
<b-dropdown
:disabled="isLoadingMetadatumMappers"
id="collection-creation-options-dropdown"
aria-role="list"
trap-focus>
@ -285,17 +282,15 @@
<small class="is-small">{{ $i18n.get('info_choose_your_metadata') }}</small>
</router-link>
</b-dropdown-item>
<b-dropdown-item
:key="metadatum_mapper.slug"
v-for="metadatum_mapper in metadatum_mappers"
v-if="metadatum_mapper.metadata != false"
aria-role="listitem">
<router-link
:id="'a-create-collection-' + metadatum_mapper.slug"
<b-dropdown-item aria-role="listitem">
<div
id="a-preset-collection"
tag="div"
:to="{ path: $routerHelper.getNewMappedCollectionPath(metadatum_mapper.slug) }">
{{ $i18n.get(metadatum_mapper.name) }}
</router-link>
@click="onOpenCollectionCreationModal">
{{ $i18n.get('label_preset_collections') }}
<br>
<small class="is-small">{{ $i18n.get('info_preset_collections') }}</small>
</div>
</b-dropdown-item>
<b-dropdown-item aria-role="listitem">
<div
@ -364,6 +359,7 @@
<script>
import CollectionsList from '../../components/lists/collections-list.vue';
import AvailableImportersModal from '../../components/modals/available-importers-modal.vue';
import CollectionCreationModal from '../../components/modals/collection-creation-modal.vue';
import { mapActions, mapGetters } from 'vuex';
export default {
@ -391,11 +387,6 @@ export default {
}
},
computed: {
metadatum_mappers: {
get() {
return this.getMetadatumMappers();
}
},
collections() {
return this.getCollections();
},
@ -419,15 +410,6 @@ export default {
created() {
this.collectionsPerPage = this.$userPrefs.get('collections_per_page');
this.isLoadingMetadatumTypes = true;
this.fetchMetadatumMappers()
.then(() => {
this.isLoadingMetadatumMappers = false;
})
.catch(() => {
this.isLoadingMetadatumMappers = false;
});
this.isLoadingCollectionTaxonomies = true;
this.fetchCollectionTaxonomies()
.then(() => {
@ -438,6 +420,7 @@ export default {
});
},
mounted() {
if (this.collectionsPerPage != this.$userPrefs.get('collections_per_page'))
this.collectionsPerPage = this.$userPrefs.get('collections_per_page');
if (!this.collectionsPerPage) {
@ -452,7 +435,6 @@ export default {
this.$userPrefs.set('collections_order', 'asc');
}
if (this.orderBy != this.$userPrefs.get('collections_order_by'))
this.orderBy = this.$userPrefs.get('collections_order_by');
if (!this.orderBy) {
@ -476,9 +458,6 @@ export default {
'getRepositoryTotalCollections',
'getCollectionTaxonomies'
]),
...mapGetters('metadata', [
'getMetadatumMappers'
]),
onChangeTab(status) {
this.page = 1;
this.status = status;
@ -569,6 +548,16 @@ export default {
closeButtonAriaLabel: this.$i18n.get('close')
});
},
onOpenCollectionCreationModal() {
this.$buefy.modal.open({
parent: this,
component: CollectionCreationModal,
hasModalCard: true,
trapFocus: true,
customClass: 'tainacan-modal',
closeButtonAriaLabel: this.$i18n.get('close')
});
},
searchCollections() {
this.page = 1;
this.loadCollections();

View File

@ -374,6 +374,11 @@ class Admin {
$settings['wp_post_types'] = $wp_post_types;
// Key-valued array with extra options to be passed to every request in the admin (goes the header)
$admin_request_options = [];
$admin_request_options = apply_filters('tainacan-admin-extra-request-options', $admin_request_options);
$settings['admin_request_options'] = $admin_request_options;
return $settings;
}
@ -394,7 +399,7 @@ class Admin {
$admin_options = apply_filters('set_tainacan_admin_options', $_GET);
$admin_options = apply_filters('tainacan-admin-ui-options', $_GET);
$admin_options = json_encode($admin_options);
// TODO move it to a separate file and start the Vue project
echo "<div id='tainacan-admin-app' data-module='admin' data-options='$admin_options'></div>";
}

View File

@ -523,6 +523,7 @@ return apply_filters( 'tainacan-i18n', [
'label_%s_items_copy_success' => __( '%s item copies were created with success!', 'tainacan' ),
'label_one_item_copy_success' => __( 'The item copy was created with success!', 'tainacan' ),
'label_item_copy_failure' => __( 'Something wrong happened... Item copy failed!', 'tainacan' ),
'label_preset_success' => __( 'The preset was applied with success!', 'tainacan' ),
'label_create_another_taxonomy' => __( 'Create another Taxonomy', 'tainacan' ),
'label_make_copies_of_item' => __( 'Make copies of item', 'tainacan' ),
'label_number_of_copies' => __( 'Number of copies', 'tainacan' ),
@ -654,6 +655,11 @@ return apply_filters( 'tainacan-i18n', [
'label_create_item' => __( 'Create item', 'tainacan' ),
'label_ready_to_create_item' => __( 'Ready to create this item?', 'tainacan' ),
'label_only_required' => __( 'Only required', 'tainacan' ),
'label_create_collection_from_mapper' => __( 'Create a new collection from a mapper', 'tainacan' ),
'label_create_collection_from_preset' => __( 'Create a preset collection', 'tainacan' ),
'label_preset_collections' => __( 'Preset collections', 'tainacan' ),
'label_from_a_mapper' => __( 'From a metadata mapper', 'tainacan' ),
'label_using_a_preset' => __( 'Using a preset', 'tainacan' ),
// Instructions. More complex sentences to guide user and placeholders
'instruction_delete_selected_collections' => __( 'Delete selected collections', 'tainacan' ),
@ -970,6 +976,9 @@ return apply_filters( 'tainacan-i18n', [
'info_metadata_mapper_helper' => __( 'Select the corresponding metadata so they can be exposed according to the mapper', 'tainacan'),
'info_default_orderby' => __( 'These settings only affect the initial state of the items sorting. After changed, the value used will be the latest selected by the user.', 'tainacan' ),
'info_collection_thumbnail_and_header' => __( 'The thumbnail is a squared image that will represent the collection in listings. The header image is a complementary, decorative image that may or not be displayed by your theme in the items list. Keep in mind that it might be cropped.', 'tainacan'),
'info_preset_collections' => __( 'Use mappers or standards as pre configuration', 'tainacan' ),
'info_create_collection_from_mapper' => __( 'Have the metadata preset by an installed mapper, such as Dublin core, then set the rest manually.', 'tainacan' ),
'info_create_collection_from_preset' => __( 'Have metadata, taxonomies, terms and related collections preset according to a standard.', 'tainacan' ),
/* Activity actions */
'action_update-metadata-value' => __( 'Item Metadata Value Updates', 'tainacan'),