Remove old vue editor
This commit is contained in:
parent
51c79ba6a6
commit
f5a66df802
5 changed files with 0 additions and 567 deletions
|
@ -1,254 +0,0 @@
|
||||||
import { getJSON, debounce } from '@utils';
|
|
||||||
import Uploader from '../../shared/activestorage/uploader';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
props: ['state', 'index', 'item'],
|
|
||||||
computed: {
|
|
||||||
isValid() {
|
|
||||||
if (this.deleted) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (this.libelle) {
|
|
||||||
return !!this.libelle.trim();
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
itemClassName() {
|
|
||||||
const classNames = [`draggable-item-${this.index}`];
|
|
||||||
if (this.isHeaderSection) {
|
|
||||||
classNames.push('type-header-section');
|
|
||||||
}
|
|
||||||
return classNames.join(' ');
|
|
||||||
},
|
|
||||||
isDropDown() {
|
|
||||||
return [
|
|
||||||
'drop_down_list',
|
|
||||||
'multiple_drop_down_list',
|
|
||||||
'linked_drop_down_list'
|
|
||||||
].includes(this.typeChamp);
|
|
||||||
},
|
|
||||||
isFile() {
|
|
||||||
return this.typeChamp === 'piece_justificative';
|
|
||||||
},
|
|
||||||
isCarte() {
|
|
||||||
return this.typeChamp === 'carte';
|
|
||||||
},
|
|
||||||
isExplication() {
|
|
||||||
return this.typeChamp === 'explication';
|
|
||||||
},
|
|
||||||
isHeaderSection() {
|
|
||||||
return this.typeChamp === 'header_section';
|
|
||||||
},
|
|
||||||
isRepetition() {
|
|
||||||
return this.typeChamp === 'repetition';
|
|
||||||
},
|
|
||||||
options() {
|
|
||||||
const options = this.item.options || {};
|
|
||||||
for (let key of Object.keys(options)) {
|
|
||||||
options[key] = castBoolean(options[key]);
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
},
|
|
||||||
attribute() {
|
|
||||||
if (this.state.isAnnotation) {
|
|
||||||
return 'types_de_champ_private_attributes';
|
|
||||||
} else {
|
|
||||||
return 'types_de_champ_attributes';
|
|
||||||
}
|
|
||||||
},
|
|
||||||
payload() {
|
|
||||||
const payload = {
|
|
||||||
libelle: this.libelle,
|
|
||||||
type_champ: this.typeChamp,
|
|
||||||
mandatory: this.mandatory,
|
|
||||||
description: this.description,
|
|
||||||
drop_down_list_value: this.dropDownListValue,
|
|
||||||
order_place: this.index
|
|
||||||
};
|
|
||||||
if (this.pieceJustificativeTemplate) {
|
|
||||||
payload.piece_justificative_template = this.pieceJustificativeTemplate;
|
|
||||||
}
|
|
||||||
if (this.state.parentId) {
|
|
||||||
payload.parent_id = this.state.parentId;
|
|
||||||
}
|
|
||||||
if (!this.id && this.state.isAnnotation) {
|
|
||||||
payload.private = true;
|
|
||||||
}
|
|
||||||
Object.assign(payload, this.options);
|
|
||||||
return payload;
|
|
||||||
},
|
|
||||||
saveUrl() {
|
|
||||||
if (this.id) {
|
|
||||||
return `${this.state.saveUrl}/${this.id}`;
|
|
||||||
}
|
|
||||||
return this.state.saveUrl;
|
|
||||||
},
|
|
||||||
savePayload() {
|
|
||||||
if (this.deleted) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
return { type_de_champ: this.payload };
|
|
||||||
},
|
|
||||||
saveMethod() {
|
|
||||||
if (this.deleted) {
|
|
||||||
return 'delete';
|
|
||||||
} else if (this.id) {
|
|
||||||
return 'patch';
|
|
||||||
}
|
|
||||||
return 'post';
|
|
||||||
},
|
|
||||||
typesDeChamp() {
|
|
||||||
return this.item.types_de_champ;
|
|
||||||
},
|
|
||||||
typesDeChampOptions() {
|
|
||||||
return this.state.typesDeChampOptions.filter(
|
|
||||||
([, typeChamp]) => !EXCLUDE_FROM_REPETITION.includes(typeChamp)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
stateForRepetition() {
|
|
||||||
return Object.assign({}, this.state, {
|
|
||||||
typesDeChamp: this.typesDeChamp,
|
|
||||||
typesDeChampOptions: this.typesDeChampOptions,
|
|
||||||
prefix: `${this.state.prefix}[${this.attribute}][${this.index}]`,
|
|
||||||
parentId: this.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
id: this.item.id,
|
|
||||||
typeChamp: this.item.type_champ,
|
|
||||||
libelle: this.item.libelle,
|
|
||||||
mandatory: this.item.mandatory,
|
|
||||||
description: this.item.description,
|
|
||||||
pieceJustificativeTemplate: null,
|
|
||||||
pieceJustificativeTemplateUrl: this.item.piece_justificative_template_url,
|
|
||||||
pieceJustificativeTemplateFilename: this.item
|
|
||||||
.piece_justificative_template_filename,
|
|
||||||
dropDownListValue: this.item.drop_down_list_value,
|
|
||||||
deleted: false,
|
|
||||||
isSaving: false,
|
|
||||||
isUploading: false,
|
|
||||||
hasChanges: false
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
index() {
|
|
||||||
this.update();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
created() {
|
|
||||||
this.debouncedSave = debounce(() => this.save(), 500);
|
|
||||||
this.debouncedUpload = debounce(evt => this.upload(evt), 500);
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
removeChamp() {
|
|
||||||
if (this.id) {
|
|
||||||
this.deleted = true;
|
|
||||||
this.debouncedSave();
|
|
||||||
} else {
|
|
||||||
const index = this.state.typesDeChamp.indexOf(this.item);
|
|
||||||
this.state.typesDeChamp.splice(index, 1);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nameFor(name) {
|
|
||||||
return `${this.state.prefix}[${this.attribute}][${this.index}][${name}]`;
|
|
||||||
},
|
|
||||||
elementIdFor(name) {
|
|
||||||
const prefix = this.state.prefix.replace(/\[/g, '_').replace(/\]/g, '');
|
|
||||||
return `${prefix}_${this.attribute}_${this.index}_${name}`;
|
|
||||||
},
|
|
||||||
addChamp() {
|
|
||||||
this.typesDeChamp.push({
|
|
||||||
type_champ: 'text',
|
|
||||||
types_de_champ: []
|
|
||||||
});
|
|
||||||
},
|
|
||||||
update() {
|
|
||||||
this.hasChanges = true;
|
|
||||||
if (this.isValid) {
|
|
||||||
if (this.state.inFlight === 0) {
|
|
||||||
this.state.flash.clear();
|
|
||||||
}
|
|
||||||
this.debouncedSave();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
upload(evt) {
|
|
||||||
if (this.isUploading) {
|
|
||||||
this.debouncedUpload();
|
|
||||||
} else {
|
|
||||||
const input = evt.target;
|
|
||||||
const file = input.files[0];
|
|
||||||
if (file) {
|
|
||||||
this.isUploading = true;
|
|
||||||
const controller = new Uploader(
|
|
||||||
input,
|
|
||||||
file,
|
|
||||||
this.state.directUploadUrl
|
|
||||||
);
|
|
||||||
controller.start().then(signed_id => {
|
|
||||||
this.pieceJustificativeTemplate = signed_id;
|
|
||||||
this.isUploading = false;
|
|
||||||
this.debouncedSave();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
input.value = null;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
if (this.isSaving) {
|
|
||||||
this.debouncedSave();
|
|
||||||
} else {
|
|
||||||
this.isSaving = true;
|
|
||||||
this.state.inFlight++;
|
|
||||||
getJSON(this.saveUrl, this.savePayload, this.saveMethod)
|
|
||||||
.then(data => {
|
|
||||||
this.onSuccess(data);
|
|
||||||
})
|
|
||||||
.catch(xhr => {
|
|
||||||
this.onError(xhr);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onSuccess(data) {
|
|
||||||
if (data && data.type_de_champ) {
|
|
||||||
this.id = data.type_de_champ.id;
|
|
||||||
this.pieceJustificativeTemplateUrl =
|
|
||||||
data.type_de_champ.piece_justificative_template_url;
|
|
||||||
this.pieceJustificativeTemplateFilename =
|
|
||||||
data.type_de_champ.piece_justificative_template_filename;
|
|
||||||
this.pieceJustificativeTemplate = null;
|
|
||||||
}
|
|
||||||
this.state.inFlight--;
|
|
||||||
this.isSaving = false;
|
|
||||||
this.hasChanges = false;
|
|
||||||
|
|
||||||
if (this.state.inFlight === 0) {
|
|
||||||
this.state.flash.success();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onError(xhr) {
|
|
||||||
this.isSaving = false;
|
|
||||||
this.state.inFlight--;
|
|
||||||
try {
|
|
||||||
const {
|
|
||||||
errors: [message]
|
|
||||||
} = JSON.parse(xhr.responseText);
|
|
||||||
this.state.flash.error(message);
|
|
||||||
} catch (e) {
|
|
||||||
this.state.flash.error(xhr.responseText);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const EXCLUDE_FROM_REPETITION = [
|
|
||||||
'carte',
|
|
||||||
'dossier_link',
|
|
||||||
'repetition',
|
|
||||||
'siret'
|
|
||||||
];
|
|
||||||
|
|
||||||
function castBoolean(value) {
|
|
||||||
return value && value != 0;
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="deleted" v-if="deleted">
|
|
||||||
<input type="hidden" :name="nameFor('id')" :value="id">
|
|
||||||
<input type="hidden" :name="nameFor('_destroy')" value="true">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="draggable-item flex column justify-start" v-else :class="itemClassName">
|
|
||||||
<div class="flex justify-start section head" :class="{ hr: !isHeaderSection }">
|
|
||||||
<div class="handle">
|
|
||||||
<img :src="state.dragIconUrl" alt="">
|
|
||||||
</div>
|
|
||||||
<div class="cell">
|
|
||||||
<select
|
|
||||||
:id="elementIdFor('type_champ')"
|
|
||||||
:name="nameFor('type_champ')"
|
|
||||||
v-model="typeChamp"
|
|
||||||
@change="update"
|
|
||||||
class="small-margin small inline">
|
|
||||||
<option v-for="option in state.typesDeChampOptions" :key="option[1]" :value="option[1]">
|
|
||||||
{{ option[0] }}
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-start delete">
|
|
||||||
<button class="button danger" @click.prevent="removeChamp">
|
|
||||||
Supprimer
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-start section" :class="{ hr: isDropDown || isFile || isCarte }">
|
|
||||||
<div class="flex column justify-start shift-left">
|
|
||||||
<div class="cell libelle">
|
|
||||||
<label :for="elementIdFor('libelle')">
|
|
||||||
Libellé
|
|
||||||
</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
:id="elementIdFor('libelle')"
|
|
||||||
:name="nameFor('libelle')"
|
|
||||||
v-model="libelle"
|
|
||||||
@change="update"
|
|
||||||
class="small-margin small"
|
|
||||||
:class="{ error: hasChanges && !isValid }">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="cell" v-show="!isHeaderSection && !isExplication && !state.isAnnotation">
|
|
||||||
<label :for="elementIdFor('mandatory')">
|
|
||||||
Obligatoire
|
|
||||||
</label>
|
|
||||||
<input :name="nameFor('mandatory')" type="hidden" value="0">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:id="elementIdFor('mandatory')"
|
|
||||||
:name="nameFor('mandatory')"
|
|
||||||
v-model="mandatory"
|
|
||||||
@change="update"
|
|
||||||
class="small-margin small"
|
|
||||||
value="1">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-start">
|
|
||||||
<div class="cell" v-show="!isHeaderSection">
|
|
||||||
<label :for="elementIdFor('description')">
|
|
||||||
Description
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
:id="elementIdFor('description')"
|
|
||||||
:name="nameFor('description')"
|
|
||||||
v-model="description"
|
|
||||||
@change="update"
|
|
||||||
rows=3
|
|
||||||
cols=40
|
|
||||||
class="small-margin small">
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-start section shift-left" v-show="!isHeaderSection">
|
|
||||||
<div class="cell" v-show="isDropDown">
|
|
||||||
<label :for="elementIdFor('drop_down_list')">
|
|
||||||
Liste déroulante
|
|
||||||
</label>
|
|
||||||
<textarea
|
|
||||||
:id="elementIdFor('drop_down_list')"
|
|
||||||
:name="nameFor('drop_down_list_attributes[value]')"
|
|
||||||
v-model="dropDownListValue"
|
|
||||||
@change="update"
|
|
||||||
rows=3
|
|
||||||
cols=40
|
|
||||||
placeholder="Ecrire une valeur par ligne et --valeur-- pour un séparateur."
|
|
||||||
class="small-margin small">
|
|
||||||
</textarea>
|
|
||||||
</div>
|
|
||||||
<div class="cell" v-show="isFile">
|
|
||||||
<label :for="elementIdFor('piece_justificative_template')">
|
|
||||||
Modèle
|
|
||||||
</label>
|
|
||||||
<template v-if="pieceJustificativeTemplateUrl">
|
|
||||||
<a :href="pieceJustificativeTemplateUrl" rel="noopener" target="_blank">
|
|
||||||
{{pieceJustificativeTemplateFilename}}
|
|
||||||
</a>
|
|
||||||
<br> Modifier :
|
|
||||||
</template>
|
|
||||||
<input
|
|
||||||
type="file"
|
|
||||||
:id="elementIdFor('piece_justificative_template')"
|
|
||||||
:name="nameFor('piece_justificative_template')"
|
|
||||||
@change="upload"
|
|
||||||
class="small-margin small">
|
|
||||||
</div>
|
|
||||||
<div class="cell" v-show="isCarte">
|
|
||||||
<label>
|
|
||||||
Utilisation de la cartographie
|
|
||||||
</label>
|
|
||||||
<div class="carte-options">
|
|
||||||
<label :for="elementIdFor('quartiers_prioritaires')">
|
|
||||||
<input :name="nameFor('quartiers_prioritaires')" type="hidden" value="0">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:id="elementIdFor('quartiers_prioritaires')"
|
|
||||||
:name="nameFor('quartiers_prioritaires')"
|
|
||||||
v-model="options.quartiers_prioritaires"
|
|
||||||
@change="update"
|
|
||||||
class="small-margin small"
|
|
||||||
value="1">
|
|
||||||
Quartiers prioritaires
|
|
||||||
</label>
|
|
||||||
<label :for="elementIdFor('cadastres')">
|
|
||||||
<input :name="nameFor('cadastres')" type="hidden" value="0">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:id="elementIdFor('cadastres')"
|
|
||||||
:name="nameFor('cadastres')"
|
|
||||||
v-model="options.cadastres"
|
|
||||||
@change="update"
|
|
||||||
class="small-margin small"
|
|
||||||
value="1">
|
|
||||||
Cadastres
|
|
||||||
</label>
|
|
||||||
<label :for="elementIdFor('parcelles_agricoles')">
|
|
||||||
<input :name="nameFor('parcelles_agricoles')" type="hidden" value="0">
|
|
||||||
<input
|
|
||||||
type="checkbox"
|
|
||||||
:id="elementIdFor('parcelles_agricoles')"
|
|
||||||
:name="nameFor('parcelles_agricoles')"
|
|
||||||
v-model="options.parcelles_agricoles"
|
|
||||||
@change="update"
|
|
||||||
class="small-margin small"
|
|
||||||
value="1">
|
|
||||||
Parcelles Agricoles
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow cell" v-show="isRepetition">
|
|
||||||
<Draggable :list="typesDeChamp" :options="{handle:'.handle'}">
|
|
||||||
<DraggableItem
|
|
||||||
v-for="(item, index) in typesDeChamp"
|
|
||||||
:state="stateForRepetition"
|
|
||||||
:index="index"
|
|
||||||
:item="item"
|
|
||||||
:key="item.id" />
|
|
||||||
</Draggable>
|
|
||||||
|
|
||||||
<button class="button" @click.prevent="addChamp">
|
|
||||||
<template v-if="state.isAnnotation">
|
|
||||||
Ajouter une annotation
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
Ajouter un champ
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="meta">
|
|
||||||
<input type="hidden" :name="nameFor('order_place')" :value="index">
|
|
||||||
<input type="hidden" :name="nameFor('id')" :value="id">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./DraggableItem.js"></script>
|
|
|
@ -1,14 +0,0 @@
|
||||||
export default {
|
|
||||||
props: ['state', 'version'],
|
|
||||||
methods: {
|
|
||||||
addChamp() {
|
|
||||||
this.state.typesDeChamp.push({
|
|
||||||
type_champ: 'text',
|
|
||||||
types_de_champ: []
|
|
||||||
});
|
|
||||||
},
|
|
||||||
save() {
|
|
||||||
this.state.flash.success();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
|
@ -1,28 +0,0 @@
|
||||||
<template>
|
|
||||||
<div class="champs-editor">
|
|
||||||
<Draggable :list="state.typesDeChamp" :options="{handle:'.handle'}">
|
|
||||||
<DraggableItem
|
|
||||||
v-for="(item, index) in state.typesDeChamp"
|
|
||||||
:state="state"
|
|
||||||
:index="index"
|
|
||||||
:item="item"
|
|
||||||
:key="item.id" />
|
|
||||||
</Draggable>
|
|
||||||
|
|
||||||
<div class="footer"></div>
|
|
||||||
<div class="buttons">
|
|
||||||
<button class="button" v-scroll-to="'.footer'" @click.prevent="addChamp">
|
|
||||||
<template v-if="state.isAnnotation">
|
|
||||||
Ajouter une annotation
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
Ajouter un champ
|
|
||||||
</template>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<button class="button primary" @click.prevent="save">Enregistrer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script src="./DraggableList.js"></script>
|
|
|
@ -1,90 +0,0 @@
|
||||||
import Vue from 'vue';
|
|
||||||
import Draggable from 'vuedraggable';
|
|
||||||
import VueScrollTo from 'vue-scrollto';
|
|
||||||
|
|
||||||
import DraggableItem from './DraggableItem';
|
|
||||||
import DraggableList from './DraggableList';
|
|
||||||
|
|
||||||
Vue.component('Draggable', Draggable);
|
|
||||||
Vue.component('DraggableItem', DraggableItem);
|
|
||||||
Vue.use(VueScrollTo, { duration: 1500, easing: 'ease' });
|
|
||||||
|
|
||||||
addEventListener('DOMContentLoaded', () => {
|
|
||||||
const el = document.querySelector('#champs-editor');
|
|
||||||
if (el) {
|
|
||||||
initEditor(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function initEditor(el) {
|
|
||||||
const { directUploadUrl, dragIconUrl, saveUrl } = el.dataset;
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
typesDeChamp: JSON.parse(el.dataset.typesDeChamp),
|
|
||||||
typesDeChampOptions: JSON.parse(el.dataset.typesDeChampOptions),
|
|
||||||
directUploadUrl,
|
|
||||||
dragIconUrl,
|
|
||||||
saveUrl,
|
|
||||||
isAnnotation: el.dataset.type === 'annotation',
|
|
||||||
prefix: 'procedure',
|
|
||||||
inFlight: 0,
|
|
||||||
flash: new Flash()
|
|
||||||
};
|
|
||||||
|
|
||||||
// We add an initial type de champ here if form is empty
|
|
||||||
if (state.typesDeChamp.length === 0) {
|
|
||||||
state.typesDeChamp.push({
|
|
||||||
type_champ: 'text',
|
|
||||||
types_de_champ: []
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
new Vue({
|
|
||||||
el,
|
|
||||||
data: {
|
|
||||||
state
|
|
||||||
},
|
|
||||||
render(h) {
|
|
||||||
return h(DraggableList, {
|
|
||||||
props: {
|
|
||||||
state: this.state
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
class Flash {
|
|
||||||
constructor(isAnnotation) {
|
|
||||||
this.element = document.querySelector('#flash_messages');
|
|
||||||
this.isAnnotation = isAnnotation;
|
|
||||||
}
|
|
||||||
success() {
|
|
||||||
if (this.isAnnotation) {
|
|
||||||
this.add('Annotations privées enregistrées.');
|
|
||||||
} else {
|
|
||||||
this.add('Formulaire enregistré.');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
error(message) {
|
|
||||||
this.add(message, true);
|
|
||||||
}
|
|
||||||
clear() {
|
|
||||||
this.element.innerHTML = '';
|
|
||||||
}
|
|
||||||
add(message, isError) {
|
|
||||||
const html = `<div id="flash_message" class="center">
|
|
||||||
<div class="alert alert-fixed ${
|
|
||||||
isError ? 'alert-danger' : 'alert-success'
|
|
||||||
}">
|
|
||||||
${message}
|
|
||||||
</div>
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
this.element.innerHTML = html;
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
this.clear();
|
|
||||||
}, 6000);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Add table
Reference in a new issue