Created
April 9, 2023 18:59
-
-
Save chrispahm/ca5bbdd8366edcfc59763c7833b41164 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div class=""> | |
<div class="container"> | |
<div v-if="loading" style="z-index: 100;"> | |
<div class="loading" /> | |
<span style="position: absolute;margin-left: -10px">Lädt...</span> | |
</div> | |
<div id="geocoder" class="geocoder" /> | |
<div class="break" /> | |
<div class="header"> | |
<div style="max-width: 70%; margin-left: 10px;margin-bottom: 5px;"> | |
<canvas id="crop-summary" height="50px" width="700px" /> | |
</div> | |
<span style="margin-right: 10px; margin-bottom: 5px; font-weight: 600; font-size: 22px;">{{ roundedLand }} ha</span> | |
</div> | |
<div id="map" /> | |
<div class="footer"> | |
<ul> | |
<draggable | |
v-model="crops" | |
v-bind="dragOptions" | |
draggable=".crop" | |
:delay="400" | |
class="bubble-wrapper" | |
:remove-on-spill="true" | |
:disabled="false" | |
:drag="checkMove" | |
@start="startDrag" | |
@end="endDrag" | |
@spill="deleteCrop" | |
> | |
<li v-for="crop in crops" :key="crop.name" class="crop" @click="selectedCrop = crop.name"> | |
<div | |
:class="['bubble',selectedCrop === crop.name ? 'selectedCrop' : '']" | |
:style="{ backgroundColor: crop.color }" | |
/> | |
<span>{{ truncateName(crop.name) }}</span> | |
</li> | |
<div slot="footer" style="display: inherit;"> | |
<div style="border-left: 1px solid #ececec; height: 100%; padding-left: 25px;" /> | |
<li class="addCrop" @click="showModal = true"> | |
<div | |
style="width: 30px; height: 30px; line-height: 28px;" | |
class="flex text-gray-600 font-bold justify-center rounded-full bg-gray-200" | |
> | |
<p>+</p> | |
</div> | |
<span>Hinzufügen</span> | |
</li> | |
</div> | |
</draggable> | |
</ul> | |
</div> | |
</div> | |
<modal v-if="showModal" :default-color="randomColor()" @addCrop="addCrop" @close="showModal = false" /> | |
</div> | |
</template> | |
<script> | |
import mapboxgl from 'mapbox-gl' | |
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder' | |
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css' | |
import PouchDB from 'pouchdb-browser' | |
import 'mapbox-gl/dist/mapbox-gl.css' | |
import Chart from 'chart.js/auto' | |
import ChartDataLabels from 'chartjs-plugin-datalabels' | |
import { union } from 'polygon-clipping' | |
import bbox from '@turf/bbox' | |
import draggable from '@/components/vuedraggable.umd.js' | |
import modal from '@/components/modal.vue' | |
const db = new PouchDB('https://fruchtfolge.agp.uni-bonn.de/db/fields', { skip_setup: true }) | |
/* | |
Chart.Legend.prototype.afterFit = function () { | |
this.height = this.height - 30 | |
} | |
*/ | |
Chart.register(ChartDataLabels) | |
export default { | |
components: { | |
modal, | |
draggable | |
}, | |
data () { | |
return { | |
loading: true, | |
showModal: false, | |
draggedElement: null, | |
crops: [{ | |
color: '#F88353', | |
name: 'Belana', | |
area: 0 | |
}, { | |
color: '#FAA54B', | |
name: 'Agria', | |
area: 0 | |
}, { | |
color: '#3996CD', | |
name: 'Ackerbohnen', | |
area: 0 | |
}, { | |
color: '#1975B2', | |
name: 'Winterweizen', | |
area: 0 | |
}, { | |
color: '#F8214F', | |
name: 'Zwiebeln', | |
area: 0 | |
}, { | |
color: '#4D4777', | |
name: 'Mais', | |
area: 0 | |
}, { | |
color: '#B9262C', | |
name: 'Möhren', | |
area: 0 | |
}, { | |
color: '#f5f5f5', | |
name: '(Unbekannt)', | |
area: 0 | |
}], | |
selectedCrop: 'Belana', | |
totalArea: 0, | |
featureCollection: { | |
type: 'FeatureCollection', | |
features: [] | |
} | |
} | |
}, | |
computed: { | |
roundedLand () { | |
return Math.round(this.totalArea * Math.pow(10, 1)) / Math.pow(10, 1) | |
}, | |
dragOptions () { | |
return { | |
animation: 200, | |
forceFallback: true, | |
group: 'description', | |
disabled: false, | |
ghostClass: 'dontDisplayGhost' | |
} | |
} | |
}, | |
watch: { | |
crops: { | |
deep: true, | |
handler () { | |
return this.updateData() | |
} | |
} | |
}, | |
beforeCreate () { | |
// reload the page early if no query parameters are attached | |
// eslint-disable-next-line nuxt/no-globals-in-created | |
const urlParams = new URLSearchParams(window.location.search) | |
if (!urlParams.has('id')) { | |
const sessionId = new Date().toISOString() | |
urlParams.set('id', sessionId) | |
// eslint-disable-next-line nuxt/no-globals-in-created | |
document.location.search = urlParams.toString() | |
} | |
}, | |
async mounted () { | |
// get or create session id | |
const urlParams = new URLSearchParams(window.location.search) | |
this.sessionId = urlParams.get('id') | |
try { | |
const data = await db.get(this.sessionId) | |
this._rev = data._rev | |
this.featureCollection = data.featureCollection | |
// update feauture collection in case it's containing old data | |
data.featureCollection.features = data.featureCollection.features.map((f) => { | |
if (!f.id) { f.id = f.properties.ID } | |
if (!f.properties.area) { f.properties.area = f.properties.AREA_HA } | |
return f | |
}) | |
this.totalArea = data.featureCollection.features.reduce((sum, plot) => { | |
sum += plot.properties.area || 0 | |
return sum | |
}, 0) | |
if (data.crops) { | |
this.crops = data.crops | |
} else { | |
// extract crops from fields | |
} | |
this.selectedCrop = data.selectedCrop || this.crops[0].name | |
// this.totalArea = data.totalArea || 0 | |
} catch (e) { | |
console.log(e) | |
// no data yet | |
} | |
if (urlParams.has('preset') && !this.featureCollection.features.length) { | |
const preset = urlParams.get('preset') | |
try { | |
const data = await db.get(preset) | |
this.featureCollection = data.featureCollection | |
this.crops = data.crops | |
this.selectedCrop = data.selectedCrop || this.crops[0].name | |
this.totalArea = data.totalArea || 0 | |
} catch (e) { | |
// preset doesn't exist | |
} | |
} | |
this.createMap() | |
this.createChart() | |
this.loading = false | |
}, | |
methods: { | |
startDrag (e) { | |
this.draggedElement = e.item | |
this.draggedElement.style = 'cursor: grabbing;' | |
}, | |
endDrag (e) { | |
this.draggedElement.style = 'cursor: pointer;' | |
}, | |
checkMove (e, ghostEl) { | |
if (e.y > 737 && ghostEl.classList.contains('drag-test')) { | |
// inside footer | |
ghostEl.classList.remove('drag-test') | |
} else if (e.y < 737 && !ghostEl.classList.contains('drag-test')) { | |
ghostEl.classList.add('drag-test') | |
} | |
}, | |
addCrop (cropData) { | |
// update crops array | |
this.crops.push(cropData) | |
// add crop to chart | |
this.chart.data.datasets.push({ | |
data: [cropData.area], | |
label: cropData.name, | |
backgroundColor: cropData.color, | |
barPercentage: 1, | |
categoryPercentage: 1, | |
barThickness: 30, | |
maxBarThickness: 30 | |
}) | |
this.chart.update() | |
// update map colors | |
this.map.setPaintProperty('plots', 'fill-color', { | |
type: 'categorical', | |
property: 'crop', | |
stops: this.crops.map(c => [c.name, c.color]) | |
}) | |
}, | |
deleteCrop (item) { | |
const index = item.oldIndex | |
const crop = this.crops[index] | |
// remove selected plots and total | |
this.featureCollection.features = this.featureCollection.features.filter(p => p.properties.crop !== crop.name) | |
this.totalArea = this.totalArea - crop.area | |
this.map.getSource('plots').setData(this.featureCollection) | |
// remove from chart | |
this.chart.data.datasets.splice(index, 1) | |
this.chart.update() | |
// remove from crops array | |
this.crops.splice(index, 1) | |
// make adjacent crop the selected one | |
if (this.selectedCrop === crop.name) { | |
if (this.crops.length && this.crops[index]) { | |
this.selectedCrop = this.crops[index].name | |
} else if (this.crops.length) { | |
this.selectedCrop = this.crops[0].name | |
} | |
} | |
this.updateData() | |
}, | |
async updateData () { | |
let data = { | |
_id: this.sessionId, | |
_rev: this._rev | |
} | |
try { | |
data = await db.get(this.sessionId) | |
} catch (e) { | |
// no data yet | |
} | |
data.crops = this.crops | |
data.featureCollection = this.featureCollection | |
data.totalArea = this.totalArea | |
data.selectedCrop = this.selectedCrop | |
try { | |
await db.put(data) | |
} catch (e) { | |
// eslint-disable-next-line no-console | |
console.log(e) | |
} | |
console.log('updated') | |
}, | |
truncateName (name) { | |
if (name.length > 10) { | |
return name.slice(0, 8) + '..' | |
} else { | |
return name | |
} | |
}, | |
merge (inputs) { | |
const output = { | |
id: inputs[0].id, | |
type: inputs[0].type, | |
geometry: { | |
coordinates: union(...inputs.map(i => i.geometry.coordinates)), | |
type: 'MultiPolygon' | |
}, | |
properties: inputs[0].properties | |
} | |
return output | |
}, | |
createMap () { | |
mapboxgl.accessToken = 'pk.eyJ1IjoidG9mZmkiLCJhIjoiY2lvMDBxMzR3MDB1eHZza2x4NGI2YjI5OSJ9.IEaNA05pWbT92nOu-lEOYw' | |
this.map = new mapboxgl.Map({ | |
container: 'map', | |
style: 'mapbox://styles/mapbox/satellite-streets-v11?optimize=true', | |
center: [8.3502733, 52.0887843], | |
zoom: 13 | |
}) | |
this.map.addControl( | |
new mapboxgl.GeolocateControl({ | |
positionOptions: { | |
enableHighAccuracy: true | |
}, | |
trackUserLocation: true | |
}), 'bottom-right' | |
) | |
const geocoder = new MapboxGeocoder({ | |
accessToken: mapboxgl.accessToken, | |
mapboxgl | |
}) | |
if (screen.width < 475) { | |
document.getElementById('geocoder').appendChild(geocoder.onAdd(this.map)) | |
} else { | |
this.map.addControl(geocoder) | |
} | |
this.map.on('load', () => { | |
this.map.addSource('plots', { | |
type: 'geojson', | |
data: this.featureCollection | |
}) | |
this.map.addSource('plot-shapes', { | |
url: 'mapbox://toffi.plots-germany', | |
type: 'vector' | |
}) | |
this.map.addLayer( | |
{ | |
id: 'plots-germany-outline', | |
type: 'line', | |
source: 'plot-shapes', | |
'source-layer': 'plots_germany', | |
minzoom: 10, | |
paint: { | |
'line-color': 'hsl(0, 0%, 100%)', | |
'line-width': [ | |
'interpolate', | |
['linear'], | |
['zoom'], | |
0, | |
0, | |
12, | |
0, | |
14, | |
2 | |
] | |
} | |
}, | |
'country-label' | |
) | |
this.map.addLayer( | |
{ | |
id: 'plots-germany', | |
type: 'fill', | |
source: 'plot-shapes', | |
'source-layer': 'plots_germany', | |
minzoom: 10, | |
paint: { | |
'fill-color': 'hsl(0, 0%, 100%)', | |
'fill-outline-color': 'hsl(0, 0%, 100%)', | |
'fill-antialias': false, | |
'fill-opacity': [ | |
'interpolate', | |
['linear'], | |
['zoom'], | |
0, | |
0, | |
12, | |
0, | |
14, | |
0.26 | |
] | |
} | |
}, | |
'country-label' | |
) | |
this.map.addLayer({ | |
id: 'plots', | |
type: 'fill', | |
source: 'plots', | |
layout: {}, | |
paint: { | |
'fill-color': { | |
type: 'categorical', | |
property: 'crop', | |
stops: this.crops.map(c => [c.name, c.color]) | |
}, | |
'fill-opacity': 1 | |
} | |
}) | |
this.map.addLayer({ | |
id: 'plots-label', | |
type: 'symbol', | |
source: 'plots', | |
minzoom: 13, | |
maxzoom: 14, | |
layout: { | |
'text-field': ['format', | |
['number-format', ['get', 'area'], { 'max-fraction-digits': 2 }], ' ha', {} // ['get', 'AREA_HA'], {}, // Use default formatting | |
], | |
'text-font': [ | |
'DIN Offc Pro Medium', | |
'Arial Unicode MS Bold' | |
], | |
'text-size': 12 | |
} | |
}) | |
this.map.addLayer({ | |
id: 'plots-label-prev-crop', | |
type: 'symbol', | |
source: 'plots', | |
minzoom: 14, | |
layout: { | |
'text-field': ['format', | |
['number-format', ['get', 'area'], { 'max-fraction-digits': 2 }], ' ha', {}, // ['get', 'AREA_HA'], {}, // Use default formatting | |
'\n', {}, | |
'Ausgewählt: ', { | |
'text-font': ['literal', ['DIN Offc Pro Italic']], | |
'font-scale': 0.8 | |
}, ['get', 'crop'], | |
{ | |
'text-font': ['literal', ['DIN Offc Pro Italic']], | |
'font-scale': 0.8 | |
} | |
], | |
'text-font': [ | |
'DIN Offc Pro Medium', | |
'Arial Unicode MS Bold' | |
], | |
'text-size': 12 | |
} | |
}) | |
if (this.featureCollection?.features?.length) { | |
this.map.fitBounds(bbox(this.featureCollection), { padding: 30, duration: 0 }) | |
} | |
}) | |
this.map.on('click', (e) => { | |
const features = this.map.queryRenderedFeatures(e.point) | |
let feature | |
if (features[0]) { | |
console.log(features[0]) | |
try { | |
const featureData = this.map.querySourceFeatures(features[0].source, { | |
sourceLayer: features[0].sourceLayer, | |
filter: ['==', '$id', features[0].id] | |
}) | |
if (Array.isArray(featureData)) { | |
feature = this.merge(featureData) | |
} else { | |
feature = featureData | |
} | |
} catch (e) { | |
// clicked on a non-feature, fail silently | |
return | |
} | |
// assign the id if it's not given | |
if (!feature.properties.ID) { | |
feature.properties.ID = feature.id | |
} | |
// check if the feature is on the map already | |
const match = this.featureCollection.features.find(f => f.properties.ID === feature.properties.ID) | |
if (match) { | |
// if the plot was clicked upon while a different crop was chosen, | |
// we just switch the crop shares accordingly | |
if (match.properties.crop !== this.selectedCrop) { | |
const oldCrop = this.crops.find(c => c.name === match.properties.crop) | |
const newCrop = this.crops.find(c => c.name === this.selectedCrop) | |
oldCrop.area = oldCrop.area - match.properties.area | |
newCrop.area = newCrop.area + match.properties.area | |
match.properties.crop = this.selectedCrop | |
} else { | |
const crop = this.crops.find(c => c.name === match.properties.crop) | |
crop.area = crop.area - match.properties.area | |
this.totalArea = this.totalArea - match.properties.area | |
const index = this.featureCollection.features.indexOf(match) | |
this.featureCollection.features.splice(index, 1) | |
} | |
// check if the selected crop is different than the current crop | |
} else { | |
const crop = this.crops.find(c => c.name === this.selectedCrop) | |
crop.area = crop.area + feature.properties.area | |
this.totalArea = this.totalArea + feature.properties.area | |
feature.properties.crop = this.selectedCrop | |
this.featureCollection.features.push(feature) | |
} | |
this.map.getSource('plots').setData(this.featureCollection) | |
this.updateChart() | |
} | |
// this.featureCollection.features.find() | |
}) | |
}, | |
updateChart () { | |
this.chart.data.datasets.forEach((dataset, i) => { | |
dataset.data[0] = this.crops[i].area < 0.1 ? 0 : this.crops[i].area | |
}) | |
this.chart.update() | |
}, | |
randomColor () { | |
return '#' + Math.floor(Math.random() * 16777215).toString(16) | |
}, | |
createChart () { | |
const ctx = document.getElementById('crop-summary').getContext('2d') | |
const getOrCreateTooltip = (chart) => { | |
let tooltipEl = chart.canvas.parentNode.querySelector('div') | |
if (!tooltipEl) { | |
tooltipEl = document.createElement('div') | |
tooltipEl.style.background = 'rgba(0, 0, 0, 0.7)' | |
tooltipEl.style.borderRadius = '3px' | |
tooltipEl.style.color = 'white' | |
tooltipEl.style.opacity = 1 | |
tooltipEl.style.pointerEvents = 'none' | |
tooltipEl.style.position = 'absolute' | |
tooltipEl.style.transform = 'translate(-50%, 0)' | |
tooltipEl.style.transition = 'all .1s ease' | |
const table = document.createElement('table') | |
table.style.margin = '0px' | |
tooltipEl.appendChild(table) | |
chart.canvas.parentNode.appendChild(tooltipEl) | |
} | |
return tooltipEl | |
} | |
const externalTooltipHandler = (context) => { | |
// Tooltip Element | |
const { chart, tooltip } = context | |
const tooltipEl = getOrCreateTooltip(chart) | |
// Hide if no tooltip | |
if (tooltip.opacity === 0) { | |
tooltipEl.style.opacity = 0 | |
return | |
} | |
// Set Text | |
if (tooltip.body) { | |
const titleLines = tooltip.title || [] | |
const bodyLines = tooltip.body.map(b => b.lines) | |
const tableHead = document.createElement('thead') | |
titleLines.forEach((title) => { | |
const tr = document.createElement('tr') | |
tr.style.borderWidth = 0 | |
const th = document.createElement('th') | |
th.style.borderWidth = 0 | |
const text = document.createTextNode(title) | |
th.appendChild(text) | |
tr.appendChild(th) | |
tableHead.appendChild(tr) | |
}) | |
const tableBody = document.createElement('tbody') | |
bodyLines.forEach((body, i) => { | |
const colors = tooltip.labelColors[i] | |
const span = document.createElement('span') | |
span.style.background = colors.backgroundColor | |
span.style.borderColor = colors.borderColor | |
span.style.borderWidth = '2px' | |
span.style.marginRight = '10px' | |
span.style.height = '10px' | |
span.style.width = '10px' | |
span.style.display = 'inline-block' | |
const tr = document.createElement('tr') | |
tr.style.backgroundColor = 'inherit' | |
tr.style.borderWidth = 0 | |
const td = document.createElement('td') | |
td.style.borderWidth = 0 | |
const text = document.createTextNode(body) | |
td.appendChild(span) | |
td.appendChild(text) | |
tr.appendChild(td) | |
tableBody.appendChild(tr) | |
}) | |
const tableRoot = tooltipEl.querySelector('table') | |
// Remove old children | |
while (tableRoot.firstChild) { | |
tableRoot.firstChild.remove() | |
} | |
// Add new children | |
tableRoot.appendChild(tableHead) | |
tableRoot.appendChild(tableBody) | |
} | |
const { offsetLeft: positionX, offsetTop: positionY } = chart.canvas | |
// Display, position, and set styles for font | |
tooltipEl.style.opacity = 1 | |
tooltipEl.style.left = positionX + tooltip.caretX + 'px' | |
tooltipEl.style.top = positionY + tooltip.caretY + 'px' | |
tooltipEl.style.font = tooltip.options.bodyFont.string | |
tooltipEl.style.padding = tooltip.options.padding + 'px ' + tooltip.options.padding + 'px' | |
} | |
this.chart = new Chart(ctx, { | |
type: 'bar', | |
data: { | |
labels: [''], | |
datasets: this.crops.map((crop, i) => { | |
return { | |
data: [crop.area || 0], | |
label: crop.name, | |
backgroundColor: crop.color, | |
barPercentage: 1, | |
categoryPercentage: 1, | |
barThickness: 30, | |
maxBarThickness: 30 | |
} | |
}) | |
}, | |
options: { | |
indexAxis: 'y', | |
responsive: true, | |
maintainAspectRatio: false, | |
layout: { | |
padding: { | |
left: 0, | |
right: 0, | |
top: 0, | |
bottom: 0 | |
} | |
}, | |
plugins: { | |
legend: { | |
display: false, | |
align: 'start' | |
}, | |
tooltip: { | |
enabled: false, | |
position: 'nearest', | |
external: externalTooltipHandler | |
}, | |
datalabels: { | |
color (context) { | |
const crop = context.dataset.label | |
return crop === '(Unbekannt)' ? 'black' : 'white' | |
}, | |
anchor: 'end', | |
align: 'start', | |
display (context) { | |
return context.dataset.data[context.dataIndex] > 5 | |
}, | |
font: { | |
weight: 'normal' | |
}, | |
formatter (value, context) { | |
return Math.round(value) + ' ha' | |
} | |
} | |
}, | |
scales: { | |
y: { | |
display: false, | |
stacked: true, | |
gridLines: { | |
display: false | |
}, | |
ticks: { | |
display: false | |
} | |
}, | |
x: { | |
display: false, | |
stacked: true, | |
ticks: { | |
// min: 0, | |
// max: 100, | |
mirror: true, | |
display: false | |
}, | |
gridLines: { | |
display: false | |
} | |
} | |
} | |
} | |
}) | |
} | |
} | |
} | |
</script> | |
<style> | |
body { | |
margin: 0; | |
padding: 0; | |
} | |
#map { | |
position: absolute; | |
top: 65px; | |
bottom: 84px; | |
width: 100%; | |
} | |
#crop-summary { | |
width: 100% !important; | |
} | |
.geocoder { | |
/*position: absolute;*/ | |
/*top: 0;*/ | |
z-index: 100; | |
width: 100%; | |
min-width: 100%; | |
background-color: white; | |
} | |
.mapboxgl-ctrl-geocoder { | |
border-bottom: 1px solid #ececec; | |
border-radius: 0px; | |
box-shadow: unset; | |
} | |
.break { | |
flex-basis: 100%; | |
height: 0; | |
} | |
.header { | |
/*position: absolute;*/ | |
/*top: 0;*/ | |
width: 100%; | |
background-color: white; | |
/* height: 150px; */ | |
min-height: 50px; | |
z-index: 10; | |
display: inline-flex; | |
flex-wrap: nowrap; | |
justify-content: space-between; | |
align-items: center; | |
} | |
@media (min-width: 576px) { | |
.header { | |
padding: 10px 10px 0px 10px; | |
align-items: center; | |
} | |
} | |
.loading { | |
position: absolute; | |
background-color: white; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
} | |
.footer { | |
display: flex; | |
position: absolute; | |
bottom: 0; | |
width: 100%; | |
height: 84px; | |
overflow: scroll; | |
} | |
.footer::before, .footer::after { | |
content: ''; /* Insert pseudo-element */ | |
margin: auto; /* Make it push flex items to the center */ | |
} | |
.crop { | |
display: flex; | |
width: 50px; | |
margin-right: 25px; | |
font-size: 14px; | |
cursor: pointer; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
-webkit-touch-callout: none; /* iOS Safari */ | |
-webkit-user-select: none; /* Safari */ | |
-khtml-user-select: none; /* Konqueror HTML */ | |
-moz-user-select: none; /* Old versions of Firefox */ | |
-ms-user-select: none; /* Internet Explorer/Edge */ | |
user-select: none; | |
} | |
.crop:hover .bubble { | |
border: 3px solid #878484; | |
} | |
.crop:active .bubble { | |
animation: pulse 1s 1, done 2s infinite 0.5s; | |
} | |
@keyframes done { | |
0% { | |
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.3); | |
} | |
70% { | |
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.3); | |
} | |
100% { | |
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.3); | |
} | |
} | |
@keyframes pulse { | |
0% { | |
transform: scale(0.95); | |
box-shadow: 0 0 0 0 rgba(0, 0, 0, 0.7); | |
} | |
70% { | |
transform: scale(1); | |
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0); | |
} | |
100% { | |
transform: scale(0.95); | |
box-shadow: 0 0 0 10px rgba(0, 0, 0, 0); | |
} | |
} | |
.selectedCrop { | |
border: 3px solid black !important; | |
} | |
.addCrop { | |
display: flex; | |
width: 50px; | |
margin-right: 25px; | |
font-size: 14px; | |
cursor: pointer; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
-webkit-touch-callout: none; /* iOS Safari */ | |
-webkit-user-select: none; /* Safari */ | |
-khtml-user-select: none; /* Konqueror HTML */ | |
-moz-user-select: none; /* Old versions of Firefox */ | |
-ms-user-select: none; /* Internet Explorer/Edge */ | |
user-select: none; | |
} | |
.addCrop:hover div { | |
border: 3px solid #878484 !important; | |
} | |
.bubble { | |
width: 30px; | |
height: 30px; | |
background: grey; | |
border-radius: 50%; | |
} | |
.bubble-wrapper { | |
padding: 15px 0px 15px 15px; | |
width: fit-content; | |
display: flex; | |
flex-wrap: nowrap; | |
overflow-y: clip; | |
} | |
/* Sample `apply` at-rules with Tailwind CSS | |
.container { | |
@apply min-h-screen flex justify-center items-center text-center mx-auto; | |
} | |
*/ | |
.container { | |
margin: 0 auto; | |
/* min-height: 100vh; */ | |
display: flex; | |
flex-wrap: wrap; | |
justify-content: center; | |
align-items: center; | |
text-align: center; | |
} | |
.title { | |
font-family: | |
'Quicksand', | |
'Source Sans Pro', | |
-apple-system, | |
BlinkMacSystemFont, | |
'Segoe UI', | |
Roboto, | |
'Helvetica Neue', | |
Arial, | |
sans-serif; | |
display: block; | |
font-weight: 300; | |
font-size: 100px; | |
color: #35495e; | |
letter-spacing: 1px; | |
} | |
.subtitle { | |
font-weight: 300; | |
font-size: 42px; | |
color: #526488; | |
word-spacing: 5px; | |
padding-bottom: 15px; | |
} | |
.links { | |
padding-top: 15px; | |
} | |
.flip-list-move { | |
transition: transform 0.8s ease; | |
} | |
.dontDisplayGhost { | |
opacity: 0; | |
} | |
.sortable-drag .bubble { | |
transform: scale(0.8); | |
box-shadow: 0 0 0 5px rgba(0, 0, 0, 0.3); | |
} | |
.drag-test { | |
width: 24px; | |
height: 24px; | |
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' x='0px' y='0px'%0Awidth='24' height='24'%0AviewBox='0 0 24 24'%0Astyle=' fill:%23000;'%3E%3Cpath fill='white' d='M 10 2 L 9 3 L 4 3 L 4 5 L 5 5 L 5 20 C 5 20.522222 5.1913289 21.05461 5.5683594 21.431641 C 5.9453899 21.808671 6.4777778 22 7 22 L 17 22 C 17.522222 22 18.05461 21.808671 18.431641 21.431641 C 18.808671 21.05461 19 20.522222 19 20 L 19 5 L 20 5 L 20 3 L 15 3 L 14 2 L 10 2 z'%3E%3C/path%3E%3Cpath d='M 10 2 L 9 3 L 4 3 L 4 5 L 5 5 L 5 20 C 5 20.522222 5.1913289 21.05461 5.5683594 21.431641 C 5.9453899 21.808671 6.4777778 22 7 22 L 17 22 C 17.522222 22 18.05461 21.808671 18.431641 21.431641 C 18.808671 21.05461 19 20.522222 19 20 L 19 5 L 20 5 L 20 3 L 15 3 L 14 2 L 10 2 z M 7 5 L 17 5 L 17 20 L 7 20 L 7 5 z M 9 7 L 9 18 L 11 18 L 11 7 L 9 7 z M 13 7 L 13 18 L 15 18 L 15 7 L 13 7 z'%3E%3C/path%3E%3C/svg%3E"); | |
background-size: 100%; | |
} | |
.drag-test .bubble { | |
box-shadow: none; | |
} | |
.drag-test > * { | |
color: transparent; | |
border: none !important; | |
border-radius: none !important; | |
color: transparent !important; | |
background-color: unset !important; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment