fix(webapp): fix map & user-teaser (#9484)
|
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 86 KiB |
5
packages/ui/src/ocelot/icons/svgs/map-pin-filled.svg
Normal file
@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="33" viewBox="0 0 20 33">
|
||||
<ellipse cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3"/>
|
||||
<path d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z" fill="#fff"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 692 B |
@ -140,7 +140,7 @@ hr {
|
||||
transition-delay: 0;
|
||||
transition: opacity 80ms ease-out;
|
||||
|
||||
@media(hover: none) {
|
||||
@media(pointer: coarse) {
|
||||
pointer-events: all;
|
||||
}
|
||||
}
|
||||
|
||||
@ -48,8 +48,10 @@ export default {
|
||||
if (typeof document === 'undefined') return
|
||||
if (isOpen) {
|
||||
document.body.classList.add('dropdown-open')
|
||||
this.addOverlayClickHandler()
|
||||
} else {
|
||||
document.body.classList.remove('dropdown-open')
|
||||
this.removeOverlayClickHandler()
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -57,6 +59,7 @@ export default {
|
||||
beforeDestroy() {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
this.removeOverlayClickHandler()
|
||||
if (this.isPopoverOpen) {
|
||||
this.isPopoverOpen = false
|
||||
if (typeof document !== 'undefined') {
|
||||
@ -116,6 +119,28 @@ export default {
|
||||
clearTimeout(mouseEnterTimer)
|
||||
clearTimeout(mouseLeaveTimer)
|
||||
},
|
||||
addOverlayClickHandler() {
|
||||
this.overlayClickHandler = (e) => {
|
||||
// Allow clicks inside the popover content (rendered under <body> by v-tooltip)
|
||||
if (e.target.closest('.tooltip-inner, .popover-inner')) return
|
||||
// Allow clicks on the trigger itself
|
||||
if (this.$el.contains(e.target)) return
|
||||
e.stopPropagation()
|
||||
e.preventDefault()
|
||||
this.isPopoverOpen = false
|
||||
}
|
||||
// Capture phase fires before any other handler
|
||||
setTimeout(() => {
|
||||
if (this.isPopoverOpen && this.overlayClickHandler) {
|
||||
document.addEventListener('click', this.overlayClickHandler, true)
|
||||
}
|
||||
}, 0)
|
||||
},
|
||||
removeOverlayClickHandler() {
|
||||
if (!this.overlayClickHandler) return
|
||||
document.removeEventListener('click', this.overlayClickHandler, true)
|
||||
this.overlayClickHandler = null
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -5,6 +5,12 @@ import Vuex from 'vuex'
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
window.matchMedia = jest.fn().mockImplementation(() => ({
|
||||
matches: false,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}))
|
||||
|
||||
// Mock Math.random, used in Dropdown
|
||||
Object.assign(Math, {
|
||||
random: () => 0,
|
||||
@ -12,11 +18,6 @@ Object.assign(Math, {
|
||||
|
||||
const waitForPopover = async () => await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||
|
||||
let mockIsTouchDevice
|
||||
jest.mock('../utils/isTouchDevice', () => ({
|
||||
isTouchDevice: jest.fn(() => mockIsTouchDevice),
|
||||
}))
|
||||
|
||||
const userTilda = {
|
||||
name: 'Tilda Swinton',
|
||||
slug: 'tilda-swinton',
|
||||
@ -65,7 +66,11 @@ describe('UserTeaser', () => {
|
||||
user = userTilda,
|
||||
withPopoverEnabled = true,
|
||||
}) => {
|
||||
mockIsTouchDevice = onTouchScreen
|
||||
window.matchMedia.mockImplementation(() => ({
|
||||
matches: onTouchScreen,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}))
|
||||
|
||||
const store = new Vuex.Store({
|
||||
getters: {
|
||||
|
||||
@ -2,6 +2,16 @@ import { render, waitFor, fireEvent } from '@testing-library/vue'
|
||||
import { RouterLinkStub } from '@vue/test-utils'
|
||||
import UserTeaserHelper from './UserTeaserHelper.vue'
|
||||
|
||||
let mockMatchMediaMatches = false
|
||||
const mockMatchMediaListeners = []
|
||||
window.matchMedia = jest.fn().mockImplementation(() => ({
|
||||
get matches() {
|
||||
return mockMatchMediaMatches
|
||||
},
|
||||
addEventListener: jest.fn((event, handler) => mockMatchMediaListeners.push(handler)),
|
||||
removeEventListener: jest.fn(),
|
||||
}))
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const userLink = {
|
||||
@ -9,12 +19,6 @@ const userLink = {
|
||||
params: { slug: 'slug', id: 'id' },
|
||||
}
|
||||
|
||||
let mockIsTouchDevice
|
||||
|
||||
jest.mock('../utils/isTouchDevice', () => ({
|
||||
isTouchDevice: jest.fn(() => mockIsTouchDevice),
|
||||
}))
|
||||
|
||||
describe('UserTeaserHelper', () => {
|
||||
const Wrapper = ({
|
||||
withLinkToProfile = true,
|
||||
@ -22,7 +26,7 @@ describe('UserTeaserHelper', () => {
|
||||
withPopoverEnabled = true,
|
||||
hoverDelay = 500,
|
||||
}) => {
|
||||
mockIsTouchDevice = onTouchScreen
|
||||
mockMatchMediaMatches = onTouchScreen
|
||||
|
||||
return render(UserTeaserHelper, {
|
||||
localVue,
|
||||
|
||||
@ -20,10 +20,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { isTouchDevice } from '../utils/isTouchDevice'
|
||||
import touchDevice from '~/mixins/touchDevice'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaserHelper',
|
||||
mixins: [touchDevice],
|
||||
props: {
|
||||
userLink: { type: Object, default: null },
|
||||
linkToProfile: { type: Boolean, default: true },
|
||||
@ -36,11 +37,6 @@ export default {
|
||||
isHovering: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isTouchDevice() {
|
||||
return isTouchDevice()
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleMouseEnter() {
|
||||
if (!this.showPopover) return
|
||||
|
||||
@ -2,6 +2,15 @@ import { render } from '@testing-library/vue'
|
||||
import { RouterLinkStub } from '@vue/test-utils'
|
||||
import UserTeaserPopover from './UserTeaserPopover.vue'
|
||||
|
||||
const mockMatchMedia = (matches = false) => {
|
||||
window.matchMedia = jest.fn().mockImplementation(() => ({
|
||||
matches,
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}))
|
||||
}
|
||||
mockMatchMedia(false)
|
||||
|
||||
const localVue = global.localVue
|
||||
|
||||
const user = {
|
||||
@ -51,10 +60,7 @@ describe('UserTeaserPopover', () => {
|
||||
onTouchScreen = false,
|
||||
userData = user,
|
||||
}) => {
|
||||
const mockIsTouchDevice = onTouchScreen
|
||||
jest.mock('../utils/isTouchDevice', () => ({
|
||||
isTouchDevice: jest.fn(() => mockIsTouchDevice),
|
||||
}))
|
||||
mockMatchMedia(onTouchScreen)
|
||||
return render(UserTeaserPopover, {
|
||||
localVue,
|
||||
propsData: {
|
||||
|
||||
@ -44,11 +44,12 @@
|
||||
import { OsButton, OsNumber } from '@ocelot-social/ui'
|
||||
import Badges from '~/components/Badges.vue'
|
||||
import LocationInfo from '~/components/LocationInfo/LocationInfo.vue'
|
||||
import { isTouchDevice } from '~/components/utils/isTouchDevice'
|
||||
import touchDevice from '~/mixins/touchDevice'
|
||||
import { userTeaserQuery } from '~/graphql/User.js'
|
||||
|
||||
export default {
|
||||
name: 'UserTeaserPopover',
|
||||
mixins: [touchDevice],
|
||||
components: {
|
||||
Badges,
|
||||
LocationInfo,
|
||||
@ -60,9 +61,6 @@ export default {
|
||||
userLink: { type: Object },
|
||||
},
|
||||
computed: {
|
||||
isTouchDevice() {
|
||||
return isTouchDevice()
|
||||
},
|
||||
user() {
|
||||
return (this.User && this.User[0]) ?? null
|
||||
},
|
||||
|
||||
@ -412,7 +412,19 @@ exports[`UserTeaserPopover given a touch device shows button when userLink is pr
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<!---->
|
||||
<a
|
||||
class="link os-button inline-flex items-center justify-center [white-space-collapse:collapse] relative appearance-none font-semibold tracking-[0.05em] transition-[color,background-color] duration-[80ms] ease-[cubic-bezier(0.25,0.46,0.45,0.94)] cursor-pointer select-none border-[0.8px] border-solid focus:outline-1 disabled:pointer-events-none disabled:cursor-default focus:outline-dashed focus:outline-[var(--color-primary)] shadow-[inset_0_0_0_1px_rgba(0,0,0,0.05)] disabled:bg-[var(--color-disabled)] disabled:text-[var(--color-disabled-contrast)] disabled:border-[var(--color-disabled)] disabled:shadow-[inset_0_0_0_1px_transparent] disabled:hover:bg-[var(--color-disabled)] disabled:hover:text-[var(--color-disabled-contrast)] disabled:hover:border-[var(--color-disabled)] disabled:active:bg-[var(--color-disabled)] disabled:active:text-[var(--color-disabled-contrast)] disabled:active:border-[var(--color-disabled)] h-[36px] min-w-[36px] px-[16px] py-0 text-[15px] leading-[normal] rounded-[5px] align-middle bg-[var(--color-primary)] text-[var(--color-primary-contrast)] border-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] hover:border-[var(--color-primary-hover)] hover:text-[var(--color-primary-contrast)] active:bg-[var(--color-primary-active)] active:border-[var(--color-primary-active)] active:text-[var(--color-primary-contrast)] link"
|
||||
data-appearance="filled"
|
||||
data-variant="primary"
|
||||
>
|
||||
<span
|
||||
class="inline-flex items-center"
|
||||
>
|
||||
|
||||
user-teaser.popover.open-profile
|
||||
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
23
webapp/mixins/touchDevice.js
Normal file
@ -0,0 +1,23 @@
|
||||
export default {
|
||||
data() {
|
||||
const pointerQuery =
|
||||
typeof window !== 'undefined' ? window.matchMedia('(pointer: coarse)') : null
|
||||
return {
|
||||
isTouchDevice: pointerQuery ? pointerQuery.matches : false,
|
||||
pointerQuery,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
if (this.pointerQuery) {
|
||||
this.onPointerChange = (e) => {
|
||||
this.isTouchDevice = e.matches
|
||||
}
|
||||
this.pointerQuery.addEventListener('change', this.onPointerChange)
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
if (this.pointerQuery && this.onPointerChange) {
|
||||
this.pointerQuery.removeEventListener('change', this.onPointerChange)
|
||||
}
|
||||
},
|
||||
}
|
||||
@ -6,7 +6,12 @@ import Map from './map'
|
||||
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'
|
||||
|
||||
jest.mock('@mapbox/mapbox-gl-geocoder', () => {
|
||||
return jest.fn().mockImplementation(jest.fn())
|
||||
return jest.fn().mockImplementation(() => {
|
||||
const mockParent = global.document.createElement('div')
|
||||
const container = global.document.createElement('div')
|
||||
mockParent.appendChild(container)
|
||||
return { container, clear: jest.fn() }
|
||||
})
|
||||
})
|
||||
|
||||
jest.mock('mapbox-gl', () => {
|
||||
@ -79,9 +84,9 @@ const mapMock = {
|
||||
getContainer: mapGetContainerMock,
|
||||
queryRenderedFeatures: mapQueryRenderedFeaturesMock,
|
||||
getStyle: mapGetStyleMock,
|
||||
getCanvas: jest.fn(() => ({
|
||||
getCanvas: jest.fn().mockReturnValue({
|
||||
style: { cursor: '' },
|
||||
})),
|
||||
}),
|
||||
}
|
||||
|
||||
const stubs = {
|
||||
@ -574,18 +579,6 @@ describe('map', () => {
|
||||
expect(links[1].getAttribute('href')).toBe('/groups/g1/journalism')
|
||||
})
|
||||
|
||||
it('clears pending leave timeout', () => {
|
||||
jest.useFakeTimers()
|
||||
wrapper.vm.popupOnLeaveTimeoutId = setTimeout(() => {}, 3000)
|
||||
mapQueryRenderedFeaturesMock.mockReturnValueOnce(features)
|
||||
onEventMocks.mouseenter({
|
||||
point: { x: 100, y: 200 },
|
||||
lngLat: { lng: 10.0, lat: 53.55 },
|
||||
})
|
||||
expect(wrapper.vm.popupOnLeaveTimeoutId).toBeNull()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('removes existing popup before showing new one', () => {
|
||||
mapboxgl.__popupInstance.isOpen.mockReturnValueOnce(true)
|
||||
mapQueryRenderedFeaturesMock.mockReturnValueOnce(features)
|
||||
@ -621,20 +614,10 @@ describe('map', () => {
|
||||
})
|
||||
|
||||
describe('mouseleave event', () => {
|
||||
it('sets timeout to remove popup when open', () => {
|
||||
jest.useFakeTimers()
|
||||
mapboxgl.__popupInstance.isOpen.mockReturnValueOnce(true)
|
||||
it('resets cursor style', () => {
|
||||
mapMock.getCanvas().style.cursor = 'pointer'
|
||||
onEventMocks.mouseleave()
|
||||
expect(wrapper.vm.popupOnLeaveTimeoutId).toBeTruthy()
|
||||
jest.advanceTimersByTime(3000)
|
||||
expect(mapboxgl.__popupInstance.remove).toHaveBeenCalled()
|
||||
jest.useRealTimers()
|
||||
})
|
||||
|
||||
it('does nothing when popup is not open', () => {
|
||||
mapboxgl.__popupInstance.isOpen.mockReturnValueOnce(false)
|
||||
onEventMocks.mouseleave()
|
||||
expect(wrapper.vm.popupOnLeaveTimeoutId).toBeFalsy()
|
||||
expect(mapMock.getCanvas().style.cursor).toBe('')
|
||||
})
|
||||
})
|
||||
|
||||
@ -1057,10 +1040,13 @@ describe('map', () => {
|
||||
})
|
||||
|
||||
describe('beforeDestroy', () => {
|
||||
it('removes resize listener', () => {
|
||||
it('removes resize listeners', () => {
|
||||
wrapper.vm.onMapLoad({ map: mapMock })
|
||||
const spy = jest.spyOn(window, 'removeEventListener')
|
||||
const geocoderHandler = wrapper.vm.geocoderCollapseHandler
|
||||
wrapper.destroy()
|
||||
expect(spy).toHaveBeenCalledWith('resize', wrapper.vm.updateMapPosition)
|
||||
expect(spy).toHaveBeenCalledWith('resize', geocoderHandler)
|
||||
spy.mockRestore()
|
||||
})
|
||||
})
|
||||
|
||||
@ -33,17 +33,19 @@
|
||||
</button>
|
||||
<div
|
||||
id="map-legend-content"
|
||||
v-show="legendOpen || !isMobile"
|
||||
:class="{ 'map-legend-content--hidden': isMobile && !legendOpen }"
|
||||
class="map-legend-content"
|
||||
role="region"
|
||||
:aria-label="$t('map.legend.title')"
|
||||
>
|
||||
<div v-for="type in markers.types" :key="type.id" class="map-legend-item">
|
||||
<img
|
||||
:alt="$t('map.legend.' + type.id)"
|
||||
:src="'/img/mapbox/marker-icons/' + type.icon.legendName"
|
||||
width="15"
|
||||
/>
|
||||
<span :style="{ color: type.color }">
|
||||
<os-icon
|
||||
:icon="icons.mapPinFilled"
|
||||
size="xl"
|
||||
:aria-label="$t('map.legend.' + type.id)"
|
||||
/>
|
||||
</span>
|
||||
{{ $t('map.legend.' + type.id) }}
|
||||
</div>
|
||||
</div>
|
||||
@ -61,6 +63,8 @@ import mapboxgl from 'mapbox-gl'
|
||||
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder'
|
||||
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css'
|
||||
import { mapGetters } from 'vuex'
|
||||
import { OsIcon } from '@ocelot-social/ui'
|
||||
import { iconRegistry } from '~/utils/iconRegistry'
|
||||
import { profileUserQuery } from '~/graphql/User'
|
||||
import { mapQuery } from '~/graphql/MapQuery'
|
||||
import mobile from '~/mixins/mobile'
|
||||
@ -73,6 +77,7 @@ export default {
|
||||
mixins: [mobile(maxMobileWidth)],
|
||||
components: {
|
||||
Empty,
|
||||
OsIcon,
|
||||
},
|
||||
head() {
|
||||
return {
|
||||
@ -96,33 +101,33 @@ export default {
|
||||
types: [
|
||||
{
|
||||
id: 'theUser',
|
||||
color: '#f79640',
|
||||
icon: {
|
||||
id: 'marker-orange',
|
||||
legendName: 'mapbox-marker-icon-orange.svg',
|
||||
mapName: 'mapbox-marker-icon-20px-orange.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'user',
|
||||
color: '#33c377',
|
||||
icon: {
|
||||
id: 'marker-green',
|
||||
legendName: 'mapbox-marker-icon-green.svg',
|
||||
mapName: 'mapbox-marker-icon-20px-green.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'group',
|
||||
color: '#f84d4d',
|
||||
icon: {
|
||||
id: 'marker-red',
|
||||
legendName: 'mapbox-marker-icon-red.svg',
|
||||
mapName: 'mapbox-marker-icon-20px-red.png',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'event',
|
||||
color: '#7753eb',
|
||||
icon: {
|
||||
id: 'marker-purple',
|
||||
legendName: 'mapbox-marker-icon-purple.svg',
|
||||
mapName: 'mapbox-marker-icon-20px-purple.png',
|
||||
},
|
||||
},
|
||||
@ -133,10 +138,12 @@ export default {
|
||||
isSourceAndLayerAdded: false,
|
||||
isFlyToCenter: false,
|
||||
popup: null,
|
||||
popupOnLeaveTimeoutId: null,
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.icons = iconRegistry
|
||||
},
|
||||
async mounted() {
|
||||
this.updateMapPosition()
|
||||
window.addEventListener('resize', this.updateMapPosition)
|
||||
@ -149,6 +156,9 @@ export default {
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.removeEventListener('resize', this.updateMapPosition)
|
||||
if (this.geocoderCollapseHandler) {
|
||||
window.removeEventListener('resize', this.geocoderCollapseHandler)
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@ -212,6 +222,21 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
addGeocoder() {
|
||||
this.geocoder = new MapboxGeocoder({
|
||||
accessToken: this.$env.MAPBOX_TOKEN,
|
||||
mapboxgl: this.mapboxgl,
|
||||
marker: false,
|
||||
collapsed: this.geocoderCollapsed,
|
||||
})
|
||||
this.map.addControl(this.geocoder, 'top-right')
|
||||
// Ensure geocoder stays at the top of the control group
|
||||
const container = this.geocoder.container
|
||||
const parent = container.parentNode
|
||||
if (parent && parent.firstChild !== container) {
|
||||
parent.insertBefore(container, parent.firstChild)
|
||||
}
|
||||
},
|
||||
updateMapPosition() {
|
||||
const navbar = document.getElementById('navbar')
|
||||
const footer = document.getElementById('footer')
|
||||
@ -239,15 +264,17 @@ export default {
|
||||
})
|
||||
|
||||
// add search field for locations
|
||||
this.map.addControl(
|
||||
new MapboxGeocoder({
|
||||
accessToken: this.$env.MAPBOX_TOKEN,
|
||||
mapboxgl: this.mapboxgl,
|
||||
marker: false,
|
||||
collapsed: window.innerWidth <= 810,
|
||||
}),
|
||||
'top-right',
|
||||
)
|
||||
this.geocoderCollapsed = window.innerWidth <= 810
|
||||
this.addGeocoder()
|
||||
this.geocoderCollapseHandler = () => {
|
||||
const shouldCollapse = window.innerWidth <= 810
|
||||
if (shouldCollapse !== this.geocoderCollapsed) {
|
||||
this.geocoderCollapsed = shouldCollapse
|
||||
this.map.removeControl(this.geocoder)
|
||||
this.addGeocoder()
|
||||
}
|
||||
}
|
||||
window.addEventListener('resize', this.geocoderCollapseHandler)
|
||||
|
||||
// add style switcher control
|
||||
let closePopoverHandler = null
|
||||
@ -335,10 +362,6 @@ export default {
|
||||
|
||||
// show popup for given features at coordinates
|
||||
const showPopup = (features, lngLat) => {
|
||||
if (this.popupOnLeaveTimeoutId) {
|
||||
clearTimeout(this.popupOnLeaveTimeoutId)
|
||||
this.popupOnLeaveTimeoutId = null
|
||||
}
|
||||
if (this.markers.popup.isOpen()) {
|
||||
this.markers.popup.remove()
|
||||
}
|
||||
@ -441,12 +464,7 @@ export default {
|
||||
})
|
||||
|
||||
this.map.on('mouseleave', 'markers', () => {
|
||||
if (this.markers.popup.isOpen()) {
|
||||
this.popupOnLeaveTimeoutId = setTimeout(() => {
|
||||
this.map.getCanvas().style.cursor = ''
|
||||
this.markers.popup.remove()
|
||||
}, 3000)
|
||||
}
|
||||
this.map.getCanvas().style.cursor = ''
|
||||
})
|
||||
|
||||
// Mobile: show popup on click/tap
|
||||
@ -722,6 +740,8 @@ export default {
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
font-family: $font-family-text;
|
||||
font-size: $font-size-base;
|
||||
}
|
||||
|
||||
.mgl-map-wrapper {
|
||||
@ -733,10 +753,22 @@ export default {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib.mapboxgl-compact {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.mapboxgl-ctrl-attrib-button {
|
||||
position: static;
|
||||
}
|
||||
|
||||
.mapboxgl-popup-content {
|
||||
max-height: 40vh;
|
||||
overflow: hidden;
|
||||
padding: 10px;
|
||||
border-radius: $border-radius-x-large;
|
||||
}
|
||||
|
||||
.map-popup-container {
|
||||
@ -747,14 +779,14 @@ export default {
|
||||
}
|
||||
|
||||
.mapboxgl-popup-close-button {
|
||||
font-size: 1.2rem;
|
||||
font-size: $font-size-large;
|
||||
padding: 2px 6px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.map-popup-header {
|
||||
font-weight: bold;
|
||||
font-size: 1.1em;
|
||||
font-size: $font-size-large;
|
||||
margin-bottom: 4px;
|
||||
padding-right: 16px;
|
||||
flex-shrink: 0;
|
||||
@ -804,7 +836,7 @@ export default {
|
||||
height: 29px;
|
||||
min-width: 29px;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
border-radius: $border-radius-x-large;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
overflow: hidden;
|
||||
|
||||
@ -832,9 +864,9 @@ export default {
|
||||
left: 10px;
|
||||
background: rgba(255, 255, 255, 0.75);
|
||||
backdrop-filter: blur(4px);
|
||||
border-radius: 4px;
|
||||
border-radius: $border-radius-x-large;
|
||||
z-index: 1;
|
||||
font-size: 0.8rem;
|
||||
font-size: $font-size-base;
|
||||
color: $color-neutral-10;
|
||||
}
|
||||
|
||||
@ -845,11 +877,13 @@ export default {
|
||||
border: none;
|
||||
background: rgba(0, 0, 0, 0.05);
|
||||
color: $color-neutral-10;
|
||||
border-radius: 4px;
|
||||
border-radius: $border-radius-x-large;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
font-size: $font-size-base;
|
||||
text-align: left;
|
||||
order: 1;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
&:hover,
|
||||
&:active {
|
||||
@ -858,8 +892,8 @@ export default {
|
||||
}
|
||||
|
||||
.map-legend-arrow {
|
||||
float: right;
|
||||
font-size: 0.7rem;
|
||||
font-size: $font-size-small;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.map-legend-content {
|
||||
@ -879,22 +913,34 @@ export default {
|
||||
}
|
||||
|
||||
.map-legend-toggle {
|
||||
display: block;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.map-legend-content {
|
||||
order: 0;
|
||||
}
|
||||
|
||||
.map-legend-content--hidden {
|
||||
visibility: hidden;
|
||||
height: 0;
|
||||
padding: 0 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.map-legend--open .map-legend-content {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.map-legend--open .map-legend-toggle {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.map-style-switcher {
|
||||
position: relative;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border-radius: $border-radius-x-large;
|
||||
box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
@ -922,7 +968,7 @@ export default {
|
||||
right: 100%;
|
||||
margin-right: 6px;
|
||||
background: white;
|
||||
border-radius: 4px;
|
||||
border-radius: $border-radius-x-large;
|
||||
box-shadow: 0 0 4px rgba(0, 0, 0, 0.3);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
@ -939,7 +985,7 @@ export default {
|
||||
border: none;
|
||||
background: none;
|
||||
cursor: pointer;
|
||||
font-size: 0.8rem;
|
||||
font-size: $font-size-base;
|
||||
text-align: left;
|
||||
|
||||
&:not(:last-child) {
|
||||
|
||||
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#4264fb" stroke="#314ccd" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#314ccd" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#5b7897" stroke="#23374d" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#23374d" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#33c377" stroke="#269561" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#269561" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#f79640" stroke="#ba7334" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#ba7334" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#ee4e8b" stroke="#b43b71" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#b43b71" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#7753eb" stroke="#5a3fc0" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#5a3fc0" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#f84d4d" stroke="#951212" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#951212" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,16 +0,0 @@
|
||||
<!-- Create a custom map style: https://studio.mapbox.com -->
|
||||
<svg id="marker" data-name="marker" xmlns="http://www.w3.org/2000/svg" width="20" height="48" viewBox="0 0 20 48">
|
||||
<g id="mapbox-marker-icon">
|
||||
<g id="icon">
|
||||
<ellipse id="shadow" cx="10" cy="27" rx="9" ry="5" fill="#c4c4c4" opacity="0.3" style="isolation: isolate"/>
|
||||
<g id="mask" opacity="0.3">
|
||||
<g id="group">
|
||||
<path id="shadow-2" data-name="shadow" fill="#bfbfbf" d="M10,32c5,0,9-2.2,9-5s-4-5-9-5-9,2.2-9,5S5,32,10,32Z" fill-rule="evenodd"/>
|
||||
</g>
|
||||
</g>
|
||||
<path id="color" fill="#d9d838" stroke="#a4a62d" stroke-width="0.5" d="M19.25,10.4a13.0663,13.0663,0,0,1-1.4607,5.2235,41.5281,41.5281,0,0,1-3.2459,5.5483c-1.1829,1.7369-2.3662,3.2784-3.2541,4.3859-.4438.5536-.8135.9984-1.0721,1.3046-.0844.1-.157.1852-.2164.2545-.06-.07-.1325-.1564-.2173-.2578-.2587-.3088-.6284-.7571-1.0723-1.3147-.8879-1.1154-2.0714-2.6664-3.2543-4.41a42.2677,42.2677,0,0,1-3.2463-5.5535A12.978,12.978,0,0,1,.75,10.4,9.4659,9.4659,0,0,1,10,.75,9.4659,9.4659,0,0,1,19.25,10.4Z"/>
|
||||
<path id="circle" fill="#fff" stroke="#a4a62d" stroke-width="0.5" d="M13.55,10A3.55,3.55,0,1,1,10,6.45,3.5484,3.5484,0,0,1,13.55,10Z"/>
|
||||
</g>
|
||||
</g>
|
||||
<rect width="20" height="48" fill="none"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.2 KiB |
@ -1,3 +1,4 @@
|
||||
// Global mock for matchMedia (used by touchDevice mixin, drag detection, etc.)
|
||||
import Vue from 'vue'
|
||||
import { createLocalVue } from '@vue/test-utils'
|
||||
import Vuex from 'vuex'
|
||||
@ -6,6 +7,14 @@ import Filters from '~/plugins/vue-filters'
|
||||
import InfiniteLoading from '~/plugins/vue-infinite-loading'
|
||||
import Directives from '~/plugins/vue-directives'
|
||||
import VueObserveVisibility from '~/plugins/vue-observe-visibility'
|
||||
|
||||
window.matchMedia =
|
||||
window.matchMedia ||
|
||||
jest.fn().mockImplementation((query) => ({
|
||||
matches: query === '(pointer: fine)' || query === '(min-width: 640px)',
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
}))
|
||||
require('intersection-observer')
|
||||
|
||||
// Fail tests on Vue warnings
|
||||
|
||||