Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • geocontrib/geocontrib-frontend
  • ext_matthieu/geocontrib-frontend
  • fnecas/geocontrib-frontend
  • MatthieuE/geocontrib-frontend
4 results
Show changes
Showing
with 1359 additions and 40840 deletions
src/assets/img/multipolygon.png

24.7 KiB | W: 0px | H: 0px

src/assets/img/multipolygon.png

9.69 KiB | W: 0px | H: 0px

src/assets/img/multipolygon.png
src/assets/img/multipolygon.png
src/assets/img/multipolygon.png
src/assets/img/multipolygon.png
  • 2-up
  • Swipe
  • Onion skin
src/assets/img/polygon.png

8.26 KiB | W: 0px | H: 0px

src/assets/img/polygon.png

4.03 KiB | W: 0px | H: 0px

src/assets/img/polygon.png
src/assets/img/polygon.png
src/assets/img/polygon.png
src/assets/img/polygon.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -12,4 +12,117 @@ export function fileConvertSizeToMo(aSize){ ...@@ -12,4 +12,117 @@ export function fileConvertSizeToMo(aSize){
aSize = Math.abs(parseInt(aSize, 10)); aSize = Math.abs(parseInt(aSize, 10));
const def = [1024*1024, 'Mo', 1]; const def = [1024*1024, 'Mo', 1];
return (aSize/def[0]).toFixed(def[2]); return (aSize/def[0]).toFixed(def[2]);
} }
\ No newline at end of file
/**
* Determines the likely field delimiter in a CSV string by analyzing the first few lines.
* The function counts the occurrences of common delimiters such as commas, semicolons, and tabs.
* The most frequently and consistently occurring delimiter across the sampled lines is chosen as the likely delimiter.
*
* @param {string} text - The CSV string to analyze for determining the delimiter.
* @returns {string|false} The most likely delimiter character if one can be determined, or false if none is found.
*/
export function determineDelimiter(text) {
const lines = text.split('\n').slice(0, 5); // Analyze the first 5 lines
const delimiters = [',', ';', '\t']; // List of possible delimiters
let delimiterCounts = new Map(delimiters.map(d => [d, 0])); // Initialize a map to count delimiter occurrences
// Count the occurrences of each delimiter in each line
lines.forEach(line => {
delimiters.forEach(delimiter => {
const count = line.split(delimiter).length - 1; // Count the occurrences of the delimiter in the line
delimiterCounts.set(delimiter, delimiterCounts.get(delimiter) + count); // Update the count in the map
});
});
let mostCommonDelimiter = '';
let maxCount = 0;
// Determine the most common delimiter
delimiterCounts.forEach((count, delimiter) => {
if (count > maxCount) {
mostCommonDelimiter = delimiter; // Set the most common delimiter found so far
maxCount = count; // Update the maximum count found so far
}
});
return mostCommonDelimiter || false; // Return the most common delimiter or false if none is found
}
/**
* Parses a CSV string into an array of rows, where each row is an array of fields.
* The function correctly handles multiline fields enclosed in double quotes, removes
* carriage return characters (\r) at the end of lines, and allows for different field
* delimiters.
*
* @param {string} text - The CSV string to be parsed.
* @param {string} delimiter - The field delimiter character (default is comma ',').
* @returns {Array<Array<string>>} An array of rows, each row being an array of fields.
*/
export function parseCSV(text, delimiter = ',') {
let rows = []; // This will hold the parsed rows
let row = []; // Temporary array to hold the fields of the current row
let field = ''; // Temporary string to hold the current field
let inQuotes = false; // Boolean to track whether we are inside quotes
for (let i = 0; i < text.length; i++) {
const char = text[i]; // Current character
if (char === '"' && text[i - 1] !== '\\') {
inQuotes = !inQuotes; // Toggle the inQuotes flag if not escaped
} else if (char === delimiter && !inQuotes) {
// If the current character is the delimiter and we are not inside quotes,
// add the field to the row and reset the field variable
row.push(field.replace(/\r$/, '')); // Remove trailing carriage return
field = '';
} else if (char === '\n' && !inQuotes) {
// If the current character is a newline and we are not inside quotes,
// add the field to the row, add the row to the list of rows,
// and reset the field and row variables
row.push(field.replace(/\r$/, '')); // Remove trailing carriage return
rows.push(row);
row = [];
field = '';
} else {
// If the current character is part of a field, add it to the field variable
field += char;
}
}
// After the loop, check if there's a remaining field or row to be added
if (field) {
row.push(field.replace(/\r$/, '')); // Remove trailing carriage return
rows.push(row);
}
return rows; // Return the parsed rows
}
/**
* Checks if the values in 'lon' and 'lat' columns are decimal numbers in the provided CSV data.
*
* @param {Array<string>} headers - The array of headers from the CSV file.
* @param {Array<Array<string>>} data - The CSV data as an array of rows, each row being an array of field values.
* @returns {boolean} True if 'lon' and 'lat' are found and their values are decimal numbers, false otherwise.
*/
export function checkLonLatValues(headers, data) {
const lonIndex = headers.indexOf('lon');
const latIndex = headers.indexOf('lat');
// Check if both 'lon' and 'lat' headers are found
if (lonIndex === -1 || latIndex === -1) {
return false;
}
// Function to check if a string is a decimal number
const isDecimal = (str) => !isNaN(str) && str.includes('.');
for (const row of data) {
// Check if 'lon' and 'lat' values are decimal numbers
if (!isDecimal(row[lonIndex]) || !isDecimal(row[latIndex])) {
return false;
}
}
return true;
}
/* required styles */
/* For input, we add the strong #map selector to avoid conflicts with semantic-ui */
#map .leaflet-control-geocoder {
border-radius: 4px;
background: white;
min-width: 26px;
min-height: 26px;
}
.leaflet-touch .leaflet-control-geocoder {
min-width: 30px;
min-height: 30px;
}
.leaflet-control-geocoder a,
.leaflet-control-geocoder .leaflet-control-geocoder-icon {
border-bottom: none;
display: inline-block;
}
.leaflet-control-geocoder .leaflet-control-geocoder-alternatives a {
width: inherit;
height: inherit;
line-height: inherit;
}
.leaflet-control-geocoder a:hover,
.leaflet-control-geocoder .leaflet-control-geocoder-icon:hover {
border-bottom: none;
display: inline-block;
}
.leaflet-control-geocoder-form {
display: none;
vertical-align: middle;
}
.leaflet-control-geocoder-expanded .leaflet-control-geocoder-form {
display: inline-block;
}
#map .leaflet-control-geocoder-form input {
font-size: 120%;
border: 0;
background-color: transparent;
width: 246px;
}
.leaflet-control-geocoder-icon {
border-radius: 4px;
width: 26px;
height: 26px;
border: none;
background-color: white;
background-image: url(./images/geocoder.svg);
background-repeat: no-repeat;
background-position: center;
cursor: pointer;
}
.leaflet-touch .leaflet-control-geocoder-icon {
width: 30px;
height: 30px;
}
.leaflet-control-geocoder-throbber .leaflet-control-geocoder-icon {
background-image: url(./images/throbber.gif);
}
.leaflet-control-geocoder-form-no-error {
display: none;
}
#map .leaflet-control-geocoder-form input:focus {
outline: none;
}
.leaflet-control-geocoder-form button {
display: none;
}
.leaflet-control-geocoder-error {
margin-top: 8px;
margin-left: 8px;
display: block;
color: #444;
}
.leaflet-control-geocoder-alternatives {
display: block;
width: 272px;
list-style: none;
padding: 0;
margin: 0;
}
.leaflet-control-geocoder-alternatives-minimized {
display: none;
height: 0;
}
.leaflet-control-geocoder-alternatives li {
white-space: nowrap;
display: block;
overflow: hidden;
padding: 5px 8px;
text-overflow: ellipsis;
border-bottom: 1px solid #ccc;
cursor: pointer;
}
.leaflet-control-geocoder-alternatives li a,
.leaflet-control-geocoder-alternatives li a:hover {
width: inherit;
height: inherit;
line-height: inherit;
background: inherit;
border-radius: inherit;
text-align: left;
}
.leaflet-control-geocoder-alternatives li:last-child {
border-bottom: none;
}
.leaflet-control-geocoder-alternatives li:hover,
.leaflet-control-geocoder-selected {
background-color: #f5f5f5;
}
/* Custom style */
.leaflet-control-geocoder-icon {
border-radius: 4px;
width: 35px;
height: 35px;
}
#map .leaflet-control-geocoder-form input {
height: 35px;
}
.leaflet-control-geocoder-alternatives li:first-of-type {
border-top: 1px solid #ccc;
}
.leaflet-control-geocoder-address-item {
font-weight: 600;
}
.leaflet-control-geocoder-address-detail {
font-size: 12px;
font-weight: normal;
}
.leaflet-control-geocoder-address-context {
color: #666;
font-size: 12px;
font-weight: lighter;
}
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
\ No newline at end of file
Source diff could not be displayed: it is too large. Options to address this: view the blob.
...@@ -10,6 +10,7 @@ ...@@ -10,6 +10,7 @@
*/ */
   
@import url('https://fonts.googleapis.com/css?family=Roboto Condensed:400,700,400italic,700italic&subset=latin'); @import url('https://fonts.googleapis.com/css?family=Roboto Condensed:400,700,400italic,700italic&subset=latin');
/*! /*!
* # Semantic UI 2.4.2 - Reset * # Semantic UI 2.4.2 - Reset
* http://github.com/semantic-org/semantic-ui/ * http://github.com/semantic-org/semantic-ui/
...@@ -271,7 +272,7 @@ body { ...@@ -271,7 +272,7 @@ body {
overflow-x: hidden; overflow-x: hidden;
min-width: 320px; min-width: 320px;
background: #fff; background: #fff;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 14px; font-size: 14px;
line-height: 1.4285em; line-height: 1.4285em;
color: #252525; color: #252525;
...@@ -279,7 +280,7 @@ body { ...@@ -279,7 +280,7 @@ body {
} }
   
h1, h2, h3, h4, h5 { h1, h2, h3, h4, h5 {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
line-height: 1.28571429em; line-height: 1.28571429em;
margin: calc(2rem - .14285714em) 0 1rem; margin: calc(2rem - .14285714em) 0 1rem;
font-weight: 700; font-weight: 700;
...@@ -420,7 +421,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover { ...@@ -420,7 +421,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover {
vertical-align: baseline; vertical-align: baseline;
background: #e0e1e2 none; background: #e0e1e2 none;
color: rgba(0, 0, 0, .6); color: rgba(0, 0, 0, .6);
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
margin: 0 .25em 0 0; margin: 0 .25em 0 0;
padding: .78571429em 1.5em .78571429em; padding: .78571429em 1.5em .78571429em;
text-transform: none; text-transform: none;
...@@ -2924,7 +2925,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover { ...@@ -2924,7 +2925,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover {
} }
   
.ui.teal.button, .ui.teal.buttons .button { .ui.teal.button, .ui.teal.buttons .button {
background-color: #00b5ad; background-color: var(--primary-color, #00b5ad);
color: #fff; color: #fff;
text-shadow: none; text-shadow: none;
background-image: none background-image: none
...@@ -2936,60 +2937,68 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover { ...@@ -2936,60 +2937,68 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover {
} }
   
.ui.teal.button:hover, .ui.teal.buttons .button:hover { .ui.teal.button:hover, .ui.teal.buttons .button:hover {
background-color: #009c95; background-color: var(--primary-highlight-color, #009c95)
;
color: #fff; color: #fff;
text-shadow: none text-shadow: none
} }
   
.ui.teal.button:focus, .ui.teal.buttons .button:focus { .ui.teal.button:focus, .ui.teal.buttons .button:focus {
background-color: #008c86; background-color: var(--primary-highlight-color, #008c86);
color: #fff; color: #fff;
text-shadow: none text-shadow: none
} }
   
.ui.teal.button:active, .ui.teal.buttons .button:active { .ui.teal.button:active, .ui.teal.buttons .button:active {
background-color: #00827c; background: var(--primary-highlight-color, #00827c);
color: #fff; color: #fff;
text-shadow: none text-shadow: none
} }
   
.ui.teal.active.button, .ui.teal.button .active.button:active, .ui.teal.buttons .active.button, .ui.teal.buttons .active.button:active { .ui.teal.active.button, .ui.teal.button .active.button:active, .ui.teal.buttons .active.button, .ui.teal.buttons .active.button:active {
background-color: #009c95; background-color: var(--primary-highlight-color, #009c95)
;
color: #fff; color: #fff;
text-shadow: none text-shadow: none
} }
   
.ui.basic.teal.button, .ui.basic.teal.buttons .button { .ui.basic.teal.button, .ui.basic.teal.buttons .button {
-webkit-box-shadow: 0 0 0 1px #00b5ad inset!important; -webkit-box-shadow: 0 0 0 1px var(--primary-color, #00b5ad) inset!important;
box-shadow: 0 0 0 1px #00b5ad inset!important; box-shadow: 0 0 0 1px var(--primary-color, #00b5ad) inset!important;
color: #00b5ad!important color: var(--primary-color, #00b5ad)!important
} }
   
.ui.basic.teal.button:hover, .ui.basic.teal.buttons .button:hover { .ui.basic.teal.button:hover, .ui.basic.teal.buttons .button:hover {
background: 0 0!important; background: 0 0!important;
-webkit-box-shadow: 0 0 0 1px #009c95 inset!important; -webkit-box-shadow: 0 0 0 1px var(--primary-highlight-color, #009c95)
box-shadow: 0 0 0 1px #009c95 inset!important; inset!important;
color: #009c95!important box-shadow: 0 0 0 1px var(--primary-highlight-color, #009c95)
inset!important;
color: var(--primary-highlight-color, #009c95)
!important
} }
   
.ui.basic.teal.button:focus, .ui.basic.teal.buttons .button:focus { .ui.basic.teal.button:focus, .ui.basic.teal.buttons .button:focus {
background: 0 0!important; background: 0 0!important;
-webkit-box-shadow: 0 0 0 1px #008c86 inset!important; -webkit-box-shadow: 0 0 0 1px var(--primary-highlight-color, #008c86) inset!important;
box-shadow: 0 0 0 1px #008c86 inset!important; box-shadow: 0 0 0 1px var(--primary-highlight-color, #008c86) inset!important;
color: #009c95!important color: var(--primary-highlight-color, #009c95)
!important
} }
   
.ui.basic.teal.active.button, .ui.basic.teal.buttons .active.button { .ui.basic.teal.active.button, .ui.basic.teal.buttons .active.button {
background: 0 0!important; background: 0 0!important;
-webkit-box-shadow: 0 0 0 1px #009c95 inset!important; -webkit-box-shadow: 0 0 0 1px var(--primary-highlight-color, #009c95)
box-shadow: 0 0 0 1px #009c95 inset!important; inset!important;
color: #00827c!important box-shadow: 0 0 0 1px var(--primary-highlight-color, #009c95)
inset!important;
color: var(--primary-highlight-color, #00827c)!important
} }
   
.ui.basic.teal.button:active, .ui.basic.teal.buttons .button:active { .ui.basic.teal.button:active, .ui.basic.teal.buttons .button:active {
-webkit-box-shadow: 0 0 0 1px #00827c inset!important; -webkit-box-shadow: 0 0 0 1px var(--primary-highlight-color, #00827c) inset!important;
box-shadow: 0 0 0 1px #00827c inset!important; box-shadow: 0 0 0 1px var(--primary-highlight-color, #00827c) inset!important;
color: #00827c!important color: var(--primary-highlight-color, #00827c)!important
} }
   
.ui.buttons:not(.vertical)>.basic.teal.button:not(:first-child) { .ui.buttons:not(.vertical)>.basic.teal.button:not(:first-child) {
...@@ -3905,7 +3914,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover { ...@@ -3905,7 +3914,7 @@ body .ui.inverted::-webkit-scrollbar-thumb:hover {
} }
   
.ui.text.container { .ui.text.container {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
max-width: 700px!important; max-width: 700px!important;
line-height: 1.5 line-height: 1.5
} }
...@@ -5146,7 +5155,7 @@ i.flag.zimbabwe:before, i.flag.zw:before { ...@@ -5146,7 +5155,7 @@ i.flag.zimbabwe:before, i.flag.zw:before {
border: none; border: none;
margin: calc(2rem - .14285714em) 0 1rem; margin: calc(2rem - .14285714em) 0 1rem;
padding: 0 0; padding: 0 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-weight: 700; font-weight: 700;
line-height: 1.28571429em; line-height: 1.28571429em;
text-transform: none; text-transform: none;
...@@ -5516,15 +5525,16 @@ a.ui.inverted.green.header:hover { ...@@ -5516,15 +5525,16 @@ a.ui.inverted.green.header:hover {
} }
   
.ui.teal.header { .ui.teal.header {
color: #00b5ad!important color: var(--primary-color, #00b5ad)!important
} }
   
a.ui.teal.header:hover { a.ui.teal.header:hover {
color: #009c95!important color: var(--primary-highlight-color, #009c95)
!important
} }
   
.ui.teal.dividing.header { .ui.teal.dividing.header {
border-bottom: 2px solid #00b5ad border-bottom: 2px solid var(--primary-color, #00b5ad)
} }
   
.ui.inverted.teal.header { .ui.inverted.teal.header {
...@@ -6027,7 +6037,7 @@ i.inverted.bordered.green.icon, i.inverted.circular.green.icon { ...@@ -6027,7 +6037,7 @@ i.inverted.bordered.green.icon, i.inverted.circular.green.icon {
} }
   
i.teal.icon { i.teal.icon {
color: #00b5ad!important color: var(--primary-color, #00b5ad)!important
} }
   
i.inverted.teal.icon { i.inverted.teal.icon {
...@@ -6035,7 +6045,7 @@ i.inverted.teal.icon { ...@@ -6035,7 +6045,7 @@ i.inverted.teal.icon {
} }
   
i.inverted.bordered.teal.icon, i.inverted.circular.teal.icon { i.inverted.bordered.teal.icon, i.inverted.circular.teal.icon {
background-color: #00b5ad!important; background-color: var(--primary-color, #00b5ad)!important;
color: #fff!important color: #fff!important
} }
   
...@@ -11654,7 +11664,7 @@ img.ui.image { ...@@ -11654,7 +11664,7 @@ img.ui.image {
-webkit-tap-highlight-color: rgba(255, 255, 255, 0); -webkit-tap-highlight-color: rgba(255, 255, 255, 0);
text-align: left; text-align: left;
line-height: 1.21428571em; line-height: 1.21428571em;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
padding: .67857143em 1em; padding: .67857143em 1em;
background: #fff; background: #fff;
border: 1px solid rgba(34, 36, 38, .15); border: 1px solid rgba(34, 36, 38, .15);
...@@ -12658,14 +12668,16 @@ a.ui.active.label:hover, a.ui.labels .active.label:hover { ...@@ -12658,14 +12668,16 @@ a.ui.active.label:hover, a.ui.labels .active.label:hover {
} }
   
.ui.teal.label, .ui.teal.labels .label { .ui.teal.label, .ui.teal.labels .label {
background-color: #00b5ad!important; background-color: var(--primary-color, #00b5ad)!important;
border-color: #00b5ad!important; border-color: var(--primary-color, #00b5ad)!important;
color: #fff!important color: #fff!important
} }
   
.ui.teal.labels .label:hover, a.ui.teal.label:hover { .ui.teal.labels .label:hover, a.ui.teal.label:hover {
background-color: #009c95!important; background-color: var(--primary-highlight-color, #009c95)
border-color: #009c95!important; !important;
border-color: var(--primary-highlight-color, #009c95)
!important;
color: #fff!important color: #fff!important
} }
   
...@@ -12674,19 +12686,21 @@ a.ui.active.label:hover, a.ui.labels .active.label:hover { ...@@ -12674,19 +12686,21 @@ a.ui.active.label:hover, a.ui.labels .active.label:hover {
} }
   
.ui.teal.ribbon.label { .ui.teal.ribbon.label {
border-color: #00827c!important border-color: var(--primary-highlight-color, #00827c)!important
} }
   
.ui.basic.teal.label { .ui.basic.teal.label {
background: none #fff!important; background: none #fff!important;
color: #00b5ad!important; color: var(--primary-color, #00b5ad)!important;
border-color: #00b5ad!important border-color: var(--primary-color, #00b5ad)!important
} }
   
.ui.basic.teal.labels a.label:hover, a.ui.basic.teal.label:hover { .ui.basic.teal.labels a.label:hover, a.ui.basic.teal.label:hover {
background-color: #fff!important; background-color: #fff!important;
color: #009c95!important; color: var(--primary-highlight-color, #009c95)
border-color: #009c95!important !important;
border-color: var(--primary-highlight-color, #009c95)
!important
} }
   
.ui.blue.label, .ui.blue.labels .label { .ui.blue.label, .ui.blue.labels .label {
...@@ -13236,7 +13250,7 @@ a.ui.basic.label:hover { ...@@ -13236,7 +13250,7 @@ a.ui.basic.label:hover {
.ui.list .list>.item .header, .ui.list>.item .header { .ui.list .list>.item .header, .ui.list>.item .header {
display: block; display: block;
margin: 0; margin: 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-weight: 700; font-weight: 700;
color: #252525 color: #252525
} }
...@@ -15298,11 +15312,11 @@ ol.ui.list li[value]:before { ...@@ -15298,11 +15312,11 @@ ol.ui.list li[value]:before {
} }
   
.ui.teal.segment:not(.inverted) { .ui.teal.segment:not(.inverted) {
border-top: 2px solid #00b5ad!important border-top: 2px solid var(--primary-color, #00b5ad)!important
} }
   
.ui.inverted.teal.segment { .ui.inverted.teal.segment {
background-color: #00b5ad!important; background-color: var(--primary-color, #00b5ad)!important;
color: #fff!important color: #fff!important
} }
   
...@@ -15627,7 +15641,7 @@ ol.ui.list li[value]:before { ...@@ -15627,7 +15641,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.steps .step .title { .ui.steps .step .title {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 1.14285714em; font-size: 1.14285714em;
font-weight: 700 font-weight: 700
} }
...@@ -16174,7 +16188,7 @@ ol.ui.list li[value]:before { ...@@ -16174,7 +16188,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.form input:not([type]), .ui.form input[type=date], .ui.form input[type=datetime-local], .ui.form input[type=email], .ui.form input[type=file], .ui.form input[type=number], .ui.form input[type=password], .ui.form input[type=search], .ui.form input[type=tel], .ui.form input[type=text], .ui.form input[type=time], .ui.form input[type=url] { .ui.form input:not([type]), .ui.form input[type=date], .ui.form input[type=datetime-local], .ui.form input[type=email], .ui.form input[type=file], .ui.form input[type=number], .ui.form input[type=password], .ui.form input[type=search], .ui.form input[type=tel], .ui.form input[type=text], .ui.form input[type=time], .ui.form input[type=url] {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
margin: 0; margin: 0;
outline: 0; outline: 0;
-webkit-appearance: none; -webkit-appearance: none;
...@@ -17827,7 +17841,7 @@ ol.ui.list li[value]:before { ...@@ -17827,7 +17841,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.grid>.row>.teal.column, .ui.grid>.teal.column, .ui.grid>.teal.row { .ui.grid>.row>.teal.column, .ui.grid>.teal.column, .ui.grid>.teal.row {
background-color: #00b5ad!important; background-color: var(--primary-color, #00b5ad)!important;
color: #fff color: #fff
} }
   
...@@ -18237,7 +18251,7 @@ ol.ui.list li[value]:before { ...@@ -18237,7 +18251,7 @@ ol.ui.list li[value]:before {
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
margin: 1rem 0; margin: 1rem 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
background: #fff; background: #fff;
font-weight: 400; font-weight: 400;
border: 1px solid rgba(34, 36, 38, .15); border: 1px solid rgba(34, 36, 38, .15);
...@@ -19355,8 +19369,8 @@ ol.ui.list li[value]:before { ...@@ -19355,8 +19369,8 @@ ol.ui.list li[value]:before {
} }
   
.ui.menu .teal.active.item, .ui.teal.menu .active.item { .ui.menu .teal.active.item, .ui.teal.menu .active.item {
border-color: #00b5ad!important; border-color: var(--primary-color, #00b5ad)!important;
color: #00b5ad!important color: var(--primary-color, #00b5ad)!important
} }
   
.ui.blue.menu .active.item, .ui.menu .blue.active.item { .ui.blue.menu .active.item, .ui.menu .blue.active.item {
...@@ -19391,13 +19405,13 @@ ol.ui.list li[value]:before { ...@@ -19391,13 +19405,13 @@ ol.ui.list li[value]:before {
   
.ui.inverted.menu { .ui.inverted.menu {
border: 0 solid transparent; border: 0 solid transparent;
background: #373636; background: var(--primary-color, #373636);
-webkit-box-shadow: none; -webkit-box-shadow: none;
box-shadow: none box-shadow: none
} }
   
.ui.inverted.menu .item, .ui.inverted.menu .item>a:not(.ui) { .ui.inverted.menu .item, .ui.inverted.menu .item>a:not(.ui) {
background: 0 0; background: var(--primary-color, 0 0);
color: rgba(255, 255, 255, .9) color: rgba(255, 255, 255, .9)
} }
   
...@@ -19429,7 +19443,7 @@ ol.ui.list li[value]:before { ...@@ -19429,7 +19443,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.inverted.menu .dropdown.item:hover, .ui.inverted.menu .link.item:hover, .ui.inverted.menu a.item:hover, .ui.link.inverted.menu .item:hover { .ui.inverted.menu .dropdown.item:hover, .ui.inverted.menu .link.item:hover, .ui.inverted.menu a.item:hover, .ui.link.inverted.menu .item:hover {
background: rgba(255, 255, 255, .08); background: var(--primary-highlight-color, rgba(255, 255, 255, 0.08));
color: #fff color: #fff
} }
   
...@@ -19439,12 +19453,12 @@ ol.ui.list li[value]:before { ...@@ -19439,12 +19453,12 @@ ol.ui.list li[value]:before {
} }
   
.ui.inverted.menu .link.item:active, .ui.inverted.menu a.item:active { .ui.inverted.menu .link.item:active, .ui.inverted.menu a.item:active {
background: #757575; background: var(--primary-color, #757575);
color: #fff color: #fff
} }
   
.ui.inverted.menu .active.item { .ui.inverted.menu .active.item {
background: #757575; background: var(--primary-highlight-color, rgba(255, 255, 255, 0.08));
color: #fff!important color: #fff!important
} }
   
...@@ -19462,7 +19476,7 @@ ol.ui.list li[value]:before { ...@@ -19462,7 +19476,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.inverted.menu .active.item:hover { .ui.inverted.menu .active.item:hover {
background: #757575; background: var(--primary-highlight-color, #757575);
color: #fff!important color: #fff!important
} }
   
...@@ -19545,7 +19559,7 @@ ol.ui.list li[value]:before { ...@@ -19545,7 +19559,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.inverted.menu .teal.active.item, .ui.inverted.teal.menu { .ui.inverted.menu .teal.active.item, .ui.inverted.teal.menu {
background-color: #00b5ad background-color: var(--primary-color, #00b5ad)
} }
   
.ui.inverted.teal.menu .item:before { .ui.inverted.teal.menu .item:before {
...@@ -20019,10 +20033,10 @@ ol.ui.list li[value]:before { ...@@ -20019,10 +20033,10 @@ ol.ui.list li[value]:before {
padding: 1em 1.5em; padding: 1em 1.5em;
line-height: 1.4285em; line-height: 1.4285em;
color: #252525; color: #252525;
-webkit-transition: opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease; -webkit-transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease;
transition: opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease; transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, -webkit-box-shadow .5s ease;
transition: opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease; transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease;
transition: opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease, -webkit-box-shadow .5s ease; transition: padding .5s ease, max-height .5s ease, opacity .5s ease, color .5s ease, background .5s ease, box-shadow .5s ease, -webkit-box-shadow .5s ease;
border-radius: .07142857rem; border-radius: .07142857rem;
-webkit-box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent; -webkit-box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent;
box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent box-shadow: 0 0 0 1px rgba(34, 36, 38, .22) inset, 0 0 0 0 transparent
...@@ -20038,7 +20052,7 @@ ol.ui.list li[value]:before { ...@@ -20038,7 +20052,7 @@ ol.ui.list li[value]:before {
   
.ui.message .header { .ui.message .header {
display: block; display: block;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-weight: 700; font-weight: 700;
margin: -.14285714em 0 0 0 margin: -.14285714em 0 0 0
} }
...@@ -21016,11 +21030,11 @@ ol.ui.list li[value]:before { ...@@ -21016,11 +21030,11 @@ ol.ui.list li[value]:before {
} }
   
.ui.teal.table { .ui.teal.table {
border-top: .2em solid #00b5ad border-top: .2em solid var(--primary-color, #00b5ad)
} }
   
.ui.inverted.teal.table { .ui.inverted.teal.table {
background-color: #00b5ad!important; background-color: var(--primary-color, #00b5ad)!important;
color: #fff!important color: #fff!important
} }
   
...@@ -21766,7 +21780,7 @@ ol.ui.list li[value]:before { ...@@ -21766,7 +21780,7 @@ ol.ui.list li[value]:before {
.ui.card>.content>.header, .ui.cards>.card>.content>.header { .ui.card>.content>.header, .ui.cards>.card>.content>.header {
display: block; display: block;
margin: ''; margin: '';
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
color: rgba(0, 0, 0, .85) color: rgba(0, 0, 0, .85)
} }
   
...@@ -22039,13 +22053,15 @@ ol.ui.list li[value]:before { ...@@ -22039,13 +22053,15 @@ ol.ui.list li[value]:before {
} }
   
.ui.cards>.teal.card, .ui.teal.card, .ui.teal.cards>.card { .ui.cards>.teal.card, .ui.teal.card, .ui.teal.cards>.card {
-webkit-box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 #00b5ad, 0 1px 3px 0 #d4d4d5; -webkit-box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 var(--primary-color, #00b5ad), 0 1px 3px 0 #d4d4d5;
box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 #00b5ad, 0 1px 3px 0 #d4d4d5 box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 var(--primary-color, #00b5ad), 0 1px 3px 0 #d4d4d5
} }
   
.ui.cards>.teal.card:hover, .ui.teal.card:hover, .ui.teal.cards>.card:hover { .ui.cards>.teal.card:hover, .ui.teal.card:hover, .ui.teal.cards>.card:hover {
-webkit-box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 #009c95, 0 1px 3px 0 #bcbdbd; -webkit-box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 var(--primary-highlight-color, #009c95)
box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 #009c95, 0 1px 3px 0 #bcbdbd , 0 1px 3px 0 #bcbdbd;
box-shadow: 0 0 0 1px #d4d4d5, 0 2px 0 0 var(--primary-highlight-color, #009c95)
, 0 1px 3px 0 #bcbdbd
} }
   
.ui.blue.card, .ui.blue.cards>.card, .ui.cards>.blue.card { .ui.blue.card, .ui.blue.cards>.card, .ui.cards>.blue.card {
...@@ -22994,7 +23010,7 @@ ol.ui.list li[value]:before { ...@@ -22994,7 +23010,7 @@ ol.ui.list li[value]:before {
.ui.items>.item>.content>.header { .ui.items>.item>.content>.header {
display: inline-block; display: inline-block;
margin: -.21425em 0 0; margin: -.21425em 0 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-weight: 700; font-weight: 700;
color: rgba(0, 0, 0, .85) color: rgba(0, 0, 0, .85)
} }
...@@ -23341,7 +23357,7 @@ ol.ui.list li[value]:before { ...@@ -23341,7 +23357,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.statistic>.value, .ui.statistics .statistic>.value { .ui.statistic>.value, .ui.statistics .statistic>.value {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 4rem; font-size: 4rem;
font-weight: 400; font-weight: 400;
line-height: 1em; line-height: 1em;
...@@ -23351,7 +23367,7 @@ ol.ui.list li[value]:before { ...@@ -23351,7 +23367,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.statistic>.label, .ui.statistics .statistic>.label { .ui.statistic>.label, .ui.statistics .statistic>.label {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 1em; font-size: 1em;
font-weight: 700; font-weight: 700;
color: #252525; color: #252525;
...@@ -23550,7 +23566,7 @@ ol.ui.list li[value]:before { ...@@ -23550,7 +23566,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.statistics .teal.statistic>.value, .ui.teal.statistic>.value, .ui.teal.statistics .statistic>.value { .ui.statistics .teal.statistic>.value, .ui.teal.statistic>.value, .ui.teal.statistics .statistic>.value {
color: #00b5ad color: var(--primary-color, #00b5ad)
} }
   
.ui.blue.statistic>.value, .ui.blue.statistics .statistic>.value, .ui.statistics .blue.statistic>.value { .ui.blue.statistic>.value, .ui.blue.statistics .statistic>.value, .ui.statistics .blue.statistic>.value {
...@@ -23744,7 +23760,7 @@ ol.ui.list li[value]:before { ...@@ -23744,7 +23760,7 @@ ol.ui.list li[value]:before {
   
.ui.accordion .title:not(.ui) { .ui.accordion .title:not(.ui) {
padding: .5em 0; padding: .5em 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 1em; font-size: 1em;
color: #252525 color: #252525
} }
...@@ -24305,7 +24321,7 @@ ol.ui.list li[value]:before { ...@@ -24305,7 +24321,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.toggle.checkbox input:checked~.box:before, .ui.toggle.checkbox input:checked~label:before { .ui.toggle.checkbox input:checked~.box:before, .ui.toggle.checkbox input:checked~label:before {
background-color: #ee2e24!important background-color: #2185d0!important
} }
   
.ui.toggle.checkbox input:checked~.box:after, .ui.toggle.checkbox input:checked~label:after { .ui.toggle.checkbox input:checked~.box:after, .ui.toggle.checkbox input:checked~label:after {
...@@ -24319,7 +24335,7 @@ ol.ui.list li[value]:before { ...@@ -24319,7 +24335,7 @@ ol.ui.list li[value]:before {
} }
   
.ui.toggle.checkbox input:focus:checked~.box:before, .ui.toggle.checkbox input:focus:checked~label:before { .ui.toggle.checkbox input:focus:checked~.box:before, .ui.toggle.checkbox input:focus:checked~label:before {
background-color: #e90c00!important background-color: #0d71bb!important
} }
   
.ui.fitted.checkbox .box, .ui.fitted.checkbox label { .ui.fitted.checkbox .box, .ui.fitted.checkbox label {
...@@ -24605,7 +24621,7 @@ body.dimmable>.dimmer { ...@@ -24605,7 +24621,7 @@ body.dimmable>.dimmer {
.ui.dropdown>.dropdown.icon { .ui.dropdown>.dropdown.icon {
position: relative; position: relative;
width: auto; width: auto;
font-size: .85714286em; font-size: .9em;
margin: 0 0 0 1em margin: 0 0 0 1em
} }
   
...@@ -24823,9 +24839,9 @@ select.ui.dropdown { ...@@ -24823,9 +24839,9 @@ select.ui.dropdown {
z-index: 3; z-index: 3;
margin: -.78571429em; margin: -.78571429em;
padding: .91666667em; padding: .91666667em;
opacity: .8; color: #999;
-webkit-transition: opacity .5s ease; -webkit-transition: transform .2s ease;
transition: opacity .5s ease transition: transform .2s ease
} }
   
.ui.compact.selection.dropdown { .ui.compact.selection.dropdown {
...@@ -24942,7 +24958,7 @@ select.ui.dropdown { ...@@ -24942,7 +24958,7 @@ select.ui.dropdown {
} }
   
.ui.active.selection.dropdown>.dropdown.icon, .ui.visible.selection.dropdown>.dropdown.icon { .ui.active.selection.dropdown>.dropdown.icon, .ui.visible.selection.dropdown>.dropdown.icon {
opacity: ''; transform: rotate(180deg);
z-index: 3 z-index: 3
} }
   
...@@ -25976,7 +25992,7 @@ select.ui.dropdown { ...@@ -25976,7 +25992,7 @@ select.ui.dropdown {
   
.ui.modal>.header { .ui.modal>.header {
display: block; display: block;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
background: #fff; background: #fff;
margin: 0; margin: 0;
padding: 1.25rem 1.5rem; padding: 1.25rem 1.5rem;
...@@ -26593,7 +26609,7 @@ a.ui.nag { ...@@ -26593,7 +26609,7 @@ a.ui.nag {
   
.ui.popup>.header { .ui.popup>.header {
padding: 0; padding: 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 1.14285714em; font-size: 1.14285714em;
line-height: 1.2; line-height: 1.2;
font-weight: 700 font-weight: 700
...@@ -27564,7 +27580,7 @@ a.ui.nag { ...@@ -27564,7 +27580,7 @@ a.ui.nag {
} }
   
.ui.teal.progress .bar { .ui.teal.progress .bar {
background-color: #00b5ad background-color: var(--primary-color, #00b5ad)
} }
   
.ui.teal.inverted.progress .bar { .ui.teal.inverted.progress .bar {
...@@ -27957,7 +27973,7 @@ a.ui.nag { ...@@ -27957,7 +27973,7 @@ a.ui.nag {
   
.ui.search>.results .result .title { .ui.search>.results .result .title {
margin: -.14285714em 0 0; margin: -.14285714em 0 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-weight: 700; font-weight: 700;
font-size: 1em; font-size: 1em;
color: rgba(0, 0, 0, .85) color: rgba(0, 0, 0, .85)
...@@ -27979,7 +27995,7 @@ a.ui.nag { ...@@ -27979,7 +27995,7 @@ a.ui.nag {
} }
   
.ui.search>.results>.message .header { .ui.search>.results>.message .header {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 1rem; font-size: 1rem;
font-weight: 700; font-weight: 700;
color: #252525 color: #252525
...@@ -28143,7 +28159,7 @@ a.ui.nag { ...@@ -28143,7 +28159,7 @@ a.ui.nag {
width: 100px; width: 100px;
white-space: nowrap; white-space: nowrap;
background: 0 0; background: 0 0;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif);
font-size: 1em; font-size: 1em;
padding: .4em 1em; padding: .4em 1em;
font-weight: 700; font-weight: 700;
......
/* ---------------------------------- */ /* ---------------------------------- */
/* APP */ /* APP */
/* ---------------------------------- */ /* ---------------------------------- */
body { body {
height:100%; height: 100%;
width:100%; width: 100%;
margin: 0; margin: 0;
} }
#app { #app {
position: relative; position: relative;
min-height: 100vh; /* keep the space for loader, before page contents are injected */ min-height: 100vh;
display: flex; /* used to fix height on sticky header and footer */ /* keep the space for loader, before page contents are injected */
flex-direction: column; /* used to fix height on sticky header and footer */ display: flex;
/* used to fix height on sticky header and footer */
flex-direction: column;
/* used to fix height on sticky header and footer */
} }
#app-header { #app-header {
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 1; z-index: 1;
background: #373636; background: var(--header-color, #373636);
.ui.inverted.menu {
background: var(--header-color, #373636);
}
.item {
background: var(--header-color, #373636);
}
} }
#app-content { #app-content {
overflow: auto; overflow: auto;
flex: 1 0 auto; /* used to fix height on sticky header and footer */ flex: 1 0 auto;
height: 61px; /* value by default of the header, defined here to be sync with anchor below */ /* used to fix height on sticky header and footer */
position: relative; /* for anchor below */ min-height: 61px;
/* value by default of the header, defined here to be sync with anchor below, in order to keep the page stuck to top */
position: relative;
/* for anchor below */
} }
#scroll-top-anchor { #scroll-top-anchor {
...@@ -46,16 +60,19 @@ body { ...@@ -46,16 +60,19 @@ body {
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 250px; min-height: 250px;
touch-action: none; /* workaround for modifying feature on mobile */ touch-action: none;
/* workaround for modifying feature on mobile */
} }
#app-footer { #app-footer {
overflow: hidden; overflow: hidden;
background-color: #464646; background-color: #464646;
text-align: center; text-align: center;
flex-shrink: 0; /* used to fix height on sticky header and footer */ flex-shrink: 0;
/* used to fix height on sticky header and footer */
position: sticky; position: sticky;
bottom: 0; bottom: 0;
z-index: 1000;
} }
#app-footer .ui.text.menu { #app-footer .ui.text.menu {
...@@ -68,66 +85,88 @@ body { ...@@ -68,66 +85,88 @@ body {
} }
#app-footer .ui.text.menu a.item:hover { #app-footer .ui.text.menu a.item:hover {
color: #1ab2b6; color: var(--primary-color, #008c86);
} }
#app-footer .ui.text.menu .item:not(:first-child) { #app-footer .ui.text.menu .item:not(:first-child) {
border-left: 1px solid rgba(34,36,38,.15); border-left: 1px solid rgba(34, 36, 38, .15);
} }
/* ---------------------------------- */ /* ---------------------------------- */
/* UTILS */ /* UTILS */
/* ---------------------------------- */ /* ---------------------------------- */
.inline { .inline {
display: inline; display: inline;
} }
.no-margin { .no-margin {
margin: 0 !important; margin: 0 !important;
} }
.margin-top { .margin-top {
margin-top: 1rem; margin-top: 1rem;
} }
.margin-bottom { .margin-bottom {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.tiny-margin { .tiny-margin {
margin: 0.1rem 0 0.1rem 0.1rem !important; margin: 0.1rem 0 0.1rem 0.1rem !important;
} }
.tiny-margin-left { .tiny-margin-left {
margin-left: 0.1rem !important; margin-left: 0.1rem !important;
} }
.ellipsis { .ellipsis {
text-overflow: ellipsis; text-overflow: ellipsis;
overflow: hidden; overflow: hidden;
} }
.nowrap { .nowrap {
white-space: nowrap; white-space: nowrap;
} }
.important-flex { .important-flex {
display: flex !important; display: flex !important;
} }
.pointer:hover { .pointer:hover {
cursor: pointer; cursor: pointer !important;
} }
.dimmer-anchor { .dimmer-anchor {
position: relative; position: relative;
} }
.full-width {
width: 100%;
}
/* ---------------------------------- */ /* ---------------------------------- */
/* MAIN */ /* MAIN */
/* ---------------------------------- */ /* ---------------------------------- */
.button-hover-orange:hover { .button-hover-orange:hover {
background: #fbbd08 !important; background: #fbbd08 !important;
} }
.button-hover-green:hover { .button-hover-green:hover {
background: #5bba21 !important; background: #5bba21 !important;
} }
.button-hover-red:hover { .button-hover-red:hover {
background: #ee2e24 !important; background: #ee2e24 !important;
} }
.ui.button.button-hover-red:hover, .ui.button.button-hover-red:hover i.icon,
.ui.button.button-hover-green:hover, .ui.button.button-hover-green:hover i.icon { .ui.button.button-hover-red:hover,
.ui.button.button-hover-red:hover i.icon,
.ui.button.button-hover-green:hover,
.ui.button.button-hover-green:hover i.icon {
color: #fff !important; color: #fff !important;
} }
.ui.button.button-hover-red:hover i.icon, .ui.button.button-hover-red:hover i.icon,
.ui.button.button-hover-green:hover i.icon { .ui.button.button-hover-green:hover i.icon {
transition: all 0.5s ease !important; transition: all 0.5s ease !important;
...@@ -138,7 +177,7 @@ body { ...@@ -138,7 +177,7 @@ body {
} }
.ui.horizontal.divider { .ui.horizontal.divider {
color: #1ab2b6!important; color: var(--primary-color, #008c86) !important;
padding-top: 1.5em; padding-top: 1.5em;
} }
...@@ -146,7 +185,7 @@ body { ...@@ -146,7 +185,7 @@ body {
display: none; display: none;
} }
.ui.dropdown .menu > .header { .ui.dropdown .menu>.header {
font-size: 1em; font-size: 1em;
text-transform: none; text-transform: none;
} }
...@@ -156,28 +195,28 @@ body { ...@@ -156,28 +195,28 @@ body {
overflow: auto; overflow: auto;
} }
.ui.dropdown .menu.text-wrap > .item { .ui.dropdown .menu.text-wrap>.item {
white-space: normal; white-space: normal;
word-wrap: normal; word-wrap: normal;
} }
.ui.checkbox.disabled > input { .ui.checkbox.disabled>input {
cursor: default !important; cursor: default !important;
} }
/* Add basemap view */ /* Add basemap view */
#form-layers .ui.buttons{ #form-layers button.button:not(:last-of-type) {
margin-bottom: 1rem; margin-right: 0.5em !important;
} }
#form-layers .errorlist{ #form-layers .errorlist {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;
color: #9f3a38; color: #9f3a38;
} }
#form-layers .infoslist{ #form-layers .infoslist {
list-style: none; list-style: none;
padding-left: 0; padding-left: 0;
color: #38989f; color: #38989f;
...@@ -198,9 +237,9 @@ body { ...@@ -198,9 +237,9 @@ body {
} }
/* Thicker borders for each basemap segment */ /* Thicker borders for each basemap segment */
#form-layers [data-segments=basemap_set-SEGMENTS] > .ui.segment { #form-layers [data-segments=basemap_set-SEGMENTS]>.ui.segment {
margin-bottom: 3rem; margin-bottom: 3rem;
border: 1px solid rgba(34,36,38,.30); border: 1px solid rgba(34, 36, 38, .30);
} }
...@@ -220,25 +259,17 @@ body { ...@@ -220,25 +259,17 @@ body {
/* ---------------------------------- */ /* ---------------------------------- */
/* LEAFLET DRAW TOOLBAR */ /* LEAFLET DRAW TOOLBAR */
/* ---------------------------------- */ /* ---------------------------------- */
.leaflet-draw-toolbar a.leaflet-draw-draw-circlemarker, .leaflet-draw-toolbar a.leaflet-draw-draw-circlemarker,
.leaflet-draw-toolbar a.leaflet-draw-draw-polyline, .leaflet-draw-toolbar a.leaflet-draw-draw-polyline,
.leaflet-draw-toolbar a.leaflet-draw-draw-polygon { .leaflet-draw-toolbar a.leaflet-draw-draw-polygon {
background-color: #FFA19E; background-color: #FFA19E;
}
/* ---------------------------------- */
/* LEAFLET*/
/* ---------------------------------- */
.leaflet-container {
background: #FFF;
} }
/* ---------------------------------- */ /* ---------------------------------- */
/* ERROR LIST */ /* ERROR LIST */
/* ---------------------------------- */ /* ---------------------------------- */
.errorlist { .errorlist {
margin-top: 1rem; margin-top: 1rem;
...@@ -250,16 +281,16 @@ body { ...@@ -250,16 +281,16 @@ body {
padding: 0; padding: 0;
} }
.errorlist > li { .errorlist>li {
list-style: none; list-style: none;
color: rgb(177, 55, 55); color: rgb(177, 55, 55);
border: thin solid rgb(197, 157, 157); border: thin solid rgb(197, 157, 157);
border-radius: 3px; border-radius: 3px;
background-color: rgb(250, 241, 242); background-color: rgb(250, 241, 242);
padding: 0.5rem 1rem; padding: 0.5rem 1rem;
} }
.infoslist > li { .infoslist>li {
list-style: none; list-style: none;
color: #38989f; color: #38989f;
border-radius: 3px; border-radius: 3px;
...@@ -268,7 +299,7 @@ body { ...@@ -268,7 +299,7 @@ body {
} }
/* ---------------------------------- */ /* ---------------------------------- */
/* PAGINATION */ /* PAGINATION */
/* ---------------------------------- */ /* ---------------------------------- */
.custom-pagination { .custom-pagination {
...@@ -278,12 +309,13 @@ body { ...@@ -278,12 +309,13 @@ body {
font-size: 1.2em; font-size: 1.2em;
} }
.custom-pagination > .page-item > .page-link { .custom-pagination>.page-item>.page-link {
border: none; border: none;
font-weight: 400; font-weight: 400;
color: #008080; color: #008080;
} }
.custom-pagination > .page-item.active > .page-link {
.custom-pagination>.page-item.active>.page-link {
color: #008080; color: #008080;
background-color: transparent; background-color: transparent;
font-weight: bolder; font-weight: bolder;
...@@ -291,18 +323,20 @@ body { ...@@ -291,18 +323,20 @@ body {
padding: 0.325em 0.75em; padding: 0.325em 0.75em;
pointer-events: none; pointer-events: none;
} }
.custom-pagination > .page-item.disabled > .page-link {
.custom-pagination>.page-item.disabled>.page-link {
opacity: 0.5; opacity: 0.5;
pointer-events: none; pointer-events: none;
} }
.custom-pagination > div > .page-item > .page-link { .custom-pagination>div>.page-item>.page-link {
border: none; border: none;
font-weight: 400; font-weight: 400;
color: #008080; color: #008080;
padding: 0.325em 0.75em; padding: 0.325em 0.75em;
} }
.custom-pagination > div > .page-item.active > .page-link {
.custom-pagination>div>.page-item.active>.page-link {
color: #008080; color: #008080;
background-color: transparent; background-color: transparent;
font-weight: bolder; font-weight: bolder;
...@@ -311,28 +345,32 @@ body { ...@@ -311,28 +345,32 @@ body {
padding: 0.325em 0.75em; padding: 0.325em 0.75em;
pointer-events: none; pointer-events: none;
} }
.custom-pagination > div > .page-item.disabled > .page-link {
.custom-pagination>div>.page-item.disabled>.page-link {
opacity: 0.5; opacity: 0.5;
padding: 0.325em 0.75em; padding: 0.325em 0.75em;
pointer-events: none; pointer-events: none;
} }
/* ---------------------------------- */ /* ---------------------------------- */
/* MULTISELECT */ /* MULTISELECT */
/* ---------------------------------- */ /* ---------------------------------- */
.multiselect { .multiselect {
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif) !important;
} }
.multiselect__tags { .multiselect__tags {
border: 1px solid #ced4da; border: 1px solid #ced4da;
border-radius: 0 !important; border-radius: 0 !important;
font-family: 'Roboto Condensed', Lato, 'Helvetica Neue', Arial, Helvetica, sans-serif !important; font-family: var(--font-family, 'Roboto Condensed', 'Lato', 'Helvetica Neue', Arial, Helvetica, sans-serif) !important;
font-size: 1rem !important; font-size: 1rem !important;
} }
.multiselect__tags > .multiselect__input {
.multiselect__tags>.multiselect__input {
border: none !important; border: none !important;
font-size: 1rem !important; font-size: 1rem !important;
overflow: hidden;
text-overflow: ellipsis;
} }
.multiselect__placeholder { .multiselect__placeholder {
...@@ -341,7 +379,10 @@ body { ...@@ -341,7 +379,10 @@ body {
padding-top: 0; padding-top: 0;
} }
.multiselect__single, .multiselect__tags, .multiselect__content, .multiselect__option { .multiselect__single,
.multiselect__tags,
.multiselect__content,
.multiselect__option {
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
...@@ -353,6 +394,11 @@ body { ...@@ -353,6 +394,11 @@ body {
.multiselect__select { .multiselect__select {
z-index: 1 !important; z-index: 1 !important;
} }
.multiselect__content-wrapper {
box-shadow: 0 2px 3px 0 rgba(34, 36, 38, .15);
}
.multiselect__clear { .multiselect__clear {
position: absolute; position: absolute;
right: 1px; right: 1px;
...@@ -362,7 +408,10 @@ body { ...@@ -362,7 +408,10 @@ body {
cursor: pointer; cursor: pointer;
z-index: 9; z-index: 9;
background-color: #fff; background-color: #fff;
padding: 0 4px;
text-align: center;
} }
.multiselect__spinner { .multiselect__spinner {
z-index: 2 !important; z-index: 2 !important;
background-color: #fff; background-color: #fff;
...@@ -370,10 +419,11 @@ body { ...@@ -370,10 +419,11 @@ body {
top: 2px; top: 2px;
} }
.menu.projects > .item > .multiselect { .menu.projects>.item>.multiselect {
min-height: 0px !important; min-height: 0px !important;
} }
.menu.projects > .item > .multiselect > .multiselect__tags {
.menu.projects>.item>.multiselect>.multiselect__tags {
min-height: 0px !important; min-height: 0px !important;
} }
...@@ -381,18 +431,28 @@ body { ...@@ -381,18 +431,28 @@ body {
background: #fff !important; background: #fff !important;
color: #35495e !important; color: #35495e !important;
} }
.multiselect__option--highlight { .multiselect__option--highlight {
background: #f3f3f3 !important; background: #f3f3f3 !important;
color: #35495e !important; color: #35495e !important;
} }
.multiselect__option--selected.multiselect__option--highlight { .multiselect__option--selected.multiselect__option--highlight {
background: #f3f3f3 !important; background: #f3f3f3 !important;
color: #35495e !important; color: #35495e !important;
} }
.multiselect__clear i.icon {
font-size: .75em;
color: #999;
margin: 0;
}
/* ---------------------------------- */ /* ---------------------------------- */
/* OVERRIDE SEMANTIC STYLES */ /* OVERRIDE SEMANTIC STYLES */
/* ---------------------------------- */ /* ---------------------------------- */
.ui.page.dimmer { /* keep the dimmer above the dropdown (z-index 1001: above the map)*/ .ui.page.dimmer {
/* keep the dimmer above the dropdown (z-index 1001: above the map)*/
z-index: 1002; z-index: 1002;
} }
\ No newline at end of file
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
.ol-popup { .ol-popup {
position: absolute; position: absolute;
background-color: white; background-color: white;
padding: 15px; padding: 15px 5px 15px 15px;
border-radius: 10px; border-radius: 10px;
bottom: 12px; bottom: 12px;
left: -120px; left: -120px;
...@@ -18,12 +18,12 @@ ...@@ -18,12 +18,12 @@
} }
.ol-popup #popup-content { .ol-popup #popup-content {
white-space: nowrap;
line-height: 1.3; line-height: 1.3;
font-size: .95em; font-size: .95em;
} }
.ol-popup #popup-content h4 { .ol-popup #popup-content h4 {
margin-right: .5em; margin-right: 15px;
margin-bottom: .5em;
color: #cacaca; color: #cacaca;
} }
.ol-popup #popup-content h4, .ol-popup #popup-content h4,
...@@ -34,6 +34,21 @@ ...@@ -34,6 +34,21 @@
.ol-popup #popup-content div { .ol-popup #popup-content div {
color: #434343; color: #434343;
} }
.ol-popup #popup-content .fields {
max-height: 200px;
overflow-y: scroll;
overflow-x: hidden;
padding-right: 10px;
display: block; /* overide .ui.form.fields rule conflict in featureEdit page */
margin: 0; /* overide .ui.form.fields rule conflict in featureEdit page */
}
.ol-popup #popup-content .divider {
margin-bottom: 0;
}
.ol-popup #popup-content #customFields h5 {
max-height: 20;
margin: .5em 0;
}
.ol-popup:after, .ol-popup:before { .ol-popup:after, .ol-popup:before {
top: 100%; top: 100%;
...@@ -109,3 +124,48 @@ ...@@ -109,3 +124,48 @@
.map-container > #popup.ol-popup { .map-container > #popup.ol-popup {
display: none; display: none;
} }
.ol-full-screen {
top: calc(1em + 60px);
right: 5px !important;
}
/* Geolocation button */
div.geolocation-container {
position: absolute;
right: 6px;
z-index: 9;
border: 2px solid rgba(0,0,0,.2);
background-clip: padding-box;
padding: 0;
border-radius: 4px;
}
button.button-geolocation {
border: none;
padding: 0;
margin: 0;
text-align: center;
background-color: #fff;
color: rgb(39, 39, 39);
width: 30px;
height: 30px;
font: 700 18px Lucida Console,Monaco,monospace;
border-radius: 2px;
line-height: 1.15;
cursor: pointer;
}
button.button-geolocation:hover {
background-color: #ebebeb;
}
button.button-geolocation.tracking {
background-color: rgba(255, 145, 0, 0.904);
color: #fff;
}
button.button-geolocation i {
margin: 0;
vertical-align: top; /* strangely top is the only value that center at middle */
background-image: url(../img/geolocation-icon.png);
background-size: cover;
width: 25px;
height: 25px;
}
\ No newline at end of file
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
border: 1px solid grey; border: 1px solid grey;
top: 0; top: 0;
position: absolute; position: absolute;
z-index: 9;
} }
.sidebar-layers { .sidebar-layers {
...@@ -60,7 +61,7 @@ ...@@ -60,7 +61,7 @@
.sidebar-container.expanded .layers-icon svg path, .sidebar-container.expanded .layers-icon svg path,
.sidebar-container.closing .layers-icon svg path { .sidebar-container.closing .layers-icon svg path {
fill: #00b5ad; fill: var(--primary-color, #00b5ad);
} }
@keyframes open-sidebar { @keyframes open-sidebar {
...@@ -130,7 +131,7 @@ ...@@ -130,7 +131,7 @@
} }
.layers-icon:hover svg path { .layers-icon:hover svg path {
fill: #00b5ad; fill: var(--primary-color, #00b5ad);
} }
.basemaps-title { .basemaps-title {
......
...@@ -16,70 +16,55 @@ ...@@ -16,70 +16,55 @@
:key="item.id" :key="item.id"
class="item" class="item"
> >
<div class="content ellipsis nowrap"> <div :class="['content', { 'ellipsis nowrap': item.related_feature.title }]">
<span v-if="item.event_type === 'create'"> {{ getNotificationName(item.event_type, item.object_type) }}
<a
v-if="item.object_type === 'feature'" <div
:href="modifyUrl(item.related_feature.feature_url || item.project_url)" v-if="item.object_type === 'project'"
>
Signalement créé
</a>
<a
v-else-if="item.object_type === 'comment'"
:href="modifyUrl(item.related_feature.feature_url)"
>
Commentaire créé
</a>
<a
v-else-if="item.object_type === 'attachment'"
:href="modifyUrl(item.related_feature.feature_url)"
>
Pièce jointe ajoutée
</a>
<a
v-else-if="item.object_type === 'project'"
:href="modifyUrl(item.project_url)"
>
Projet créé
</a>
</span>
<span v-else-if="item.event_type === 'update'">
<a
v-if="item.object_type === 'feature'"
:href="modifyUrl(item.related_feature.feature_url || item.project_url)"
>
Signalement mis à jour
</a>
<a
v-else-if="item.object_type === 'project'"
:href="modifyUrl(item.project_url)"
>à Projet mis à jour
</a>
</span>
<span v-else-if="item.event_type === 'delete'">
<span v-if="item.object_type === 'feature'">
Signalement supprimé({{ item.data.feature_title }})
</span>
<em v-else>Événement inconnu</em>
</span>
<span
v-if="item.object_type !== 'project'"
class="meta"
> >
({{ item.related_feature.title ? <router-link
item.related_feature.title : v-if="item.project_title"
'signalement supprimé' :to="{
}}) name: 'project_detail',
</span> params: { slug: item.project_slug },
}"
>
{{ item.project_title }}
</router-link>
<span
v-else
class="meta"
><del>{{ item.project_slug }}</del>&nbsp;(supprimé)</span>
</div>
<div v-else>
<FeatureFetchOffsetRoute
v-if="item.related_feature.deletion_on === 'None'"
:feature-id="item.feature_id"
:properties="{
feature_type: {
slug: item.feature_type_slug
},
title: item.related_feature.title,
...item
}"
/>
<span
v-else
class="meta"
><del>{{ item.data.feature_title || item.feature_id }}</del>&nbsp;(supprimé)</span>
</div>
<div class="description"> <div class="description">
<em>[ {{ item.created_on }} <em>[ {{ item.created_on }}
<span v-if="user.is_authenticated"> <span v-if="user">
, par {{ item.display_user }} , par {{ item.display_user }}
</span> </span>
]</em> ]</em>
</div> </div>
</div> </div>
</div> </div>
<em <em
v-if="!events || events.length === 0" v-if="!events || events.length === 0"
>Aucune notification pour le moment.</em> >Aucune notification pour le moment.</em>
...@@ -103,18 +88,28 @@ ...@@ -103,18 +88,28 @@
> >
<div class="content"> <div class="content">
<div> <div>
<a <FeatureFetchOffsetRoute
v-if="item.related_feature && item.related_feature.feature_url" v-if="item.related_feature.deletion_on === 'None'"
:href="modifyUrl(item.related_feature.feature_url)" :feature-id="item.feature_id"
>{{ item.related_feature.title }}</a> :properties="{
feature_type: {
slug: item.feature_type_slug
},
title: item.related_feature.title,
...item
}"
/>
<span <span
v-else v-else
class="meta" class="meta"
><del>{{ item.data.feature_title }}</del>&nbsp;(supprimé)</span> >
<del>{{ item.data.feature_title || item.feature_id }}</del>&nbsp;(supprimé)
</span>
</div> </div>
<div class="description"> <div class="description">
<em>[ {{ item.created_on }} <em>[ {{ item.created_on }}
<span v-if="user.is_authenticated"> <span v-if="user">
, par {{ item.display_user }} , par {{ item.display_user }}
</span> </span>
]</em> ]</em>
...@@ -144,19 +139,35 @@ ...@@ -144,19 +139,35 @@
> >
<div class="content"> <div class="content">
<div> <div>
<a <FeatureFetchOffsetRoute
:href="modifyUrl(item.related_feature.feature_url)" v-if="item.related_feature.deletion_on === 'None'"
>"{{ item.related_comment.comment }}"</a> :feature-id="item.feature_id"
:properties="{
feature_type: {
slug: item.feature_type_slug
},
title: quoteComment(item.data.comment),
...item
}"
/>
<span
v-else
class="meta"
>
<del>{{ item.data.comment }}</del>&nbsp;(supprimé)
</span>
</div> </div>
<div class="description"> <div class="description">
<em>[ {{ item.created_on }} <em>[ {{ item.created_on }}
<span v-if="user.is_authenticated"> <span v-if="user">
, par {{ item.display_user }} , par {{ item.display_user }}
</span> </span>
]</em> ]</em>
</div> </div>
</div> </div>
</div> </div>
<em <em
v-if="!comments || comments.length === 0" v-if="!comments || comments.length === 0"
>Aucun commentaire pour le moment.</em> >Aucun commentaire pour le moment.</em>
...@@ -171,11 +182,15 @@ ...@@ -171,11 +182,15 @@
import { mapState } from 'vuex'; import { mapState } from 'vuex';
import miscAPI from '@/services/misc-api'; import miscAPI from '@/services/misc-api';
import FeatureFetchOffsetRoute from '@/components/Feature/FeatureFetchOffsetRoute';
export default { export default {
name: 'UserActivity', name: 'UserActivity',
components: {
FeatureFetchOffsetRoute,
},
data() { data() {
return { return {
events: [], events: [],
...@@ -196,6 +211,8 @@ export default { ...@@ -196,6 +211,8 @@ export default {
created(){ created(){
this.getEvents(); this.getEvents();
// unset project to avoid interfering with generating query in feature links
this.$store.commit('projects/SET_PROJECT', null);
}, },
methods: { methods: {
...@@ -208,12 +225,37 @@ export default { ...@@ -208,12 +225,37 @@ export default {
}); });
}, },
modifyUrl(url) { getNotificationName(eventType, objectType) {
if (url && this.isSharedProject) { if (eventType === 'create') {
return url.replace('projet', 'projet-partage'); if (objectType === 'feature') {
return 'Signalement créé';
} else if (objectType === 'comment') {
return 'Commentaire créé';
} else if (objectType === 'attachment') {
return 'Pièce jointe ajoutée';
} else if (objectType === 'project') {
return 'Projet créé';
}
} else if (eventType === 'update') {
if (objectType === 'feature') {
return 'Signalement mis à jour';
} else if (objectType === 'project') {
return 'Projet mis à jour';
}
} else if (eventType === 'delete') {
if (objectType === 'feature') {
return 'Signalement supprimé';
} else if (objectType === 'project') {
return 'Projet mis à jour';
} else {
return 'Événement inconnu';
}
} }
return url; },
}
quoteComment(comment) {
return `"${comment}"`;
},
} }
}; };
......
...@@ -49,19 +49,41 @@ ...@@ -49,19 +49,41 @@
</div> </div>
</div> </div>
</div> </div>
<div
v-if="qrcode"
class="qrcode"
>
<img
:src="qrcode"
alt="qrcode"
>
<p>
Ce QR code vous permet de vous connecter à l'application mobile GéoContrib (bientôt disponible)
</p>
</div>
</div> </div>
</template> </template>
<script> <script>
import { mapState } from 'vuex'; import { mapState, mapActions } from 'vuex';
import QRCode from 'qrcode';
export default { export default {
name: 'UserProfile', name: 'UserProfile',
data() {
return {
qrcode: null
};
},
computed: { computed: {
...mapState([ ...mapState([
'user' 'configuration',
'user',
'userToken'
]), ]),
userFullname() { userFullname() {
...@@ -70,7 +92,46 @@ export default { ...@@ -70,7 +92,46 @@ export default {
} }
return null; return null;
}, },
},
created() {
this.GET_USER_TOKEN()
.then(async () => {
try {
const qrcodeData = {
url: `${this.configuration.VUE_APP_DJANGO_BASE}/geocontrib/`,
token: this.userToken
};
this.qrcode = await QRCode.toDataURL(JSON.stringify(qrcodeData));
} catch (err) {
console.error(err);
}
})
.catch((err) => {
console.error(err);
});
},
methods: {
...mapActions([
'GET_USER_TOKEN'
])
} }
}; };
</script> </script>
<style scoped lang="less">
.qrcode {
img {
display: block;
margin: auto;
width: 12rem;
}
p {
font-size: 0.8rem;
font-style: italic;
text-align: center;
}
}
</style>
...@@ -107,8 +107,7 @@ ...@@ -107,8 +107,7 @@
<Pagination <Pagination
v-if="count" v-if="count"
:nb-pages="Math.ceil(count/10)" :nb-pages="Math.ceil(count/10)"
:on-page-change="SET_CURRENT_PAGE" @page-update="changePage"
@change-page="changePage"
/> />
</div> </div>
</div> </div>
......
<template> <template>
<div id="app-header"> <div
id="app-header"
:class="$route.name"
>
<div class="menu container"> <div class="menu container">
<div class="ui inverted icon menu"> <div class="ui inverted icon menu">
<router-link <router-link
...@@ -8,7 +11,7 @@ ...@@ -8,7 +11,7 @@
:class="['header item', {disable: isSharedProject}]" :class="['header item', {disable: isSharedProject}]"
> >
<img <img
class="ui mini right spaced image" class="ui right spaced image"
alt="Logo de l'application" alt="Logo de l'application"
:src="logo" :src="logo"
> >
...@@ -40,8 +43,8 @@ ...@@ -40,8 +43,8 @@
/> />
<div <div
:class="[ :class="[
'menu dropdown-list', 'menu dropdown-list transition',
{ 'visible transition': menuIsOpen }, { 'visible': menuIsOpen },
]" ]"
style="z-index: 401" style="z-index: 401"
> >
...@@ -73,9 +76,7 @@ ...@@ -73,9 +76,7 @@
</router-link> </router-link>
<router-link <router-link
v-if=" v-if="
project && isOnline && project && isOnline && hasAdminRights"
(user.is_administrator || user.is_superuser || isAdmin)
"
:to="{ :to="{
name: 'project_mapping', name: 'project_mapping',
params: { slug: project.slug }, params: { slug: project.slug },
...@@ -89,9 +90,7 @@ ...@@ -89,9 +90,7 @@
</router-link> </router-link>
<router-link <router-link
v-if=" v-if="
project && isOnline && project && isOnline && hasAdminRights"
(user.is_administrator || user.is_superuser || isAdmin)
"
:to="{ :to="{
name: 'project_members', name: 'project_members',
params: { slug: project.slug }, params: { slug: project.slug },
...@@ -120,7 +119,7 @@ ...@@ -120,7 +119,7 @@
class="item ui label vertical no-hover" class="item ui label vertical no-hover"
> >
<!-- super user rights are higher than others --> <!-- super user rights are higher than others -->
{{ user.is_superuser ? 'Administrateur' : USER_LEVEL_PROJECTS[project.slug] }} {{ user && user.is_superuser ? 'Administrateur' : USER_LEVEL_PROJECTS[project.slug] }}
<br> <br>
</div> </div>
<div <div
...@@ -152,6 +151,7 @@ ...@@ -152,6 +151,7 @@
v-else v-else
class="item" class="item"
:href="SSO_LOGIN_URL" :href="SSO_LOGIN_URL"
target="_self"
>Se connecter</a> >Se connecter</a>
</div> </div>
</div> </div>
...@@ -165,7 +165,7 @@ ...@@ -165,7 +165,7 @@
<div class="desktop flex push-right-desktop"> <div class="desktop flex push-right-desktop">
<div <div
v-if="!isOnline" v-if="!isOnline"
class="item" class="item network-icon"
> >
<span <span
data-tooltip="Vous êtes hors-ligne, data-tooltip="Vous êtes hors-ligne,
...@@ -196,7 +196,7 @@ ...@@ -196,7 +196,7 @@
class="item ui label vertical no-hover" class="item ui label vertical no-hover"
> >
<!-- super user rights are higher than others --> <!-- super user rights are higher than others -->
{{ user.is_superuser ? 'Administrateur' : USER_LEVEL_PROJECTS[project.slug] }} {{ user && user.is_superuser ? 'Administrateur' : USER_LEVEL_PROJECTS[project.slug] }}
<br> <br>
</div> </div>
<div <div
...@@ -228,6 +228,7 @@ ...@@ -228,6 +228,7 @@
v-else v-else
class="item log-item" class="item log-item"
:href="SSO_LOGIN_URL" :href="SSO_LOGIN_URL"
target="_self"
>Se connecter</a> >Se connecter</a>
</div> </div>
</div> </div>
...@@ -273,13 +274,17 @@ export default { ...@@ -273,13 +274,17 @@ export default {
return this.configuration.VUE_APP_APPLICATION_NAME; return this.configuration.VUE_APP_APPLICATION_NAME;
}, },
APPLICATION_ABSTRACT() { APPLICATION_ABSTRACT() {
return this.$store.state.configuration.VUE_APP_APPLICATION_ABSTRACT; return this.configuration.VUE_APP_APPLICATION_ABSTRACT;
}, },
DISABLE_LOGIN_BUTTON() { DISABLE_LOGIN_BUTTON() {
return this.configuration.VUE_APP_DISABLE_LOGIN_BUTTON; return this.configuration.VUE_APP_DISABLE_LOGIN_BUTTON;
}, },
SSO_LOGIN_URL() { SSO_LOGIN_URL() {
return this.configuration.VUE_APP_LOGIN_URL; if (this.configuration.VUE_APP_LOGIN_URL) {
// add a next parameter with the pathname as expected by OGS to redirect after login
return `${this.configuration.VUE_APP_LOGIN_URL}/?next=${encodeURIComponent(window.location.pathname)}`;
}
return null;
}, },
logo() { logo() {
...@@ -297,6 +302,9 @@ export default { ...@@ -297,6 +302,9 @@ export default {
? true ? true
: false; : false;
}, },
hasAdminRights() {
return this.user && (this.user.is_administrator || this.user.is_superuser) || this.isAdmin;
},
isSharedProject() { isSharedProject() {
return this.$route.path.includes('projet-partage'); return this.$route.path.includes('projet-partage');
} }
...@@ -326,6 +334,14 @@ export default { ...@@ -326,6 +334,14 @@ export default {
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>
.menu.container .header {
padding-top: 5px !important;
padding-bottom: 5px !important;
&> img {
max-height: 30px;
}
}
.vertical { .vertical {
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
...@@ -345,6 +361,10 @@ export default { ...@@ -345,6 +361,10 @@ export default {
text-align: center; text-align: center;
} }
.network-icon {
padding: .5rem !important;
}
.crossed-out { .crossed-out {
position: relative; position: relative;
padding: .2em; padding: .2em;
...@@ -361,13 +381,13 @@ export default { ...@@ -361,13 +381,13 @@ export default {
} }
} }
@media screen and (max-width: 985px) { @media screen and (max-width: 1110px) {
.abstract{ .abstract{
display: none !important; display: none !important;
} }
} }
@media screen and (min-width: 560px) { @media screen and (min-width: 726px) {
.mobile { .mobile {
display: none !important; display: none !important;
} }
...@@ -382,23 +402,36 @@ export default { ...@@ -382,23 +402,36 @@ export default {
} }
} }
@media screen and (max-width: 590px) { @media screen and (max-width: 725px) {
.desktop { .desktop {
display: none !important; display: none !important;
} }
div.dropdown-list { div.dropdown-list {
width: 100vw; width: 100vw;
left: -70px !important; /* should be the same than belows */
} }
.menu.container a.header { .menu.container .header {
width: 70px; //width: 70px;
width: 100%;
} }
.menu.container a.header > img { #app-header:not(.index) {
/* make the logo disappear on scroll */
position: sticky;
top: -90px;
height: 80px;
.menu.container {
/* make the logo disappear on scroll */
height: 30px;
position: sticky;
top: 0;
}
}
.menu.container .header > img {
margin: 0; margin: 0;
margin: auto;
max-width: 100%;
} }
#menu-dropdown { #menu-dropdown {
width: calc(100vw - 70px); width: 100%;
//justify-content: space-between;
} }
#menu-dropdown > span { #menu-dropdown > span {
text-overflow: ellipsis; text-overflow: ellipsis;
...@@ -418,9 +451,11 @@ export default { ...@@ -418,9 +451,11 @@ export default {
/* keep above map controls or buttons */ /* keep above map controls or buttons */
#app-header { #app-header {
z-index: 1001; z-index: 9999;
.menu.container .ui.inverted.icon.menu { /* avoid adding space when messages are displayed */ .menu.container .ui.inverted.icon.menu { /* avoid adding space when messages are displayed */
margin: 0; margin: 0;
display: flex;
flex-wrap: wrap;
} }
} }
.ui.menu .ui.dropdown .menu { .ui.menu .ui.dropdown .menu {
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<span class="italic">{{ selected[1] }}</span> <span class="italic">{{ selected[1] }}</span>
</div> </div>
<div v-else> <div v-else>
{{ selected }} {{ selectedDisplay }}
</div> </div>
</div> </div>
<i <i
...@@ -44,7 +44,7 @@ ...@@ -44,7 +44,7 @@
:key="option + index" :key="option + index"
:class="[ :class="[
filteredOptions ? 'item' : 'message', filteredOptions ? 'item' : 'message',
{ 'active selected': option.name === selected }, { 'active selected': option.name === selected || option.id === selected },
]" ]"
@click="select(index)" @click="select(index)"
> >
...@@ -114,6 +114,14 @@ export default { ...@@ -114,6 +114,14 @@ export default {
placehold() { placehold() {
return this.input ? '' : this.placeholder; return this.input ? '' : this.placeholder;
}, },
selectedDisplay() { // for project attributes, option are object and selected is an id
if (this.options[0] && this.options[0].name) {
const option = this.options.find(opt => opt.id === this.selected);
if (option) return option.name;
}
return this.selected;
}
}, },
created() { created() {
...@@ -167,7 +175,7 @@ export default { ...@@ -167,7 +175,7 @@ export default {
}, },
clear() { clear() {
if (this.clearable) { if (this.clearable && this.selected) {
this.input = ''; this.input = '';
this.$emit('update:selection', ''); this.$emit('update:selection', '');
if (this.isOpen) { if (this.isOpen) {
......
<template>
<div
:class="['field', { 'disabled': field.disabled }]"
:data-field_type="field.field_type"
data-test="extra-form"
name="extra-form"
>
<div
v-if="field && field.field_type === 'boolean'"
:class="['ui checkbox', { 'disabled': field.disabled }]"
>
<!-- JSON.parse is used in case of receiving a string 'true' or 'false'-->
<input
:id="field.name"
type="checkbox"
:checked="JSON.parse(field.value)"
:name="field.name"
@change="updateStore_extra_form"
>
<label :for="field.name">
{{ displayLabels ? field.label : '' }}
</label>
</div>
<template v-else>
<label
v-if="displayLabels"
:for="field.name"
:class="{ required: field.is_mandatory }"
>
{{ field.label }}
</label>
<input
v-if="field && field.field_type === 'char'"
:id="field.name"
:value="field.value"
type="text"
:name="field.name"
:required="field.is_mandatory"
@blur="updateStore_extra_form"
>
<textarea
v-else-if="field && field.field_type === 'text'"
:value="field.value"
:name="field.name"
:required="field.is_mandatory"
rows="3"
@blur="updateStore_extra_form"
/>
<input
v-else-if="field && field.field_type === 'integer'"
:id="field.name"
:value="field.value"
type="number"
:name="field.name"
:required="field.is_mandatory"
@change="updateStore_extra_form"
>
<input
v-else-if="field && field.field_type === 'decimal'"
:id="field.name"
:value="field.value"
type="number"
step=".01"
:name="field.name"
:required="field.is_mandatory"
@change="updateStore_extra_form"
>
<input
v-else-if="field && field.field_type === 'date'"
:id="field.name"
:value="field.value"
type="date"
:name="field.name"
:required="field.is_mandatory"
@change="updateStore_extra_form"
>
<Dropdown
v-else-if="field && (field.field_type === 'list' || field.field_type === 'notif_group')"
:options="field.field_type === 'notif_group' ? usersGroupsFeatureOptions : field.options"
:selected="selected_extra_form_list"
:selection.sync="selected_extra_form_list"
:required="field.is_mandatory"
:search="true"
:clearable="true"
/>
<div
v-else-if="field && field.field_type === 'multi_choices_list'"
class="checkbox_list"
>
<div
v-for="option in field.options"
:key="option.id || option"
class="ui checkbox"
>
<input
:id="option.id || option"
type="checkbox"
:checked="field.value && field.value.includes(option.id || option)"
:name="option.id || option"
@change="selectMultipleCheckbox"
>
<label :for="option.id || option">
{{ option.name || option }}
</label>
</div>
</div>
<Multiselect
v-else-if="field && field.field_type === 'pre_recorded_list'"
v-model="selectedPrerecordedValue"
:options="selectedPrerecordedListValues[field.options[0]] || []"
:options-limit="10"
:allow-empty="!field.is_mandatory"
track-by="label"
label="label"
:reset-after="false"
select-label=""
selected-label=""
deselect-label=""
:searchable="true"
:placeholder="'Recherchez une valeur de la liste pré-définie ...'"
:show-no-results="true"
:loading="loadingPrerecordedListValues"
:clear-on-select="false"
:preserve-search="false"
@search-change="search"
@select="selectPrerecordedValue"
>
<template slot="clear">
<div
v-if="selectedPrerecordedValue"
class="multiselect__clear"
@click.prevent.stop="clearPrerecordedValue"
>
<i
class="close icon"
aria-hidden="true"
/>
</div>
</template>
<span slot="noResult">
Aucun résultat.
</span>
<span slot="noOptions">
Saisissez les premiers caractères ...
</span>
</Multiselect>
</template>
</div>
</template>
<script>
import { mapState, mapActions, mapMutations, mapGetters } from 'vuex';
import Multiselect from 'vue-multiselect';
import Dropdown from '@/components/Dropdown.vue';
export default {
name: 'ExtraForm',
components: {
Dropdown,
Multiselect
},
props: {
field: {
type: Object,
default: null,
},
useValueOnly: {
type: Boolean,
default: false,
}
},
data() {
return {
error: null,
prerecordedListSearchQuery: null,
loadingPrerecordedListValues: false,
selectedPrerecordedValue: null,
selectedMultipleCheckbox: [],
};
},
computed: {
...mapState('feature-type', [
'selectedPrerecordedListValues'
]),
...mapState('feature', [
'extra_forms',
]),
...mapGetters(['usersGroupsFeatureOptions']),
selected_extra_form_list: {
get() {
if (this.field.field_type === 'notif_group') {
const usersGroup = this.usersGroupsFeatureOptions.find((group) => group.value === this.field.value);
return usersGroup ? usersGroup.name : '';
}
return this.field.value || '';
},
set(newValue) {
//* set the value selected in the dropdown
if (this.useValueOnly) {
this.$emit('update:value', newValue.id || newValue);
} else {
const newExtraForm = this.field;
newExtraForm['value'] = this.field.field_type === 'notif_group' ? newValue.value : newValue;
this.$store.commit('feature/UPDATE_EXTRA_FORM', newExtraForm);
}
},
},
displayLabels() {
return this.$route.name === 'editer-signalement' || this.$route.name === 'ajouter-signalement' || this.$route.name === 'editer-attribut-signalement';
},
},
watch: {
/**
* Watches for changes in the 'field.value' and updates the form state accordingly.
* This watcher handles specific field types, ensuring their values are correctly initialized
* and updated in scenarios like fast edition mode where certain values might not be auto-refreshed.
*
* @param {*} newValue - The new value of the field.
* @param {*} oldValue - The previous value of the field before the change.
*/
'field.value': function(newValue, oldValue) {
// Check if the field object exists.
if (this.field) {
// Handle pre-recorded list fields specifically.
if (this.field.field_type === 'pre_recorded_list') {
// Update the form value if both new and old values are defined and different,
// or if either value is undefined, indicating a change.
if ((newValue && oldValue && (newValue.label !== oldValue.label || newValue !== oldValue))
|| !newValue || !oldValue) {
this.initPrerecordedXform(); // Reinitialize the field to reflect the updated value.
}
} else if (this.field.field_type === 'multi_choices_list') {
// For multi-choice lists, reinitialize the field if the array values have changed.
// This is crucial in fast edition modes to prevent overriding the current value with a stale value.
this.initMultipleCheckboxXform();
}
// Reset any error states for the field.
this.error = null;
}
},
prerecordedListSearchQuery(newValue) {
this.loadingPrerecordedListValues = true;
this.GET_SELECTED_PRERECORDED_LIST_VALUES({
name: this.field.options[0],
pattern: newValue
})
.then(() => {
this.loadingPrerecordedListValues = false;
})
.catch(() => {
this.loadingPrerecordedListValues = false;
});
}
},
created() {
if (this.field) {
if (this.field.field_type === 'pre_recorded_list') {
this.initPrerecordedXform();
} else if (this.field.field_type === 'multi_choices_list') {
this.initMultipleCheckboxXform();
}
}
},
mounted() {
// autoset field to false if is a boolean, since user doesn't need to select it, when false value is expected
if (this.field.field_type === 'boolean' && (this.field.value === undefined || this.field.value === null)) {
this.updateStore_extra_form(false);
}
},
methods: {
...mapActions('feature-type', [
'GET_SELECTED_PRERECORDED_LIST_VALUES'
]),
...mapMutations('feature', [
'UPDATE_EXTRA_FORM',
'SET_EXTRA_FORMS',
]),
initMultipleCheckboxXform() {
this.selectedMultipleCheckbox = typeof this.field.value === 'string' ? this.field.value.split(',') : this.field.value || [];
},
initPrerecordedXform() {
const { options, value } = this.field;
this.loadingPrerecordedListValues = true;
this.GET_SELECTED_PRERECORDED_LIST_VALUES({
name: options[0],
pattern: ''
})
.then(() => {
this.loadingPrerecordedListValues = false;
})
.catch(() => {
this.loadingPrerecordedListValues = false;
});
if (value) {
this.selectedPrerecordedValue = { label: value.label ? value.label : value };
} else {
this.selectedPrerecordedValue = null;
}
},
/**
* Updates the Vuex store or component state with the new value for a form field.
* This function handles different types of form fields including boolean, multi-choice lists, and others.
*
* @param {Event|*} val - The new value or an event object.
*/
updateStore_extra_form(val) {
// Check if the field object is defined.
if (this.field) {
let newValue;
// If the function is triggered by an event from a template input.
if (val && val.target) {
// For boolean fields (like checkboxes), use the 'checked' property.
if (this.field.field_type === 'boolean') {
newValue = val.target.checked;
} else {
// For other input types, use the 'value' property.
newValue = val.target.value;
}
} else if (this.field.field_type === 'multi_choices_list') {
// For multi-choice lists, the value is stored in component state.
newValue = this.selectedMultipleCheckbox;
} else {
// If the function is called directly with a value (not from an event).
newValue = val;
}
// Set the new value for the field.
if (this.useValueOnly) {
// If the component is used to update directly a returned value, emit an event with the new value.
this.$emit('update:value', newValue);
} else {
// Otherwise, update the Vuex store with the new value for the extra form field.
this.UPDATE_EXTRA_FORM({ ...this.field, value: newValue });
}
}
},
checkForm() {
let isValid = true;
if (this.field && this.field.is_mandatory && !this.field.value) {
isValid = false;
this.error = 'Ce champ est obligatoire';
} else {
this.error = null;
}
return isValid;
},
search(text) {
this.prerecordedListSearchQuery = text;
},
selectPrerecordedValue(e) {
this.selectedPrerecordedValue = e;
this.prerecordedListSearchQuery = null;
this.updateStore_extra_form({ target: { value: this.selectedPrerecordedValue.label } });
},
clearPrerecordedValue() {
this.selectedPrerecordedValue = null;
this.prerecordedListSearchQuery = null;
this.updateStore_extra_form({ target: { value: null } });
},
/**
* Handles the selection and deselection of checkboxes in a form.
* This function updates an array to track the selected checkboxes by their names.
* It's typically called on the change event of each checkbox.
*
* @param {Event} e - The event object from the checkbox input.
*/
selectMultipleCheckbox(e) {
// Destructure the 'checked' status and 'name' of the checkbox from the event target.
const { checked, name } = e.target;
// If the checkbox is checked, add its name to the array of selected checkboxes.
// Cloning the array to allow unsaved changes detection (it wasn't working with Array.push)
if (checked) {
this.selectedMultipleCheckbox = [...this.selectedMultipleCheckbox, name];
} else {
// If the checkbox is unchecked, remove its name from the array.
this.selectedMultipleCheckbox = this.selectedMultipleCheckbox.filter((el) => el !== name);
}
// Call a method to update the Vuex store or component state with the latest selection.
this.updateStore_extra_form();
},
},
};
</script>
<style lang="less" scoped>
label.required:after {
content: ' *';
color: rgb(209, 0, 0);
}
.checkbox_list {
display: flex;
flex-direction: column;
.ui.checkbox {
margin: .25rem 0;
font-weight: normal;
}
}
</style>
<style>
.multiselect__placeholder {
position: absolute;
width: calc(100% - 48px);
overflow: hidden;
text-overflow: ellipsis;
}
.multiselect__tags {
position: relative;
}
/* keep font-weight from overide of semantic classes */
.multiselect__placeholder, .multiselect__content, .multiselect__tags {
font-weight: initial !important;
}
/* keep placeholder eigth */
.multiselect .multiselect__placeholder {
margin-bottom: 9px !important;
padding-top: 1px;
}
/* keep placeholder height when opening dropdown without selection */
input.multiselect__input {
padding: 3px 0 0 0 !important;
}
/* keep placeholder height when opening dropdown with already a value selected */
.multiselect__tags .multiselect__single {
padding: 1px 0 0 0 !important;
margin-bottom: 9px;
}
</style>
...@@ -142,6 +142,21 @@ ...@@ -142,6 +142,21 @@
{{ comment_form.attachment_file.errors }} {{ comment_form.attachment_file.errors }}
</div> </div>
</div> </div>
<div
v-if="enableKeyDocNotif"
class="field"
>
<div class="ui checkbox">
<input
id="is_key_document"
v-model="comment_form.attachment_file.isKeyDocument"
class="hidden"
name="is_key_document"
type="checkbox"
>
<label for="is_key_document">Envoyer une notification de publication aux abonnés du projet</label>
</div>
</div>
<ul <ul
v-if="comment_form.attachment_file.errors" v-if="comment_form.attachment_file.errors"
class="errorlist" class="errorlist"
...@@ -181,6 +196,10 @@ export default { ...@@ -181,6 +196,10 @@ export default {
default: () => { default: () => {
return []; return [];
} }
},
enableKeyDocNotif: {
type: Boolean,
default: false,
} }
}, },
...@@ -191,6 +210,7 @@ export default { ...@@ -191,6 +210,7 @@ export default {
errors: null, errors: null,
title: '', title: '',
file: null, file: null,
isKeyDocument: false
}, },
comment: { comment: {
id_for_label: 'add-comment', id_for_label: 'add-comment',
...@@ -233,20 +253,23 @@ export default { ...@@ -233,20 +253,23 @@ export default {
if (this.validateForm()) { if (this.validateForm()) {
featureAPI featureAPI
.postComment({ .postComment({
featureId: this.currentFeature.feature_id, featureId: this.currentFeature.feature_id || this.currentFeature.id,
comment: this.comment_form.comment.value, comment: this.comment_form.comment.value,
}) })
.then((response) => { .then((response) => {
if (response && this.comment_form.attachment_file.file) { if (response && this.comment_form.attachment_file.file) {
featureAPI featureAPI
.postCommentAttachment({ .postCommentAttachment({
featureId: this.currentFeature.feature_id, featureId: this.currentFeature.feature_id || this.currentFeature.id,
file: this.comment_form.attachment_file.file, file: this.comment_form.attachment_file.file,
fileName: this.comment_form.attachment_file.fileName, fileName: this.comment_form.attachment_file.fileName,
title: this.comment_form.attachment_file.title, title: this.comment_form.attachment_file.title,
isKeyDocument: this.comment_form.attachment_file.isKeyDocument,
commentId: response.data.id, commentId: response.data.id,
}) })
.then(() => { .then(() => {
// Reset isKeyDocument to default
this.comment_form.attachment_file.isKeyDocument = false;
this.confirmComment(); this.confirmComment();
}); });
} else { } else {
......
...@@ -21,42 +21,42 @@ ...@@ -21,42 +21,42 @@
v-else v-else
class="ellipsis" class="ellipsis"
> >
{{ currentFeature.title || currentFeature.feature_id }} {{ currentFeature.properties ?
currentFeature.properties.title : currentFeature.id }}
</div> </div>
<div <div
id="feature-actions" id="feature-actions"
class="ui icon compact buttons" class="ui icon compact buttons"
> >
<div> <div
<router-link v-if="queryparams"
v-if="displayToListButton" class="fast_browsing"
id="feature-detail-to-features-list" >
:to="{ <div>
name: 'liste-signalements', <div>
params: { slug: $route.params.slug }, <strong>
}" Tri en cours:
custom </strong>
> <span>
<div class="ui button tiny-margin teal"> par&nbsp;{{ currentSort }}
<i </span>
class="ui icon arrow right"
aria-hidden="true"
/>
Retour à la liste des signalements
</div> </div>
</router-link> <div>
</div> <strong>
<div> Filtre en cours:
</strong>
<span>
{{ currentFilters }}
</span>
</div>
</div>
<span <span
v-if="featuresCount" class="feature-count"
id="feature-count"
class="ui button tiny-margin basic disabled no-opacity"
> >
{{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }} {{ parseInt($route.query.offset) + 1 }} sur {{ featuresCount }}
</span> </span>
<button <button
v-if="queryparams"
id="previous-feature" id="previous-feature"
:class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]" :class="['ui button button-hover-green tiny-margin', { disabled: queryparams.previous < 0 }]"
data-tooltip="Voir le précédent signalement" data-tooltip="Voir le précédent signalement"
...@@ -69,7 +69,6 @@ ...@@ -69,7 +69,6 @@
/> />
</button> </button>
<button <button
v-if="queryparams"
id="next-feature" id="next-feature"
:class="[ :class="[
'ui button button-hover-green tiny-margin', 'ui button button-hover-green tiny-margin',
...@@ -99,14 +98,14 @@ ...@@ -99,14 +98,14 @@
aria-hidden="true" aria-hidden="true"
/> />
</button> </button>
<router-link <router-link
v-if="permissions && permissions.can_create_feature" v-if="permissions && permissions.can_create_feature
&& (featureType && !featureType.geom_type.includes('multi'))"
id="add-feature" id="add-feature"
:to="{ :to="{
name: 'ajouter-signalement', name: 'ajouter-signalement',
params: { params: {
slug_type_signal: $route.params.slug_type_signal || featureType.slug, slug_type_signal: $route.params.slug_type_signal || featureType ? featureType.slug : '',
}, },
}" }"
class="ui button button-hover-green tiny-margin" class="ui button button-hover-green tiny-margin"
...@@ -126,7 +125,7 @@ ...@@ -126,7 +125,7 @@
name: 'editer-signalement', name: 'editer-signalement',
params: { params: {
slug_signal: slugSignal, slug_signal: slugSignal,
slug_type_signal: $route.params.slug_type_signal || featureType.slug, slug_type_signal: $route.params.slug_type_signal || featureType ? featureType.slug : '',
}, },
query: $route.query query: $route.query
}" }"
...@@ -171,8 +170,8 @@ ...@@ -171,8 +170,8 @@
@blur="updateDescription" @blur="updateDescription"
/> />
</span> </span>
<span v-else> <span v-else-if="currentFeature && currentFeature.properties">
{{ currentFeature.description }} {{ currentFeature.properties.description }}
</span> </span>
</div> </div>
</div> </div>
...@@ -206,10 +205,6 @@ export default { ...@@ -206,10 +205,6 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
displayToListButton: {
type: Boolean,
default: false,
},
isFeatureCreator: { isFeatureCreator: {
type: Boolean, type: Boolean,
default: false, default: false,
...@@ -233,6 +228,9 @@ export default { ...@@ -233,6 +228,9 @@ export default {
'currentFeature', 'currentFeature',
'form', 'form',
]), ]),
...mapState('projects', [
'project'
]),
...mapGetters([ ...mapGetters([
'permissions', 'permissions',
]), ]),
...@@ -243,6 +241,39 @@ export default { ...@@ -243,6 +241,39 @@ export default {
next: parseInt(this.$route.query.offset) + 1 next: parseInt(this.$route.query.offset) + 1
} : null; } : null;
}, },
currentSort() {
const sort = this.$route.query.ordering;
if (sort) {
if (sort.includes('status')) {
return 'statut';
} else if (sort.includes('feature_type')) {
return 'type de signalement';
} else if (sort.includes('title')) {
return 'nom';
} else if (sort.includes('updated_on')) {
return 'date de modification';
} else if (sort.includes('creator')) {
return 'auteur';
} else if (sort.includes('last_editor')) {
return 'dernier éditeur';
}
}
return 'date de création';
},
currentFilters() {
let filters = [];
if (this.$route.query.feature_type_slug) filters.push('type de signalement');
if (this.$route.query.status__value) filters.push('statut');
if (this.$route.query.title) filters.push('titre');
if (filters.length > 0) {
return `par ${filters.join(', ')}`;
} else {
return 'désactivé';
}
}
}, },
methods: { methods: {
...@@ -250,7 +281,7 @@ export default { ...@@ -250,7 +281,7 @@ export default {
this.$emit('tofeature', { this.$emit('tofeature', {
name: 'details-signalement-filtre', name: 'details-signalement-filtre',
params: { params: {
slug_type_signal: this.currentFeature.feature_type.slug, slug_type_signal: this.currentFeature.properties.feature_type.slug,
}, },
query: { query: {
...this.$route.query, ...this.$route.query,
...@@ -272,13 +303,10 @@ export default { ...@@ -272,13 +303,10 @@ export default {
} }
} }
}; };
</script> </script>
<style> <style lang="less">
#feature-detail-to-features-list {
line-height: 0;
margin-right: 5px;
}
#feature_detail_title_input { #feature_detail_title_input {
font-weight: bold; font-weight: bold;
font-size: 2em; font-size: 2em;
...@@ -289,11 +317,30 @@ export default { ...@@ -289,11 +317,30 @@ export default {
justify-content: space-between; justify-content: space-between;
margin-bottom: .5em; margin-bottom: .5em;
} }
#feature-actions > div { #feature-actions {
margin-left: .5rem; flex-direction: column;
} > div {
#feature-actions .no-opacity { line-height: initial;
opacity: 1 !important; /* overide disabled low opacity to customize button style */ &:last-of-type {
text-align: right;
}
}
> .fast_browsing {
display: flex;
align-items: center;
margin-bottom: .25rem;
span {
margin-left: .1em;
}
span, div {
font-size: 1rem;
color: #666666;
margin-right: 1rem;
font-weight: normal;
opacity: 1 !important;
}
}
} }
@media screen and (max-width: 700px) { @media screen and (max-width: 700px) {
......
...@@ -14,49 +14,54 @@ ...@@ -14,49 +14,54 @@
</td> </td>
</tr> </tr>
<tr <tr
v-for="(field, index) in currentFeature.feature_data" v-for="field in featureFields"
:key="'field' + index" :key="field.name"
> >
<td> <template v-if="!field.isDeactivated">
<strong <td>
:class="{ required: getExtraForm(field) && getExtraForm(field).is_mandatory }" <strong :class="{ required: field.is_mandatory }">
> {{ field.label }}
{{ field.label }} </strong>
</strong> </td>
</td> <td>
<td> <strong class="ui form">
<strong class="ui form"> <span
<span v-if="fastEditionMode && canEditFeature && extra_forms.length > 0"
v-if="fastEditionMode && canEditFeature && extra_forms.length > 0" :id="field.label"
:id="field.label" >
> <ExtraForm
<FeatureExtraForm ref="extraForm"
ref="extraForm" :field="field"
:field="getExtraForm(field)" />
</span>
<i
v-else-if="field.field_type === 'boolean'"
:class="[
'icon',
field.value ? 'olive check' : 'grey times',
]"
aria-hidden="true"
/> />
</span> <span v-else-if="field.value && field.field_type === 'multi_choices_list'">
<i {{ field.value.join(', ') }}
v-else-if="field.field_type === 'boolean'" </span>
:class="[ <span v-else-if="field.value && field.field_type === 'notif_group'">
'icon', {{ usersGroupLabel(field) }}
field.value ? 'olive check' : 'grey times', </span>
]" <span v-else>
aria-hidden="true" {{ field.value && field.value.label ? field.value.label : field.value }}
/> </span>
<span v-else-if="field.field_type === 'multi_choices_list'"> </strong>
{{ field.value.join(', ') }} </td>
</span> </template>
<span v-else>
{{ field.value }}
</span>
</strong>
</td>
</tr> </tr>
<tr> <tr>
<td> <td>
Auteur Auteur
</td> </td>
<td>{{ currentFeature.display_creator }}</td> <td v-if="currentFeature.properties">
{{ currentFeature.properties.display_creator }}
</td>
</tr> </tr>
<tr> <tr>
<td> <td>
...@@ -64,13 +69,13 @@ ...@@ -64,13 +69,13 @@
</td> </td>
<td> <td>
<i <i
v-if="currentFeature.status" v-if="currentFeature.properties && currentFeature.properties.status"
:class="['icon', statusIcon]" :class="['icon', statusIcon]"
aria-hidden="true" aria-hidden="true"
/> />
<FeatureEditStatusField <FeatureEditStatusField
v-if="fastEditionMode && canEditFeature && form" v-if="fastEditionMode && canEditFeature && form"
:status="form.status.value" :status="form.status.value.value || form.status.value"
class="inline" class="inline"
/> />
<span v-else> <span v-else>
...@@ -78,20 +83,33 @@ ...@@ -78,20 +83,33 @@
</span> </span>
</td> </td>
</tr> </tr>
<tr v-if="project && project.feature_assignement">
<td>
Membre assigné
</td>
<td>
<ProjectMemberSelect
:selected-user-id="assignedMemberId"
:disabled="!fastEditionMode || !canEditFeature"
class="inline"
@update:user="$store.commit('feature/UPDATE_FORM_FIELD', { name: 'assigned_member', value: $event })"
/>
</td>
</tr>
<tr> <tr>
<td> <td>
Date de création Date de création
</td> </td>
<td v-if="currentFeature.created_on"> <td v-if="currentFeature.properties && currentFeature.properties.created_on">
{{ currentFeature.created_on | formatDate }} {{ currentFeature.properties.created_on | formatDate }}
</td> </td>
</tr> </tr>
<tr> <tr>
<td> <td>
Date de dernière modification Date de dernière modification
</td> </td>
<td v-if="currentFeature.updated_on"> <td v-if="currentFeature.properties && currentFeature.properties.updated_on">
{{ currentFeature.updated_on | formatDate }} {{ currentFeature.properties.updated_on | formatDate }}
</td> </td>
</tr> </tr>
</tbody> </tbody>
...@@ -116,10 +134,13 @@ ...@@ -116,10 +134,13 @@
<td <td
v-if="link.feature_to.feature_type_slug" v-if="link.feature_to.feature_type_slug"
> >
<a <FeatureFetchOffsetRoute
class="pointer" :feature-id="link.feature_to.feature_id"
@click="toFeature(link)" :properties="{
>{{ link.feature_to.title }} </a> title: link.feature_to.title,
feature_type: { slug: link.feature_to.feature_type_slug }
}"
/>
({{ link.feature_to.display_creator }} - ({{ link.feature_to.display_creator }} -
{{ link.feature_to.created_on }}) {{ link.feature_to.created_on }})
</td> </td>
...@@ -138,30 +159,33 @@ ...@@ -138,30 +159,33 @@
<script> <script>
import { mapState } from 'vuex'; import { mapState, mapGetters } from 'vuex';
import FeatureTypeLink from '@/components/FeatureType/FeatureTypeLink'; import FeatureTypeLink from '@/components/FeatureType/FeatureTypeLink';
import FeatureEditStatusField from '@/components/Feature/FeatureEditStatusField'; import FeatureEditStatusField from '@/components/Feature/FeatureEditStatusField';
import FeatureExtraForm from '@/components/Feature/Edit/FeatureExtraForm'; import ExtraForm from '@/components/ExtraForm';
import { statusChoices } from '@/utils'; import FeatureFetchOffsetRoute from '@/components/Feature/FeatureFetchOffsetRoute';
import ProjectMemberSelect from '@/components/ProjectMemberSelect';
import { statusChoices, formatStringDate, checkDeactivatedValues } from '@/utils';
export default { export default {
name: 'FeatureTable', name: 'FeatureTable',
components: {
FeatureTypeLink,
FeatureEditStatusField,
FeatureExtraForm,
},
filters: { filters: {
formatDate(value) { formatDate(value) {
let date = new Date(value); return formatStringDate(value);
date = date.toLocaleString().replace(',', '');
return date.substr(0, date.length - 3); //* quick & dirty way to remove seconds from date
}, },
}, },
components: {
FeatureTypeLink,
FeatureEditStatusField,
ExtraForm,
FeatureFetchOffsetRoute,
ProjectMemberSelect,
},
props: { props: {
featureType: { featureType: {
type: Object, type: Object,
...@@ -178,15 +202,19 @@ export default { ...@@ -178,15 +202,19 @@ export default {
}, },
computed: { computed: {
...mapState('projects', [
'project'
]),
...mapState('feature', [ ...mapState('feature', [
'currentFeature', 'currentFeature',
'linked_features', 'linked_features',
'form', 'form',
'extra_forms', 'extra_forms',
]), ]),
...mapGetters(['usersGroupsFeatureOptions']),
statusIcon() { statusIcon() {
switch (this.currentFeature.status) { switch (this.currentFeature.properties.status.value || this.currentFeature.properties.status) {
case 'archived': case 'archived':
return 'grey archive'; return 'grey archive';
case 'pending': case 'pending':
...@@ -199,30 +227,53 @@ export default { ...@@ -199,30 +227,53 @@ export default {
return ''; return '';
} }
}, },
statusLabel() { statusLabel() {
const status = statusChoices.find( if (this.currentFeature.properties) {
(el) => el.value === this.currentFeature.status if (this.currentFeature.properties && this.currentFeature.properties.status.label) {
); return this.currentFeature.properties.status.label;
return status ? status.name : ''; }
const status = statusChoices.find(
(el) => el.value === this.currentFeature.properties.status
);
return status ? status.name : '';
}
return '';
}, },
},
methods: { featureData() {
toFeature(link) { if (this.currentFeature.properties && this.featureType) {
this.$emit('tofeature', { // retrieve value for each feature type custom field within feature data
name: 'details-signalement', const extraFieldsWithValue = this.featureType.customfield_set.map((xtraField) => {
params: { return {
slug_type_signal: link.feature_to.feature_type_slug, ...xtraField,
slug_signal: link.feature_to.feature_id, value: this.currentFeature.properties[xtraField.name]
}, };
}); });
// filter out fields not meeting condition to be activated
return checkDeactivatedValues(extraFieldsWithValue);
}
return [];
},
featureFields() {
return this.fastEditionMode ? this.extra_forms : this.featureData;
}, },
getExtraForm(field) { assignedMemberId() {
return this.extra_forms.find(exF => exF.label === field.label); if (this.form && this.form.assigned_member) {
return this.form.assigned_member.value;
}
return this.currentFeature.properties.assigned_member;
},
},
methods: {
usersGroupLabel(field) {
const usersGroup = this.usersGroupsFeatureOptions.find((group) => group.value === field.value);
return usersGroup ? usersGroup.name : field.value;
} }
} }
}; };
</script> </script>
......