Adds UI to show separate page of mapper. #783.
This commit is contained in:
parent
d9eb5a6285
commit
fa64b506bc
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<div class="page-container repository-level-page">
|
||||
<div
|
||||
:class="{ 'repository-level-page page-container': isRepositoryLevel }"
|
||||
style="padding-bottom: 60px">
|
||||
<tainacan-title
|
||||
:bread-crumb-items="[
|
||||
{ path: $routerHelper.getMappersPath(), label: $i18n.get('mappers') },
|
||||
|
@ -7,7 +9,8 @@
|
|||
]" />
|
||||
<metadata-mapping-list
|
||||
v-if="(isRepositoryLevel && $userCaps.hasCapability('tnc_rep_edit_metadata') || (!isRepositoryLevel && collection && collection.current_user_can_edit_metadata))"
|
||||
:is-repository-level="isRepositoryLevel"/>
|
||||
:is-repository-level="isRepositoryLevel"
|
||||
:mapper="mapper" />
|
||||
<section
|
||||
v-else
|
||||
class="section">
|
||||
|
@ -36,13 +39,20 @@
|
|||
mixins: [ wpAjax, formHooks ],
|
||||
data(){
|
||||
return {
|
||||
mapper: null,
|
||||
isLoading: false,
|
||||
isUpdatingSlug: false,
|
||||
isRepositoryLevel: false,
|
||||
collectionId: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
collection() {
|
||||
return this.getCollection();
|
||||
},
|
||||
mapper() {
|
||||
return this.getMapper();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.isRepositoryLevel = (this.$route.params.collectionId === undefined);
|
||||
this.collectionId = this.$route.params.collectionId;
|
||||
|
@ -62,104 +72,15 @@
|
|||
},
|
||||
methods: {
|
||||
...mapActions('metadata', [
|
||||
'updateMapper',
|
||||
'fetchMapper',
|
||||
]),
|
||||
...mapGetters('metadata',[
|
||||
'getMapper',
|
||||
]),
|
||||
...mapGetters('collection',[
|
||||
'getCollection',
|
||||
]),
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.tab-content {
|
||||
overflow: visible !important;
|
||||
}
|
||||
.status-radios {
|
||||
display: flex;
|
||||
}
|
||||
.status-radios .control-lable {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.tainacan-form>.columns {
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
.tainacan-form .column:last-of-type {
|
||||
padding-left: var(--tainacan-one-column) !important;
|
||||
}
|
||||
.two-columns-fields {
|
||||
column-width: 180px;
|
||||
|
||||
.field {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
}
|
||||
.form-submit {
|
||||
align-items: center;
|
||||
}
|
||||
.updated-at {
|
||||
margin: 0 1em 0 auto;
|
||||
color: var(--tainacan-info-color);
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.footer {
|
||||
padding: 14px var(--tainacan-one-column);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
background-color: var(--tainacan-gray1);
|
||||
width: calc(100% - var(--tainacan-sidebar-width, 3.25em));
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
transition: bottom 0.5s ease, width 0.2s linear;
|
||||
|
||||
.footer-message {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.update-info-section {
|
||||
color: var(--tainacan-info-color);
|
||||
margin-right: auto;
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.help {
|
||||
display: inline-flex;
|
||||
font-size: 1.0em;
|
||||
margin-top: 0;
|
||||
margin-left: 24px;
|
||||
|
||||
.tainacan-help-tooltip-trigger {
|
||||
margin-left: 0.25em;
|
||||
}
|
||||
}
|
||||
|
||||
.link-button {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 769px) {
|
||||
padding: 13px 0.5em;
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
height: auto;
|
||||
position: fixed;
|
||||
|
||||
.update-info-section {
|
||||
margin-left: auto;margin-bottom: 0.75em;
|
||||
margin-top: -0.25em;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
@ -5,12 +5,19 @@
|
|||
<div
|
||||
class="mapper"
|
||||
v-for="mapper in mappers"
|
||||
:key="mapper.slug"
|
||||
:key="mapper.slug">
|
||||
<button
|
||||
class="mapper-clickable"
|
||||
@click="goToMapperEditionPage(mapper.slug)">
|
||||
<h4>{{ mapper.name }}</h4>
|
||||
<p>{{ mapper.description }}</p>
|
||||
</button>
|
||||
<b-switch
|
||||
v-model="mapper.enabled"
|
||||
style="z-index: 1;position: relative;"
|
||||
:false-value="true"
|
||||
:true-value="false"
|
||||
:value="mapper.disabled"
|
||||
@input="updateCurrentMapper($event, mapper.slug)"
|
||||
size="is-small" />
|
||||
</div>
|
||||
|
||||
|
@ -24,15 +31,32 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import { mapActions } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'MappersList',
|
||||
props: {
|
||||
isLoading: false,
|
||||
isRepositoryLevel: false,
|
||||
mappers: Array
|
||||
},
|
||||
methods: {
|
||||
...mapActions('metadata', [
|
||||
'updateMapper',
|
||||
]),
|
||||
goToMapperEditionPage(mapperSlug) {
|
||||
if ( this.isRepositoryLevel )
|
||||
this.$router.push(this.$routerHelper.getMapperEditPath(mapperSlug));
|
||||
else
|
||||
this.$router.push(this.$routerHelper.getCollectionMapperEditPath(this.$route.params.collectionId, mapperSlug));
|
||||
},
|
||||
updateCurrentMapper($event, mapperSlug) {
|
||||
this.updateMapper({
|
||||
isRepositoryLevel: this.isRepositoryLevel,
|
||||
collectionId: this.$route.params.collectionId,
|
||||
disabled: $event,
|
||||
mapper: mapperSlug
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -49,7 +73,6 @@
|
|||
border: 1px solid var(--tainacan-gray2);
|
||||
padding: 15px;
|
||||
min-height: 100px;
|
||||
cursor: pointer;
|
||||
background-color: var(--tainacan-item-background-color);
|
||||
transition: border 0.3s ease, background-color 0.15s ease;
|
||||
|
||||
|
@ -57,6 +80,19 @@
|
|||
background-color: var(--tainacan-item-hover-background-color);
|
||||
border: 1px solid var(--tainacan-gray3);
|
||||
}
|
||||
|
||||
.switch {
|
||||
float: left;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.mapper-clickable {
|
||||
text-align: start;
|
||||
border: none;
|
||||
background: none;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
<template>
|
||||
<div class="metadata-mappers-area">
|
||||
<b-loading
|
||||
:can-cancel="false"
|
||||
:is-full-page="false"
|
||||
:active.sync="isLoadingMapper"/>
|
||||
|
||||
<b-loading
|
||||
:can-cancel="false"
|
||||
:is-full-page="false"
|
||||
:active.sync="isLoadingMetadata"/>
|
||||
|
||||
<p>{{ mapper.description }}</p>
|
||||
|
||||
<!-- No metadata found warning -->
|
||||
<section
|
||||
v-if="activeMetadatumList.length <= 0 && !isLoadingMetadata"
|
||||
|
@ -44,8 +43,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="mapping-header"
|
||||
v-if="mapperMetadata.length > 0">
|
||||
class="mapping-header">
|
||||
<p>{{ $i18n.get('label_from_source_mapper') }}</p>
|
||||
<hr>
|
||||
<span class="icon">
|
||||
|
@ -55,7 +53,21 @@
|
|||
<p>{{ $i18n.get('label_to_target_mapper') }}</p>
|
||||
</div>
|
||||
|
||||
<section
|
||||
v-if="mapperMetadata.length <= 0"
|
||||
class="section">
|
||||
<div class="content has-text-grey has-text-centered">
|
||||
<p>
|
||||
<span class="icon">
|
||||
<i class="tainacan-icon tainacan-icon-30px tainacan-icon-processes tainacan-icon-rotate-90"/>
|
||||
</span>
|
||||
</p>
|
||||
<p>{{ $i18n.get('info_no_metadata_from_mapper') }} <span v-if="mapper.allow_extra_metadata">{{ $i18n.get('info_mapper_extra_metadata') }}</span></p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div
|
||||
v-else
|
||||
v-for="(mapperMetadatum, index) of mapperMetadata"
|
||||
:key="index"
|
||||
class="source-metadatum">
|
||||
|
@ -117,7 +129,7 @@
|
|||
</div>
|
||||
|
||||
<div
|
||||
v-if="mapper && !isLoadingMapper"
|
||||
v-if="mapper"
|
||||
class="field is-grouped form-submit fixed-form-submit">
|
||||
<div class="control">
|
||||
<button
|
||||
|
@ -199,7 +211,6 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
collectionId: '',
|
||||
isLoadingMapper: true,
|
||||
isLoadingMetadata: false,
|
||||
mapperMetadata: [],
|
||||
isMapperMetadataLoading: false,
|
||||
|
@ -220,8 +231,6 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
|
||||
/* If we're in a collection list, the metadata won't exist as they are read inside sections */
|
||||
if ( !this.isRepositoryLevel ) {
|
||||
this.collectionId = this.$route.params.collectionId;
|
||||
|
||||
this.isLoadingMetadata = true;
|
||||
|
@ -229,7 +238,7 @@ export default {
|
|||
this.cleanMetadata();
|
||||
this.fetchMetadata({
|
||||
collectionId: this.collectionId,
|
||||
isRepositoryLevel: false,
|
||||
isRepositoryLevel: this.isRepositoryLevel,
|
||||
isContextEdit: true,
|
||||
includeDisabled: true,
|
||||
includeOptionsAsHtml: false
|
||||
|
@ -244,9 +253,7 @@ export default {
|
|||
});
|
||||
})
|
||||
.catch(() => this.isLoadingMetadata = false);
|
||||
} else {
|
||||
this.loadMapperMetadata();
|
||||
}
|
||||
|
||||
},
|
||||
methods: {
|
||||
...mapActions('metadata', [
|
||||
|
@ -263,9 +270,9 @@ export default {
|
|||
this.isMapperMetadataLoading = true;
|
||||
this.mapperMetadata = [];
|
||||
|
||||
if (this.mapper) {
|
||||
for (var k in this.mapper.metadata) {
|
||||
var item = this.mapper.metadata[k];
|
||||
if ( this.mapper && this.mapper.metadata ) {
|
||||
for (let k in this.mapper.metadata) {
|
||||
let item = this.mapper.metadata[k];
|
||||
item.slug = k;
|
||||
item.selected = '';
|
||||
item.isCustom = false;
|
||||
|
@ -287,7 +294,7 @@ export default {
|
|||
) {
|
||||
this.newMapperMetadataList.push(Object.assign({},metadatum.exposer_mapping[this.mapper.slug]));
|
||||
this.mappedMetadata.push(metadatum.id);
|
||||
var item = Object.assign({},metadatum.exposer_mapping[this.mapper.slug]);
|
||||
let item = Object.assign({},metadatum.exposer_mapping[this.mapper.slug]);
|
||||
item.selected = metadatum.id;
|
||||
item.isCustom = true;
|
||||
this.mapperMetadata.push(item);
|
||||
|
@ -309,10 +316,10 @@ export default {
|
|||
},
|
||||
onUpdateMapperClick() {
|
||||
this.isMapperMetadataLoading = true;
|
||||
var metadataMapperMetadata = [];
|
||||
let metadataMapperMetadata = [];
|
||||
this.mapperMetadata.forEach((item) => {
|
||||
if (item.selected.length != 0) {
|
||||
var map = {
|
||||
let map = {
|
||||
metadatum_id: item.selected,
|
||||
mapper_metadata: item.slug
|
||||
};
|
||||
|
@ -321,7 +328,7 @@ export default {
|
|||
});
|
||||
this.activeMetadatumList.forEach((item) => {
|
||||
if(this.mappedMetadata.indexOf(item.id) == -1) {
|
||||
var map = {
|
||||
let map = {
|
||||
metadatum_id: item.id,
|
||||
mapper_metadata: ''
|
||||
};
|
||||
|
@ -329,10 +336,10 @@ export default {
|
|||
}
|
||||
});
|
||||
this.newMapperMetadataList.forEach((item) => {
|
||||
var slug = item.slug;
|
||||
let slug = item.slug;
|
||||
metadataMapperMetadata.forEach( (meta, index) => {
|
||||
if(meta.mapper_metadata == slug) {
|
||||
var item_clone = Object.assign({}, item); // TODO check if still need to clone
|
||||
let item_clone = Object.assign({}, item); // TODO check if still need to clone
|
||||
delete item_clone.selected;
|
||||
delete item_clone.isCustom;
|
||||
meta.mapper_metadata = item_clone;
|
||||
|
@ -341,6 +348,8 @@ export default {
|
|||
});
|
||||
});
|
||||
this.updateMapper({
|
||||
isRepositoryLevel: this.isRepositoryLevel,
|
||||
collectionId: this.collectionId,
|
||||
metadataMapperMetadata: metadataMapperMetadata,
|
||||
mapper: this.mapper.slug
|
||||
}).then(() => {
|
||||
|
@ -366,13 +375,13 @@ export default {
|
|||
},
|
||||
onSaveNewMetadataMapperMetadata() {
|
||||
this.isMapperMetadataLoading = true;
|
||||
var newMapperMetadata = {
|
||||
let newMapperMetadata = {
|
||||
label: this.newMetadataLabel,
|
||||
uri: this.newMetadataUri,
|
||||
slug: this.stringToSlug(this.newMetadataLabel),
|
||||
isCustom: true
|
||||
};
|
||||
var selected = '';
|
||||
let selected = '';
|
||||
if(this.new_metadata_slug != '') { // Editing
|
||||
this.newMapperMetadataList.forEach((meta, index) => {
|
||||
if(meta.slug == this.new_metadata_slug) {
|
||||
|
@ -400,10 +409,10 @@ export default {
|
|||
str = str.toLowerCase();
|
||||
|
||||
// remove accents, swap ñ for n, etc
|
||||
var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
|
||||
var to = "aaaaeeeeiiiioooouuuunc------";
|
||||
const from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;";
|
||||
const to = "aaaaeeeeiiiioooouuuunc------";
|
||||
|
||||
for (var i=0, l=from.length ; i<l ; i++) {
|
||||
for (let i=0, l=from.length ; i<l ; i++) {
|
||||
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
|
||||
}
|
||||
|
||||
|
@ -421,11 +430,11 @@ export default {
|
|||
this.isMapperMetadataCreating = true;
|
||||
},
|
||||
removeMetadatumCustomMapper(customMapperMeta) {
|
||||
var itemid = 0;
|
||||
let itemid = 0;
|
||||
this.newMapperMetadataList.forEach((meta, index) => {
|
||||
if(meta.slug == customMapperMeta.slug) {
|
||||
this.newMapperMetadataList.splice(index);
|
||||
var rem = this.mappedMetadata.indexOf(meta.selected);
|
||||
let rem = this.mappedMetadata.indexOf(meta.selected);
|
||||
this.mappedMetadata.splice(rem);
|
||||
itemid = customMapperMeta.selected;
|
||||
}
|
||||
|
@ -554,13 +563,17 @@ export default {
|
|||
}
|
||||
|
||||
.fixed-form-submit {
|
||||
margin-top: 24px;
|
||||
position: sticky !important;
|
||||
padding: 14px var(--tainacan-one-column);
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
background: var(--tainacan-background-color, white);
|
||||
z-index: 9;
|
||||
padding: 12px;
|
||||
border-top: 1px solid var(--tainacan-gray3);
|
||||
box-shadow: 0 -5px 12px -14px var(--tainacan-gray5);
|
||||
right: 0;
|
||||
z-index: 9999;
|
||||
background-color: var(--tainacan-gray1);
|
||||
width: calc(100% - var(--tainacan-sidebar-width, 3.25em));
|
||||
height: 60px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
transition: bottom 0.5s ease, width 0.2s linear;
|
||||
}
|
||||
</style>
|
|
@ -281,6 +281,9 @@ RouterHelperPlugin.install = function (Vue, options = {}) {
|
|||
getCollectionActivityPath(collectionId, activityId) {
|
||||
return '/collections/' + collectionId + '/activities/' + activityId;
|
||||
},
|
||||
getCollectionMapperEditPath(collectionId, mapperSlug) {
|
||||
return '/collections/'+ collectionId + '/mappers/' + mapperSlug;
|
||||
},
|
||||
// New
|
||||
getNewCollectionPath() {
|
||||
return '/collections/new';
|
||||
|
|
|
@ -69,7 +69,7 @@ const routes = [
|
|||
{ path: '/filters', name: 'FiltersPage', component: FiltersPage, meta: { title: i18nGet('title_repository_filters_page') } },
|
||||
|
||||
{ path: '/mappers', name: 'MappersPage', component: MappersPage, meta: { title: i18nGet('title_repository_mappers_page') } },
|
||||
{ path: '/mappers/:mapperSlug', name: 'MappersEditionForm', component: MapperEditionForm, meta: { title: i18nGet('title_repository_mappers_edit_page') } },
|
||||
{ path: '/mappers/:mapperSlug', name: 'MappersEditionForm', component: MapperEditionForm, meta: { title: i18nGet('title_repository_mappers_edit') } },
|
||||
|
||||
{ path: '/taxonomies', name: 'TaxonomyPage', component: TaxonomyPage, meta: { title: i18nGet('title_taxonomies_page') } },
|
||||
{ path: '/taxonomies/new', name: 'TaxonomyCreationForm', component: TaxonomyEditionForm, meta: { title: i18nGet('title_create_taxonomy_page') } },
|
||||
|
|
|
@ -274,13 +274,24 @@ export const fetchMappers = ({commit}) => {
|
|||
});
|
||||
}
|
||||
|
||||
export const updateMapper = ({ dispatch }, {metadataMapperMetadata, mapper}) => {
|
||||
export const updateMapper = ({ commit }, { isRepositoryLevel, collectionId, metadataMapperMetadata, disabled, mapper }) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
var param = {
|
||||
metadata_mappers: metadataMapperMetadata,
|
||||
};
|
||||
param[tainacan_plugin.exposer_mapper_param] = mapper;
|
||||
axios.tainacan.post('/mappers', param).then((res) => {
|
||||
let params = {};
|
||||
|
||||
if ( metadataMapperMetadata )
|
||||
params['metadata_mappers'] = metadataMapperMetadata;
|
||||
|
||||
if ( disabled !== undefined )
|
||||
params['disabled'] = disabled;
|
||||
|
||||
let endpoint = '/mappers/' + mapper;
|
||||
|
||||
if ( collectionId && !isRepositoryLevel )
|
||||
endpoint = '/collection/' + collectionId + endpoint;
|
||||
|
||||
axios.tainacan.post(endpoint, params)
|
||||
.then((res) => {
|
||||
commit('updateMapper', mapper);
|
||||
resolve(res.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
|
@ -439,8 +450,14 @@ export const fetchMetadataSectionMetadata = ({commit}, { collectionId , metadata
|
|||
|
||||
// MAPPERS
|
||||
export const fetchMapper = ({commit}, { collectionId, mapperSlug }) => {
|
||||
|
||||
let endpoint = '/mappers/' + mapperSlug;
|
||||
|
||||
if ( collectionId )
|
||||
endpoint = '/collection/' + collectionId + endpoint;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
axios.tainacan.get('/mappers/' + mapperSlug)
|
||||
axios.tainacan.get(endpoint)
|
||||
.then((res) => {
|
||||
let mapper = res.data;
|
||||
commit('updateMapper', mapper);
|
||||
|
|
|
@ -14,6 +14,6 @@ export const getMappers = state => {
|
|||
return state.mappers;
|
||||
}
|
||||
|
||||
export const getMapper = (state, { mapperSlug }) => {
|
||||
return state.mapper[0];
|
||||
export const getMapper = (state) => {
|
||||
return state.mapper;
|
||||
}
|
|
@ -6,6 +6,7 @@ const state = {
|
|||
metadata: [],
|
||||
metadatumTypes: [],
|
||||
mappers: [],
|
||||
mapper: {},
|
||||
metadataSections: []
|
||||
};
|
||||
|
||||
|
|
|
@ -242,6 +242,6 @@ export const moveMetadatumDown = (state, { index, sectionIndex }) => {
|
|||
state.metadataSections[sectionIndex].metadata_object_list.splice(index + 1, 0, state.metadataSections[sectionIndex].metadata_object_list.splice(index, 1)[0]);
|
||||
}
|
||||
|
||||
export const updateMapper = (state, { mapper }) => {
|
||||
//state.mappers ...;
|
||||
export const updateMapper = (state, mapper) => {
|
||||
state.mapper = mapper;
|
||||
}
|
|
@ -917,6 +917,7 @@ return apply_filters( 'tainacan-i18n', [
|
|||
'info_target_collection_helper' => __( 'The collection where imported item will be added. Only those that you have permission are listed.', 'tainacan' ),
|
||||
'info_source_file_upload' => __( 'The file containing the data to be imported.', 'tainacan' ),
|
||||
'info_no_metadata_source_file' => __( 'No metadata was found from the source file.', 'tainacan' ),
|
||||
'info_no_metadata_from_mapper' => __( 'No metadata was found from this mapper.', 'tainacan' ),
|
||||
'info_no_special_fields_available' => __( 'No special field was found.', 'tainacan' ),
|
||||
'info_special_fields_mapped_default' => __( 'Mapped to default field on collection.', 'tainacan' ),
|
||||
'info_metadata_mapping_helper' => __( 'Map each file metadata with the corresponding one in selected collection.', 'tainacan' ),
|
||||
|
@ -1051,6 +1052,7 @@ return apply_filters( 'tainacan-i18n', [
|
|||
'info_%s_terms_created' => __( '%s terms created with success.', 'tainacan' ),
|
||||
'info_terms_creation_failed_due_to_value_%s' => __( 'Terms creation failed due to value: %s.', 'tainacan' ),
|
||||
'info_terms_creation_failed_due_to_values_%s' => __( 'Terms creation failed due to values: %s.', 'tainacan' ),
|
||||
'info_mapper_extra_metadata' => __( 'You may create new metadata inside the mapper to map them to your own metadata.', 'tainacan' ),
|
||||
|
||||
/* Activity actions */
|
||||
'action_update-metadata-value' => __( 'Item Metadata Value Updates', 'tainacan'),
|
||||
|
|
Loading…
Reference in New Issue