Merge pull request #2993 from tchak/new-champs-editor

New champs editor
This commit is contained in:
Pierre de La Morinerie 2019-01-17 11:57:58 +01:00 committed by GitHub
commit a92a07c018
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 1122 additions and 34 deletions

View file

@ -18,7 +18,7 @@ module.exports = {
},
overrides: [
{
files: ['config/webpack/*.js'],
files: ['config/webpack/**/*.js'],
env: {
node: true
}

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path d="M20 9H4v2h16V9zM4 15h16v-2H4v2z"/></svg>

After

Width:  |  Height:  |  Size: 132 B

View file

@ -11,6 +11,7 @@
a {
color: $black;
text-decoration: underline;
}
&::after {

View file

@ -107,6 +107,12 @@
}
}
input[type=checkbox] {
&.small-margin {
margin-bottom: $default-padding / 2;
}
}
input[type=text]:not([data-address='true']),
input[type=email],
input[type=password],

View file

@ -20,3 +20,12 @@
border-color: $light-green;
color: $dark-green;
}
.alert-fixed {
position: fixed;
left: 50%;
margin-left: -100px;
width: 200px;
top: 10px;
border-radius: 10px;
}

View file

@ -0,0 +1,137 @@
@import "colors";
#champs-editor {
.spinner {
margin-right: auto;
margin-left: auto;
margin-top: 80px;
}
}
.draggable-item {
display: flex;
flex-direction: column;
justify-content: flex-start;
border: 1px solid $border-grey;
border-radius: 5px;
margin-bottom: 10px;
width: 100%;
.handle {
cursor: ns-resize;
margin-right: 10px;
margin-top: 8px;
}
.error-message {
text-align: center;
flex-grow: 1;
font-size: 14px;
color: $light-grey;
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-around;
.content {
background-color: $medium-red;
border-radius: 8px;
padding: 4px 10px;
}
}
&.type-header-section {
background-color: $blue;
label {
color: $light-grey;
}
}
&:not(.type-header-section) {
input.error {
border: 1px solid $medium-red;
}
}
.column {
display: flex;
justify-content: flex-start;
flex-direction: column;
&.shift-left {
margin-left: 35px;
}
}
.row {
display: flex;
justify-content: flex-start;
&.section {
padding: 10px 10px 0 10px;
margin-bottom: 8px;
}
&.hr {
border-bottom: 1px solid $border-grey;
&.head {
padding-bottom: 10px;
}
}
&.shift-left {
margin-left: 35px;
}
&.head {
select {
margin-bottom: 0px;
}
}
&.delete {
flex-grow: 1;
display: flex;
justify-content: flex-end;
}
}
.cell {
margin-right: 20px;
&.small {
width: 90px;
}
&.libelle {
width: 300px;
}
label {
margin-bottom: 8px;
text-transform: uppercase;
font-size: 12px;
}
}
.carte-options {
label {
font-weight: initial;
}
}
.inline {
display: inline;
}
}
.header,
.footer {
display: flex;
justify-content: space-between;
margin-top: 30px;
margin-bottom: 30px;
}

View file

@ -79,7 +79,12 @@ class Admin::ProceduresController < AdminController
if gestionnaire
gestionnaire.assign_to_procedure(@procedure)
end
redirect_to admin_procedure_types_de_champ_path(procedure_id: @procedure.id)
if Flipflop.new_champs_editor?
redirect_to champs_procedure_path(@procedure)
else
redirect_to admin_procedure_types_de_champ_path(procedure_id: @procedure.id)
end
end
end

View file

@ -1,5 +1,28 @@
module NewAdministrateur
class AdministrateurController < ApplicationController
before_action :authenticate_administrateur!
def retrieve_procedure
id = params[:procedure_id] || params[:id]
@procedure = current_administrateur.procedures.find(id)
rescue ActiveRecord::RecordNotFound
flash.alert = 'Démarche inexistante'
redirect_to admin_procedures_path
end
def procedure_locked?
if @procedure.locked?
flash.alert = 'Démarche verrouillée'
redirect_to admin_procedure_path(@procedure)
end
end
def reset_procedure
if @procedure.brouillon_avec_lien?
@procedure.reset!
end
end
end
end

View file

@ -1,10 +1,44 @@
module NewAdministrateur
class ProceduresController < AdministrateurController
before_action :retrieve_procedure, only: [:champs, :annotations, :update]
before_action :procedure_locked?, only: [:champs, :annotations, :update]
TYPE_DE_CHAMP_ATTRIBUTES = [
:_destroy,
:libelle,
:description,
:order_place,
:type_champ,
:id,
:mandatory,
:piece_justificative_template,
:quartiers_prioritaires,
:cadastres,
:parcelles_agricoles,
drop_down_list_attributes: [:value]
]
def apercu
@dossier = procedure_without_control.new_dossier
@tab = apercu_tab
end
def update
if @procedure.update(procedure_params)
flash.now.notice = if params[:procedure][:types_de_champ_attributes].present?
'Champs enregistrés'
elsif params[:procedure][:types_de_champ_private_attributes].present?
'Annotations enregistrés'
else
'Démarche enregistrée.'
end
reset_procedure
else
flash.now.alert = @procedure.errors.full_messages
end
end
private
def apercu_tab
@ -14,5 +48,12 @@ module NewAdministrateur
def procedure_without_control
Procedure.find(params[:id])
end
def procedure_params
params.required(:procedure).permit(
types_de_champ_attributes: TYPE_DE_CHAMP_ATTRIBUTES,
types_de_champ_private_attributes: TYPE_DE_CHAMP_ATTRIBUTES
)
end
end
end

View file

@ -7,11 +7,20 @@ module ApplicationHelper
end
end
def flash_class(level, sticky = false)
case level
when "notice" then "alert-success#{sticky ? ' sticky' : ''}"
when "alert" then "alert-danger#{sticky ? ' sticky' : ''}"
def flash_class(level, sticky: false, fixed: false)
class_names = case level
when 'notice'
['alert-success']
when 'alert'
['alert-danger']
end
if sticky
class_names << 'sticky'
end
if fixed
class_names << 'alert-fixed'
end
class_names.join(' ')
end
def render_to_element(selector, partial:, outer: false, locals: {})
@ -22,9 +31,9 @@ module ApplicationHelper
# rubocop:enable Rails/OutputSafety
end
def render_flash(timeout: false, sticky: false)
def render_flash(timeout: false, sticky: false, fixed: false)
if flash.any?
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky }, outer: true)
html = render_to_element('#flash_messages', partial: 'layouts/flash_messages', locals: { sticky: sticky, fixed: fixed }, outer: true)
flash.clear
if timeout
html += remove_element('#flash_messages', timeout: timeout, inner: true)
@ -56,6 +65,12 @@ module ApplicationHelper
# rubocop:enable Rails/OutputSafety
end
def fire_event(event_name, data)
# rubocop:disable Rails/OutputSafety
raw("DS.fire('#{event_name}', #{raw(data)});")
# rubocop:enable Rails/OutputSafety
end
def current_email
current_user&.email ||
current_gestionnaire&.email ||

View file

@ -33,4 +33,56 @@ module ProcedureHelper
end
end
end
def types_de_champ_data(procedure)
{
type: "champ",
types_de_champ_options: types_de_champ_options.to_json,
types_de_champ: types_de_champ_as_json(procedure.types_de_champ).to_json,
direct_uploads_url: rails_direct_uploads_url,
drag_icon_url: image_url("icons/drag.svg")
}
end
def types_de_champ_private_data(procedure)
{
type: "annotation",
types_de_champ_options: types_de_champ_options.to_json,
types_de_champ: types_de_champ_as_json(procedure.types_de_champ_private).to_json,
direct_uploads_url: rails_direct_uploads_url,
drag_icon_url: image_url("icons/drag.svg")
}
end
def procedure_data(procedure)
{
types_de_champ: types_de_champ_as_json(procedure.types_de_champ),
types_de_champ_private: types_de_champ_as_json(procedure.types_de_champ_private)
}.to_json
end
private
TOGGLES = {
TypeDeChamp.type_champs.fetch(:siret) => :champ_siret?,
TypeDeChamp.type_champs.fetch(:integer_number) => :champ_integer_number?,
TypeDeChamp.type_champs.fetch(:repetition) => :champ_repetition?
}
def types_de_champ_options
types_de_champ = TypeDeChamp.type_de_champs_list_fr
types_de_champ.select! do |tdc|
toggle = TOGGLES[tdc.last]
toggle.blank? || Flipflop.send(toggle)
end
types_de_champ
end
def types_de_champ_as_json(types_de_champ)
types_de_champ.as_json(except: [:created_at, :updated_at],
methods: [:piece_justificative_template_filename, :piece_justificative_template_url],
include: { drop_down_list: { only: :value } })
end
end

View file

@ -0,0 +1,131 @@
export default {
props: ['state', 'update', 'index', 'item', 'prefix'],
computed: {
isDirty() {
return (
this.state.version &&
this.state.unsavedInvalidItems.size > 0 &&
this.state.unsavedItems.has(this.itemId)
);
},
isInvalid() {
if (this.deleted) {
return false;
}
if (this.libelle) {
return !this.libelle.trim();
}
return true;
},
itemId() {
return this.item.id || this.clientId;
},
changeLog() {
return [this.itemId, !this.isInvalid];
},
itemClassName() {
const classNames = [`draggable-item-${this.index}`];
if (this.isHeaderSection) {
classNames.push('type-header-section');
}
if (this.isDirty) {
if (this.isInvalid) {
classNames.push('invalid');
} else {
classNames.push('dirty');
}
}
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';
},
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';
}
}
},
data() {
return {
typeChamp: this.item.type_champ,
libelle: this.item.libelle,
mandatory: this.item.mandatory,
description: this.item.description,
dropDownList: this.item.drop_down_list.value,
deleted: false,
clientId: `id-${clientIds++}`
};
},
created() {
for (let path of PATHS_TO_WATCH) {
this.$watch(path, () => this.update(this.changeLog));
}
},
mounted() {
if (this.isInvalid) {
this.update(this.changeLog, false);
}
},
methods: {
removeChamp(item) {
if (item.id) {
this.deleted = true;
} else {
const index = this.state.typesDeChamp.indexOf(item);
this.state.typesDeChamp.splice(index, 1);
this.update([this.itemId, true]);
}
},
nameFor(name) {
return `${this.prefix}[${this.attribute}][${this.index}][${name}]`;
},
elementIdFor(name) {
return `${this.prefix}_${this.attribute}_${this.index}_${name}`;
}
}
};
const PATHS_TO_WATCH = [
'typeChamp',
'libelle',
'mandatory',
'description',
'dropDownList',
'options.quartiers_prioritaires',
'options.cadastres',
'options.parcelles_agricoles',
'index',
'deleted'
];
function castBoolean(value) {
return value && value != 0;
}
let clientIds = 0;

View file

@ -0,0 +1,165 @@
<template>
<div class="deleted" v-if="deleted">
<input type="hidden" :name="nameFor('id')" :value="item.id">
<input type="hidden" :name="nameFor('_destroy')" value="true">
</div>
<div class="draggable-item" v-else :class="itemClassName">
<div class="row section head" :class="{ hr: !isHeaderSection }">
<div class="handle">
<img :src="state.dragIconUrl" alt="">
</div>
<div class="cell">
<select :name="nameFor('type_champ')" v-model="typeChamp" 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="row delete">
<div v-if="isDirty" class="error-message">
<span v-if="isInvalid" class="content">
Le libellé doit être rempli.
</span>
<span v-else class="content">
<template v-if="state.isAnnotation">
Modifications non sauvegardées. Le libellé doit être rempli sur tous les annotations.
</template>
<template v-else>
Modifications non sauvegardées. Le libellé doit être rempli sur tous les champs.
</template>
</span>
</div>
<button class="button danger" @click.prevent="removeChamp(item)">
Supprimer
</button>
</div>
</div>
<div class="row section" :class="{ hr: isDropDown || isFile || isCarte }">
<div class="column shift-left">
<div class="cell libelle">
<label :for="elementIdFor('libelle')">
Libellé
</label>
<input
type="text"
:id="elementIdFor('libelle')"
:name="nameFor('libelle')"
v-model="libelle"
class="small-margin small"
:class="{ error: isDirty && isInvalid }">
</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"
class="small-margin small"
value="1">
</div>
</div>
<div class="row">
<div class="cell" v-show="!isHeaderSection">
<label :for="elementIdFor('description')">
Description
</label>
<textarea
:id="elementIdFor('description')"
:name="nameFor('description')"
v-model="description"
rows=3
cols=40
class="small-margin small">
</textarea>
</div>
</div>
</div>
<div class="row 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="dropDownList"
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="item.piece_justificative_template_url">
<a :href="item.piece_justificative_template_url" target="_blank">
{{item.piece_justificative_template_filename}}
</a>
<br> Modifier :
</template>
<input
type="file"
:id="elementIdFor('piece_justificative_template')"
:name="nameFor('piece_justificative_template')"
:data-direct-upload-url="state.directUploadsUrl"
@change="update(changeLog)"
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"
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"
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"
class="small-margin small"
value="1">
Parcelles Agricoles
</label>
</div>
</div>
</div>
<div class="meta">
<input type="hidden" :name="nameFor('order_place')" :value="index">
<input type="hidden" :name="nameFor('id')" :value="item.id">
</div>
</div>
</template>
<script src="./DraggableItem.js"></script>

View file

@ -0,0 +1,12 @@
export default {
props: ['state', 'version', 'update', 'updateAll'],
methods: {
addChamp() {
this.state.typesDeChamp.push({
type_champ: 'text',
drop_down_list: {},
options: {}
});
}
}
};

View file

@ -0,0 +1,42 @@
<template>
<div class="champs-editor">
<div v-if="state.typesDeChamp.length > 3" class="header">
<button class="button" @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="updateAll">Enregistrer</button>
</div>
<Draggable :list="state.typesDeChamp" :options="{handle:'.handle'}">
<DraggableItem
v-for="(item, index) in state.typesDeChamp"
prefix="procedure"
:state="state"
:update="update"
:index="index"
:item="item"
:key="item.id" />
</Draggable>
<div class="footer">
<button class="button" @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="updateAll">Enregistrer</button>
</div>
</div>
</template>
<script src="./DraggableList.js"></script>

View file

@ -0,0 +1,109 @@
import Vue from 'vue';
import Draggable from 'vuedraggable';
import { fire, debounce } from '@utils';
import DraggableItem from './DraggableItem';
import DraggableList from './DraggableList';
Vue.component('Draggable', Draggable);
Vue.component('DraggableItem', DraggableItem);
addEventListener('DOMContentLoaded', () => {
const el = document.querySelector('#champs-editor');
const { directUploadsUrl, dragIconUrl } = el.dataset;
const state = {
typesDeChamp: JSON.parse(el.dataset.typesDeChamp),
typesDeChampOptions: JSON.parse(el.dataset.typesDeChampOptions),
directUploadsUrl,
dragIconUrl,
isAnnotation: el.dataset.type === 'annotation',
unsavedItems: new Set(),
unsavedInvalidItems: new Set(),
version: 1
};
new Vue({
el,
data: {
state,
update: null
},
render(h) {
return h(DraggableList, {
props: {
state: this.state,
update: this.update,
updateAll: this.updateAll
}
});
},
mounted() {
const [update, updateAll] = createUpdateFunctions(
this,
state.isAnnotation
);
this.update = update;
this.updateAll = updateAll;
}
});
});
function createUpdateFunctions(app, isAnnotation) {
let isSaving = false;
const form = app.$el.closest('form');
const update = ([id, isValid], refresh = true) => {
app.state.unsavedItems.add(id);
if (isValid) {
app.state.unsavedInvalidItems.delete(id);
} else {
app.state.unsavedInvalidItems.add(id);
}
if (refresh) {
app.state.version += 1;
}
updateAll();
};
const updateAll = debounce(() => {
if (isSaving) {
updateAll();
} else if (
app.state.typesDeChamp.length > 0 &&
app.state.unsavedInvalidItems.size === 0
) {
isSaving = true;
app.state.unsavedItems.clear();
app.state.version += 1;
fire(form, 'submit');
}
}, 500);
addEventListener('ProcedureUpdated', event => {
const { types_de_champ, types_de_champ_private } = event.detail;
app.state.typesDeChamp = isAnnotation
? types_de_champ_private
: types_de_champ;
isSaving = false;
updateFileInputs();
});
return [update, updateAll];
}
// This is needed du to the way ActiveStorage javascript integration works.
// It is built to be used with traditional forms. Another way would be to not use
// high level ActiveStorage abstractions (and maybe this is what we should do in the future).
function updateFileInputs() {
for (let element of document.querySelectorAll('.direct-upload')) {
let hiddenInput = element.nextElementSibling;
let fileInput = hiddenInput.nextElementSibling;
element.remove();
hiddenInput.remove();
fileInput.value = '';
fileInput.removeAttribute('disabled');
}
}

View file

@ -22,6 +22,8 @@ import '../new_design/select2';
import '../new_design/champs/carte';
import '../new_design/champs/linked-drop-down-list';
import '../new_design/administrateur/champs-editor';
import { toggleCondidentielExplanation } from '../new_design/avis';
import { scrollMessagerie } from '../new_design/messagerie';
import { showMotivation, motivationCancel } from '../new_design/state-button';
@ -30,6 +32,7 @@ import { replaceSemicolonByComma } from '../new_design/avis';
// This is the global application namespace where we expose helpers used from rails views
const DS = {
fire: (eventName, data) => Rails.fire(document, eventName, data),
toggleCondidentielExplanation,
scrollMessagerie,
showMotivation,

View file

@ -1,6 +1,8 @@
class DropDownList < ApplicationRecord
belongs_to :type_de_champ
before_validation :clean_value
def options
result = value.split(/[\r\n]|[\r]|[\n]|[\n\r]/).reject(&:empty?)
result.blank? ? [] : [''] + result
@ -13,4 +15,12 @@ class DropDownList < ApplicationRecord
def multiple
type_de_champ.type_champ == TypeDeChamp.type_champs.fetch(:multiple_drop_down_list)
end
private
def clean_value
value = read_attribute(:value)
value = value ? value.split("\r\n").map(&:strip).join("\r\n") : ''
write_attribute(:value, value)
end
end

View file

@ -30,9 +30,9 @@ class Procedure < ApplicationRecord
has_one_attached :notice
has_one_attached :deliberation
accepts_nested_attributes_for :types_de_champ, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
accepts_nested_attributes_for :types_de_piece_justificative, :reject_if => proc { |attributes| attributes['libelle'].blank? }, :allow_destroy => true
accepts_nested_attributes_for :types_de_champ_private
accepts_nested_attributes_for :types_de_champ, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
accepts_nested_attributes_for :types_de_champ_private, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
accepts_nested_attributes_for :types_de_piece_justificative, reject_if: proc { |attributes| attributes['libelle'].blank? }, allow_destroy: true
mount_uploader :logo, ProcedureLogoUploader

View file

@ -83,7 +83,7 @@ class TypeDeChamp < ApplicationRecord
has_one_attached :piece_justificative_template
accepts_nested_attributes_for :drop_down_list
accepts_nested_attributes_for :drop_down_list, update_only: true
accepts_nested_attributes_for :types_de_champ, allow_destroy: true
validates :libelle, presence: true, allow_blank: false, allow_nil: false
@ -161,6 +161,18 @@ class TypeDeChamp < ApplicationRecord
"TypesDeChamp::#{type_champ.classify}TypeDeChamp"
end
def piece_justificative_template_url
if piece_justificative_template.attached?
Rails.application.routes.url_helpers.url_for(piece_justificative_template)
end
end
def piece_justificative_template_filename
if piece_justificative_template.attached?
piece_justificative_template.filename
end
end
private
def setup_procedure

View file

@ -1,9 +1,10 @@
- champs_editor_path = Flipflop.new_champs_editor? ? champs_procedure_path(@procedure) : admin_procedure_types_de_champ_path(@procedure)
.row.white-back
.alert.alert-info
.form-group
%p
Pour vos nouveaux besoins de pièces jointes, nous vous invitons à
= link_to(admin_procedure_types_de_champ_path(@procedure)) do
= link_to(champs_editor_path) do
rajouter des champs
\ <em>pièce justificative</em> à votre formulaire.
@ -17,7 +18,7 @@
%li Support des pièces de grande taille (jusquà 200 Mo par pièce)
%li Pas de limite de soumission simultanée de plusieurs pièces, pour une expérience usager plus confortable
= link_to(admin_procedure_types_de_champ_path(@procedure), class: 'btn btn-success') do
= link_to(champs_editor_path, class: 'btn btn-success') do
Ajouter un champ PJ
- if @procedure.has_old_pjs?

View file

@ -3,11 +3,12 @@
#flash_message.center
- flash.each do |key, value|
- sticky = defined?(sticky) ? sticky : false
- fixed = defined?(fixed) ? fixed : false
- if value.class == Array
.alert{ class: flash_class(key, sticky) }
.alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
- value.each do |message|
= sanitize(message)
%br
- else
.alert{ class: flash_class(key, sticky) }
.alert{ class: flash_class(key, sticky: sticky, fixed: fixed) }
= sanitize(value)

View file

@ -31,7 +31,8 @@
%p.missing-steps (à compléter)
- if !@procedure.locked?
%a#onglet-champs{ href: url_for(admin_procedure_types_de_champ_path(@procedure)) }
- champs_editor_path = Flipflop.new_champs_editor? ? champs_procedure_path(@procedure) : admin_procedure_types_de_champ_path(@procedure)
%a#onglet-champs{ href: champs_editor_path }
.procedure-list-element{ class: ('active' if active == 'Champs') }
Champs
@ -41,7 +42,8 @@
Pièces jointes
- if !@procedure.locked?
%a#onglet-private-champs{ href: url_for(admin_procedure_types_de_champ_private_path(@procedure)) }
- annotations_editor_path = Flipflop.new_champs_editor? ? annotations_procedure_path(@procedure) : admin_procedure_types_de_champ_private_path(@procedure)
%a#onglet-private-champs{ href: annotations_editor_path }
.procedure-list-element{ class: ('active' if active == 'Annotations privées') }
Annotations privées

View file

@ -1,5 +1,7 @@
.sub-header
.container
.container.flex.justify-between.align-baseline
%ul.breadcrumbs
- steps.each do |step|
%li= step
- if defined?(preview) && preview
= link_to "Prévisualiser le formulaire", apercu_procedure_path(@procedure), target: "_blank", class: 'button'

View file

@ -0,0 +1,12 @@
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
'Configuration des annotations privées'], preview: true }
.container
%h1 Configuration des annotations privées
%br
= form_for @procedure, remote: true, html: { class: 'form' } do |form|
#champs-editor{ data: types_de_champ_private_data(@procedure) }
.spinner

View file

@ -0,0 +1,12 @@
= render partial: 'new_administrateur/breadcrumbs',
locals: { steps: [link_to('Démarches', admin_procedures_path),
link_to(@procedure.libelle, admin_procedure_path(@procedure)),
'Configuration des champs'], preview: true }
.container
%h1 Configuration des champs
%br
= form_for @procedure, remote: true, html: { class: 'form' } do |form|
#champs-editor{ data: types_de_champ_data(@procedure) }
.spinner

View file

@ -0,0 +1,3 @@
<%= render_flash timeout: 6000, fixed: true %>
<%= fire_event(:ProcedureUpdated, procedure_data(@procedure)) %>

View file

@ -19,6 +19,7 @@ Flipflop.configure do
feature :publish_draft
feature :support_form
feature :enable_email_login_token
feature :new_champs_editor
group :production do
feature :remote_storage,

View file

@ -357,9 +357,11 @@ Rails.application.routes.draw do
#
scope module: 'new_administrateur' do
resources :procedures, only: [] do
resources :procedures, only: [:update] do
member do
get 'apercu'
get 'champs'
get 'annotations'
end
end

View file

@ -1,5 +1,7 @@
const path = require('path');
const { environment } = require('@rails/webpacker');
const { VueLoaderPlugin } = require('vue-loader');
const vue = require('./loaders/vue');
const resolve = {
alias: {
@ -9,4 +11,6 @@ const resolve = {
environment.config.merge({ resolve });
environment.plugins.append('VueLoaderPlugin', new VueLoaderPlugin());
environment.loaders.append('vue', vue);
module.exports = environment;

View file

@ -0,0 +1,8 @@
module.exports = {
test: /\.vue(\.erb)?$/,
use: [
{
loader: 'vue-loader'
}
]
};

View file

@ -14,6 +14,7 @@ default: &default
cache_manifest: false
extensions:
- .vue
- .js
- .sass
- .scss

View file

@ -15,7 +15,11 @@
"rails-ujs": "^5.2.1",
"ramda": "^0.25.0",
"select2": "^4.0.6-rc.1",
"turbolinks": "^5.2.0"
"turbolinks": "^5.2.0",
"vue": "^2.5.21",
"vue-loader": "^15.5.1",
"vue-template-compiler": "^2.5.21",
"vuedraggable": "^2.16.0"
},
"devDependencies": {
"eclint": "^2.8.0",

View file

@ -182,6 +182,7 @@ describe Admin::ProceduresController, type: :controller do
context 'when procedure is correctly save' do
before do
Flipflop::FeatureSet.current.test!.switch!(:new_champs_editor, true)
post :create, params: { procedure: procedure_params }
end
@ -197,8 +198,7 @@ describe Admin::ProceduresController, type: :controller do
it { expect(subject.duree_conservation_dossiers_hors_ds).to eq(duree_conservation_dossiers_hors_ds) }
end
it { is_expected.to redirect_to(admin_procedure_types_de_champ_path(procedure_id: Procedure.last.id)) }
it { is_expected.to redirect_to(champs_procedure_path(Procedure.last)) }
it { expect(flash[:notice]).to be_present }
end

View file

@ -5,9 +5,11 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
include ProcedureSpecHelper
let(:administrateur) { create(:administrateur, :with_procedure) }
let(:test_strategy) { Flipflop::FeatureSet.current.test! }
before do
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, true)
test_strategy.switch!(:publish_draft, true)
test_strategy.switch!(:new_champs_editor, true)
login_as administrateur, scope: :administrateur
visit root_path
end
@ -45,13 +47,13 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
fill_in_dummy_procedure_details
click_on 'save-procedure'
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
end
end
context "when publish_draft disabled" do
before do
Flipflop::FeatureSet.current.test!.switch!(:publish_draft, false)
test_strategy.switch!(:publish_draft, false)
end
scenario 'Finding save button for new procedure, libelle, description and cadre_juridique required' do
@ -68,7 +70,7 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
fill_in_dummy_procedure_details(fill_path: false)
click_on 'save-procedure'
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
end
end
end
@ -95,12 +97,22 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
end
scenario 'Add champ, add file, visualize them in procedure preview' do
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
click_on 'add_type_de_champ'
expect(page).to have_current_path(admin_procedure_types_de_champ_path(Procedure.last))
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
expect(Procedure.last.types_de_champ.first.libelle).to eq('libelle de champ')
page.refresh
expect(page).to have_current_path(champs_procedure_path(Procedure.last))
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
expect(page).to have_content('Champs enregistrés')
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
click_on Procedure.last.libelle
click_on 'onglet-pieces'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))
fill_in 'procedure_types_de_piece_justificative_attributes_0_libelle', with: 'libelle de piece'
@ -117,8 +129,15 @@ feature 'As an administrateur I wanna create a new procedure', js: true do
end
scenario 'After adding champ and file, make publication' do
page.refresh
within '.footer' do
click_on 'Ajouter un champ'
end
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libelle de champ'
click_on 'add_type_de_champ'
expect(page).to have_content('Champs enregistrés')
click_on Procedure.last.libelle
click_on 'onglet-pieces'
expect(page).to have_current_path(admin_procedure_pieces_justificatives_path(Procedure.last))

View file

@ -0,0 +1,95 @@
require 'spec_helper'
feature 'As an administrateur I edit procedure', js: true do
let(:administrateur) { procedure.administrateur }
let(:procedure) { create(:procedure) }
before do
login_as administrateur, scope: :administrateur
visit champs_procedure_path(procedure)
end
it "Add a new champ" do
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Champs enregistrés')
page.refresh
within '.footer' do
click_on 'Enregistrer'
end
expect(page).to have_content('Champs enregistrés')
end
it "Add multiple champs" do
within '.footer' do
click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
click_on 'Ajouter un champ'
end
expect(page).not_to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Modifications non sauvegardées.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ 0'
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_libelle')
expect(page).to have_selector('#procedure_types_de_champ_attributes_1_libelle')
expect(page).to have_selector('#procedure_types_de_champ_attributes_2_libelle')
expect(page).to have_selector('#procedure_types_de_champ_attributes_3_libelle')
expect(page).to have_content('Le libellé doit être rempli.')
expect(page).to have_content('Modifications non sauvegardées.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_2_libelle', with: 'libellé de champ 2'
within '.draggable-item-3' do
click_on 'Supprimer'
end
expect(page).to have_content('Le libellé doit être rempli.')
expect(page).to have_content('Modifications non sauvegardées.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_1_libelle', with: 'libellé de champ 1'
expect(page).not_to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Modifications non sauvegardées.')
expect(page).to have_content('Champs enregistrés')
page.refresh
expect(page).to have_content('Supprimer', count: 3)
end
it "Remove champs" do
within '.footer' do
click_on 'Ajouter un champ'
end
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Champs enregistrés')
page.refresh
click_on 'Supprimer'
expect(page).to have_content('Champs enregistrés')
expect(page).not_to have_content('Supprimer')
page.refresh
expect(page).not_to have_content('Supprimer')
end
it "Only add valid champs" do
within '.footer' do
click_on 'Ajouter un champ'
end
expect(page).to have_selector('#procedure_types_de_champ_attributes_0_description')
fill_in 'procedure_types_de_champ_attributes_0_description', with: 'déscription du champ'
expect(page).to have_content('Le libellé doit être rempli.')
expect(page).not_to have_content('Champs enregistrés')
fill_in 'procedure_types_de_champ_attributes_0_libelle', with: 'libellé de champ'
expect(page).to have_content('Champs enregistrés')
end
end

149
yarn.lock
View file

@ -767,6 +767,21 @@
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-5.5.0.tgz#146c2a29ee7d3bae4bf2fcb274636e264c813c45"
integrity sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==
"@vue/component-compiler-utils@^2.4.0":
version "2.5.0"
resolved "https://registry.yarnpkg.com/@vue/component-compiler-utils/-/component-compiler-utils-2.5.0.tgz#411846d582d393f701f747517ddd29275ce64ca4"
integrity sha512-mSB8jWmE/ZeYZHPDEx9hNiiRh5P2V1Q0tObxEQWtxxfXtkIAvPnj7oucGm5SO8Y/QwIlDJgAGqHfj5MCjoKoOg==
dependencies:
consolidate "^0.15.1"
hash-sum "^1.0.2"
lru-cache "^4.1.2"
merge-source-map "^1.1.0"
postcss "^7.0.7"
postcss-selector-parser "^5.0.0"
prettier "1.13.7"
source-map "^0.7.3"
vue-template-es2015-compiler "^1.6.0"
"@webassemblyjs/ast@1.7.11":
version "1.7.11"
resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.7.11.tgz#b988582cafbb2b095e8b556526f30c90d057cace"
@ -1370,6 +1385,11 @@ block-stream@*:
dependencies:
inherits "~2.0.0"
bluebird@^3.1.1:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
bluebird@^3.5.1:
version "3.5.1"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9"
@ -1757,6 +1777,15 @@ chalk@^2.0, chalk@^2.0.0, chalk@^2.1.0, chalk@^2.4.0, chalk@^2.4.1:
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
dependencies:
ansi-styles "^3.2.1"
escape-string-regexp "^1.0.5"
supports-color "^5.3.0"
chardet@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
@ -2089,6 +2118,13 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0:
resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
consolidate@^0.15.1:
version "0.15.1"
resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7"
integrity sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==
dependencies:
bluebird "^3.1.1"
constants-browserify@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
@ -2467,6 +2503,11 @@ date-now@^0.1.4:
resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b"
integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=
de-indent@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
debounce@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131"
@ -3827,6 +3868,11 @@ hash-base@^3.0.0:
inherits "^2.0.1"
safe-buffer "^5.0.1"
hash-sum@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/hash-sum/-/hash-sum-1.0.2.tgz#33b40777754c6432573c120cc3808bbd10d47f04"
integrity sha1-M7QHd3VMZDJXPBIMw4CLvRDUfwQ=
hash.js@^1.0.0, hash.js@^1.0.3:
version "1.1.5"
resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812"
@ -3835,6 +3881,11 @@ hash.js@^1.0.0, hash.js@^1.0.3:
inherits "^2.0.3"
minimalistic-assert "^1.0.1"
he@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hex-color-regex@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
@ -4932,7 +4983,7 @@ lru-cache@^4.0.1, lru-cache@^4.1.1:
pseudomap "^1.0.2"
yallist "^2.1.2"
lru-cache@^4.1.3:
lru-cache@^4.1.2, lru-cache@^4.1.3:
version "4.1.5"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
@ -5051,6 +5102,13 @@ merge-descriptors@1.0.1:
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
integrity sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=
merge-source-map@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/merge-source-map/-/merge-source-map-1.1.0.tgz#2fdde7e6020939f70906a68f2d7ae685e4c8c646"
integrity sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==
dependencies:
source-map "^0.6.1"
methods@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
@ -6568,6 +6626,15 @@ postcss-selector-parser@^4.0.0:
indexes-of "^1.0.1"
uniq "^1.0.1"
postcss-selector-parser@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0.tgz#249044356697b33b64f1a8f7c80922dddee7195c"
integrity sha512-w+zLE5Jhg6Liz8+rQOWEAwtwkyqpfnmsinXjXg6cY7YIONZZtgvE0v2O0uhQBs0peNomOJwWRKt6JBfTdTd3OQ==
dependencies:
cssesc "^2.0.0"
indexes-of "^1.0.1"
uniq "^1.0.1"
postcss-selector-parser@^5.0.0-rc.4:
version "5.0.0-rc.4"
resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-5.0.0-rc.4.tgz#ca5e77238bf152966378c13e91ad6d611568ea87"
@ -6633,6 +6700,15 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.5:
source-map "^0.6.1"
supports-color "^5.5.0"
postcss@^7.0.7:
version "7.0.8"
resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.8.tgz#2a3c5f2bdd00240cd0d0901fd998347c93d36696"
integrity sha512-WudsIzuTKRw9IInRTPBgVXJ7DKR26HT09Rxp0g3w0Fqh3TUtYICcUmvC0xURj04o3vdcDtnjCAUCECg/p341iQ==
dependencies:
chalk "^2.4.2"
source-map "^0.6.1"
supports-color "^6.0.0"
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
@ -6650,6 +6726,11 @@ prettier-linter-helpers@^1.0.0:
dependencies:
fast-diff "^1.1.2"
prettier@1.13.7:
version "1.13.7"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
prettier@^1.15.3:
version "1.15.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
@ -7505,6 +7586,11 @@ sort-keys@^2.0.0:
dependencies:
is-plain-obj "^1.0.0"
sortablejs@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.7.0.tgz#80a2b2370abd568e1cec8c271131ef30a904fa28"
integrity sha1-gKKyNwq9Vo4c7IwnETHvMKkE+ig=
source-list-map@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085"
@ -7551,6 +7637,11 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
source-map@^0.7.3:
version "0.7.3"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
spark-md5@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.0.tgz#3722227c54e2faf24b1dc6d933cc144e6f71bfef"
@ -7821,6 +7912,13 @@ supports-color@^5.5.0:
dependencies:
has-flag "^3.0.0"
supports-color@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a"
integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==
dependencies:
has-flag "^3.0.0"
svgo@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985"
@ -8410,6 +8508,55 @@ vm-browserify@0.0.4:
dependencies:
indexof "0.0.1"
vue-hot-reload-api@^2.3.0:
version "2.3.1"
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.3.1.tgz#b2d3d95402a811602380783ea4f566eb875569a2"
integrity sha512-AA86yKZ5uOKz87/q1UpngEXhbRkaYg1b7HMMVRobNV1IVKqZe8oLIzo6iMocVwZXnYitlGwf2k4ZRLOZlS8oPQ==
vue-loader@^15.5.1:
version "15.5.1"
resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.5.1.tgz#e005d5fc2481a55a19910f0dcf434206d68acc2a"
integrity sha512-gsTA9xRzu9jGBzkcrAB8my14RkHMzdr5rY/mCFmxgY2tOVsd2Z1MaYCDXHu5nX6PyHAsVK2/hXmarPln/2MiIw==
dependencies:
"@vue/component-compiler-utils" "^2.4.0"
hash-sum "^1.0.2"
loader-utils "^1.1.0"
vue-hot-reload-api "^2.3.0"
vue-style-loader "^4.1.0"
vue-style-loader@^4.1.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vue-style-loader/-/vue-style-loader-4.1.2.tgz#dedf349806f25ceb4e64f3ad7c0a44fba735fcf8"
integrity sha512-0ip8ge6Gzz/Bk0iHovU9XAUQaFt/G2B61bnWa2tCcqqdgfHs1lF9xXorFbE55Gmy92okFT+8bfmySuUOu13vxQ==
dependencies:
hash-sum "^1.0.2"
loader-utils "^1.0.2"
vue-template-compiler@^2.5.21:
version "2.5.21"
resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.5.21.tgz#a57ceb903177e8f643560a8d639a0f8db647054a"
integrity sha512-Vmk5Cv7UcmI99B9nXJEkaK262IQNnHp5rJYo+EwYpe2epTAXqcVyExhV6pk8jTkxQK2vRc8v8KmZBAwdmUZvvw==
dependencies:
de-indent "^1.0.2"
he "^1.1.0"
vue-template-es2015-compiler@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18"
integrity sha512-x3LV3wdmmERhVCYy3quqA57NJW7F3i6faas++pJQWtknWT+n7k30F4TVdHvCLn48peTJFRvCpxs3UuFPqgeELg==
vue@^2.5.21:
version "2.5.21"
resolved "https://registry.yarnpkg.com/vue/-/vue-2.5.21.tgz#3d33dcd03bb813912ce894a8303ab553699c4a85"
integrity sha512-Aejvyyfhn0zjVeLvXd70h4hrE4zZDx1wfZqia6ekkobLmUZ+vNFQer53B4fu0EjWBSiqApxPejzkO1Znt3joxQ==
vuedraggable@^2.16.0:
version "2.17.0"
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-2.17.0.tgz#489c16c9bcb9eb06f441944d3c52e5f2bb7060f9"
integrity sha512-TAC5tJTSbHSINQCSB59qHnuzT0Ad+E3IgvSWuA1e9UaebD8DxKaY1tCdvL3XvuLoaM3wc1dhpP/NbjpdxYsrng==
dependencies:
sortablejs "^1.7.0"
watchpack@^1.5.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00"