chore(eslint): make react-hooks/exhaustive-deps rule as an error
This commit is contained in:
parent
c6425cd1a6
commit
68e89af775
8 changed files with 151 additions and 110 deletions
|
@ -23,6 +23,7 @@ module.exports = {
|
||||||
rules: {
|
rules: {
|
||||||
'prettier/prettier': 'error',
|
'prettier/prettier': 'error',
|
||||||
'react-hooks/rules-of-hooks': 'error',
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'error',
|
||||||
'react/prop-types': 'off'
|
'react/prop-types': 'off'
|
||||||
},
|
},
|
||||||
settings: {
|
settings: {
|
||||||
|
@ -51,7 +52,12 @@ module.exports = {
|
||||||
'plugin:@typescript-eslint/recommended',
|
'plugin:@typescript-eslint/recommended',
|
||||||
'plugin:react-hooks/recommended',
|
'plugin:react-hooks/recommended',
|
||||||
'prettier'
|
'prettier'
|
||||||
]
|
],
|
||||||
|
rules: {
|
||||||
|
'prettier/prettier': 'error',
|
||||||
|
'react-hooks/rules-of-hooks': 'error',
|
||||||
|
'react-hooks/exhaustive-deps': 'error'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
|
@ -26,6 +26,19 @@ import { useDeferredSubmit, useHiddenField } from './shared/hooks';
|
||||||
|
|
||||||
const Context = createContext();
|
const Context = createContext();
|
||||||
|
|
||||||
|
const optionValueByLabel = (values, options, label) => {
|
||||||
|
const maybeOption = values.includes(label)
|
||||||
|
? [label, label]
|
||||||
|
: options.find(([optionLabel]) => optionLabel == label);
|
||||||
|
return maybeOption ? maybeOption[1] : undefined;
|
||||||
|
};
|
||||||
|
const optionLabelByValue = (values, options, value) => {
|
||||||
|
const maybeOption = values.includes(value)
|
||||||
|
? [value, value]
|
||||||
|
: options.find(([, optionValue]) => optionValue == value);
|
||||||
|
return maybeOption ? maybeOption[0] : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
function ComboMultiple({
|
function ComboMultiple({
|
||||||
options,
|
options,
|
||||||
id,
|
id,
|
||||||
|
@ -40,9 +53,6 @@ function ComboMultiple({
|
||||||
invariant(id || label, 'ComboMultiple: `id` or a `label` are required');
|
invariant(id || label, 'ComboMultiple: `id` or a `label` are required');
|
||||||
invariant(group, 'ComboMultiple: `group` is required');
|
invariant(group, 'ComboMultiple: `group` is required');
|
||||||
|
|
||||||
if (!Array.isArray(options[0])) {
|
|
||||||
options = options.filter((o) => o).map((o) => [o, o]);
|
|
||||||
}
|
|
||||||
const inputRef = useRef();
|
const inputRef = useRef();
|
||||||
const [term, setTerm] = useState('');
|
const [term, setTerm] = useState('');
|
||||||
const [selections, setSelections] = useState(selected);
|
const [selections, setSelections] = useState(selected);
|
||||||
|
@ -51,25 +61,22 @@ function ComboMultiple({
|
||||||
const removedLabelledby = `${inputId}-remove`;
|
const removedLabelledby = `${inputId}-remove`;
|
||||||
const selectedLabelledby = `${inputId}-selected`;
|
const selectedLabelledby = `${inputId}-selected`;
|
||||||
|
|
||||||
const optionValueByLabel = (label) => {
|
const optionsWithLabels = useMemo(
|
||||||
const maybeOption = newValues.includes(label)
|
() =>
|
||||||
? [label, label]
|
Array.isArray(options[0])
|
||||||
: options.find(([optionLabel]) => optionLabel == label);
|
? options
|
||||||
return maybeOption ? maybeOption[1] : undefined;
|
: options.filter((o) => o).map((o) => [o, o]),
|
||||||
};
|
[options]
|
||||||
const optionLabelByValue = (value) => {
|
);
|
||||||
const maybeOption = newValues.includes(value)
|
|
||||||
? [value, value]
|
|
||||||
: options.find(([, optionValue]) => optionValue == value);
|
|
||||||
return maybeOption ? maybeOption[0] : undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
const extraOptions = useMemo(
|
const extraOptions = useMemo(
|
||||||
() =>
|
() =>
|
||||||
acceptNewValues && term && term.length > 2 && !optionLabelByValue(term)
|
acceptNewValues &&
|
||||||
|
term &&
|
||||||
|
term.length > 2 &&
|
||||||
|
!optionLabelByValue(newValues, optionsWithLabels, term)
|
||||||
? [[term, term]]
|
? [[term, term]]
|
||||||
: [],
|
: [],
|
||||||
[acceptNewValues, term, newValues.join(',')]
|
[acceptNewValues, term, optionsWithLabels, newValues]
|
||||||
);
|
);
|
||||||
const results = useMemo(
|
const results = useMemo(
|
||||||
() =>
|
() =>
|
||||||
|
@ -77,12 +84,12 @@ function ComboMultiple({
|
||||||
...extraOptions,
|
...extraOptions,
|
||||||
...(term
|
...(term
|
||||||
? matchSorter(
|
? matchSorter(
|
||||||
options.filter(([label]) => !label.startsWith('--')),
|
optionsWithLabels.filter(([label]) => !label.startsWith('--')),
|
||||||
term
|
term
|
||||||
)
|
)
|
||||||
: options)
|
: optionsWithLabels)
|
||||||
].filter(([, value]) => !selections.includes(value)),
|
].filter(([, value]) => !selections.includes(value)),
|
||||||
[term, selections.join(','), newValues.join(',')]
|
[term, selections, extraOptions, optionsWithLabels]
|
||||||
);
|
);
|
||||||
const [, setHiddenFieldValue, hiddenField] = useHiddenField(group, name);
|
const [, setHiddenFieldValue, hiddenField] = useHiddenField(group, name);
|
||||||
const awaitFormSubmit = useDeferredSubmit(hiddenField);
|
const awaitFormSubmit = useDeferredSubmit(hiddenField);
|
||||||
|
@ -100,7 +107,7 @@ function ComboMultiple({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onSelect = (value) => {
|
const onSelect = (value) => {
|
||||||
const maybeValue = [...extraOptions, ...options].find(
|
const maybeValue = [...extraOptions, ...optionsWithLabels].find(
|
||||||
([val]) => val == value
|
([val]) => val == value
|
||||||
);
|
);
|
||||||
const selectedValue = maybeValue && maybeValue[1];
|
const selectedValue = maybeValue && maybeValue[1];
|
||||||
|
@ -128,7 +135,7 @@ function ComboMultiple({
|
||||||
};
|
};
|
||||||
|
|
||||||
const onRemove = (label) => {
|
const onRemove = (label) => {
|
||||||
const optionValue = optionValueByLabel(label);
|
const optionValue = optionValueByLabel(newValues, options, label);
|
||||||
if (optionValue) {
|
if (optionValue) {
|
||||||
saveSelection((selections) =>
|
saveSelection((selections) =>
|
||||||
selections.filter((value) => value != optionValue)
|
selections.filter((value) => value != optionValue)
|
||||||
|
@ -149,7 +156,9 @@ function ComboMultiple({
|
||||||
) {
|
) {
|
||||||
if (
|
if (
|
||||||
term &&
|
term &&
|
||||||
[...extraOptions, ...options].map(([label]) => label).includes(term)
|
[...extraOptions, ...optionsWithLabels]
|
||||||
|
.map(([label]) => label)
|
||||||
|
.includes(term)
|
||||||
) {
|
) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
onSelect(term);
|
onSelect(term);
|
||||||
|
@ -172,7 +181,9 @@ function ComboMultiple({
|
||||||
const onBlur = () => {
|
const onBlur = () => {
|
||||||
const shouldSelect =
|
const shouldSelect =
|
||||||
term &&
|
term &&
|
||||||
[...extraOptions, ...options].map(([label]) => label).includes(term);
|
[...extraOptions, ...optionsWithLabels]
|
||||||
|
.map(([label]) => label)
|
||||||
|
.includes(term);
|
||||||
|
|
||||||
awaitFormSubmit(() => {
|
awaitFormSubmit(() => {
|
||||||
if (shouldSelect) {
|
if (shouldSelect) {
|
||||||
|
@ -199,7 +210,7 @@ function ComboMultiple({
|
||||||
<ComboboxToken
|
<ComboboxToken
|
||||||
key={selection}
|
key={selection}
|
||||||
describedby={removedLabelledby}
|
describedby={removedLabelledby}
|
||||||
value={optionLabelByValue(selection)}
|
value={optionLabelByValue(newValues, options, selection)}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useCallback, useRef } from 'react';
|
import React, { useState, useRef, ChangeEventHandler } from 'react';
|
||||||
import { useDebounce } from 'use-debounce';
|
import { useDebounce } from 'use-debounce';
|
||||||
import { useQuery } from 'react-query';
|
import { useQuery } from 'react-query';
|
||||||
import {
|
import {
|
||||||
|
@ -68,7 +68,7 @@ function ComboSearch<Result>({
|
||||||
const [, value, label] = transformResult(result);
|
const [, value, label] = transformResult(result);
|
||||||
return label ?? value;
|
return label ?? value;
|
||||||
};
|
};
|
||||||
const setExternalValueAndId = useCallback((label: string) => {
|
const setExternalValueAndId = (label: string) => {
|
||||||
const { key, value, result } = resultsMap.current[label];
|
const { key, value, result } = resultsMap.current[label];
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
onChange(value, result);
|
onChange(value, result);
|
||||||
|
@ -76,11 +76,12 @@ function ComboSearch<Result>({
|
||||||
setExternalId(key);
|
setExternalId(key);
|
||||||
setExternalValue(value);
|
setExternalValue(value);
|
||||||
}
|
}
|
||||||
}, []);
|
};
|
||||||
const awaitFormSubmit = useDeferredSubmit(hiddenField);
|
const awaitFormSubmit = useDeferredSubmit(hiddenField);
|
||||||
|
|
||||||
const handleOnChange = useCallback(
|
const handleOnChange: ChangeEventHandler<HTMLInputElement> = ({
|
||||||
({ target: { value } }) => {
|
target: { value }
|
||||||
|
}) => {
|
||||||
setValue(value);
|
setValue(value);
|
||||||
if (!value) {
|
if (!value) {
|
||||||
if (onChange) {
|
if (onChange) {
|
||||||
|
@ -96,16 +97,14 @@ function ComboSearch<Result>({
|
||||||
setExternalValue(value);
|
setExternalValue(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
};
|
||||||
[minimumInputLength]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleOnSelect = useCallback((value: string) => {
|
const handleOnSelect = (value: string) => {
|
||||||
setExternalValueAndId(value);
|
setExternalValueAndId(value);
|
||||||
setValue(value);
|
setValue(value);
|
||||||
setSearchTerm('');
|
setSearchTerm('');
|
||||||
awaitFormSubmit.done();
|
awaitFormSubmit.done();
|
||||||
}, []);
|
};
|
||||||
|
|
||||||
const { isSuccess, data } = useQuery<void, void, unknown, QueryKey>(
|
const { isSuccess, data } = useQuery<void, void, unknown, QueryKey>(
|
||||||
[scope, debouncedSearchTerm, scopeExtra],
|
[scope, debouncedSearchTerm, scopeExtra],
|
||||||
|
@ -117,14 +116,14 @@ function ComboSearch<Result>({
|
||||||
const results =
|
const results =
|
||||||
isSuccess && data ? transformResults(debouncedSearchTerm, data) : [];
|
isSuccess && data ? transformResults(debouncedSearchTerm, data) : [];
|
||||||
|
|
||||||
const onBlur = useCallback(() => {
|
const onBlur = () => {
|
||||||
if (!allowInputValues && isSuccess && results[0]) {
|
if (!allowInputValues && isSuccess && results[0]) {
|
||||||
const label = getLabel(results[0]);
|
const label = getLabel(results[0]);
|
||||||
awaitFormSubmit(() => {
|
awaitFormSubmit(() => {
|
||||||
handleOnSelect(label);
|
handleOnSelect(label);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, [data]);
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Combobox onSelect={handleOnSelect}>
|
<Combobox onSelect={handleOnSelect}>
|
||||||
|
|
|
@ -28,7 +28,8 @@ export function CadastreLayer({
|
||||||
const map = useMapLibre();
|
const map = useMapLibre();
|
||||||
const selectedCadastresRef = useRef(new Set<string>());
|
const selectedCadastresRef = useRef(new Set<string>());
|
||||||
|
|
||||||
const highlightFeature = useCallback((cid: string, highlight: boolean) => {
|
const highlightFeature = useCallback(
|
||||||
|
(cid: string, highlight: boolean) => {
|
||||||
if (highlight) {
|
if (highlight) {
|
||||||
selectedCadastresRef.current.add(cid);
|
selectedCadastresRef.current.add(cid);
|
||||||
} else {
|
} else {
|
||||||
|
@ -43,9 +44,12 @@ export function CadastreLayer({
|
||||||
...selectedCadastresRef.current
|
...selectedCadastresRef.current
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[map]
|
||||||
|
);
|
||||||
|
|
||||||
const hoverFeature = useCallback((feature: Feature, hover: boolean) => {
|
const hoverFeature = useCallback(
|
||||||
|
(feature: Feature, hover: boolean) => {
|
||||||
if (!selectedCadastresRef.current.has(feature.properties?.id)) {
|
if (!selectedCadastresRef.current.has(feature.properties?.id)) {
|
||||||
map.setFeatureState(
|
map.setFeatureState(
|
||||||
{
|
{
|
||||||
|
@ -56,7 +60,9 @@ export function CadastreLayer({
|
||||||
{ hover }
|
{ hover }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[map]
|
||||||
|
);
|
||||||
|
|
||||||
useCadastres(featureCollection, {
|
useCadastres(featureCollection, {
|
||||||
hoverFeature,
|
hoverFeature,
|
||||||
|
|
|
@ -68,7 +68,9 @@ export function DrawLayer({
|
||||||
drawRef.current = null;
|
drawRef.current = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [enabled]);
|
// We only want to rerender draw layer on component mount or when the layer is toggled.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [map, enabled]);
|
||||||
|
|
||||||
const onSetId = useCallback(({ detail }) => {
|
const onSetId = useCallback(({ detail }) => {
|
||||||
drawRef.current?.setFeatureProperty(detail.lid, 'id', detail.id);
|
drawRef.current?.setFeatureProperty(detail.lid, 'id', detail.id);
|
||||||
|
@ -167,7 +169,9 @@ function useExternalEvents(
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fitBounds(featureCollection.bbox as LngLatBoundsLike);
|
fitBounds(featureCollection.bbox as LngLatBoundsLike);
|
||||||
}, []);
|
// We only want to zoom on bbox on component mount.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [fitBounds]);
|
||||||
|
|
||||||
useEvent('map:feature:focus', onFeatureFocus);
|
useEvent('map:feature:focus', onFeatureFocus);
|
||||||
useEvent('map:feature:create', onFeatureCreate);
|
useEvent('map:feature:create', onFeatureCreate);
|
||||||
|
|
|
@ -44,13 +44,13 @@ export function GeoJSONLayer({
|
||||||
popup.remove();
|
popup.remove();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[popup]
|
[map, popup]
|
||||||
);
|
);
|
||||||
|
|
||||||
const onMouseLeave = useCallback(() => {
|
const onMouseLeave = useCallback(() => {
|
||||||
map.getCanvas().style.cursor = '';
|
map.getCanvas().style.cursor = '';
|
||||||
popup.remove();
|
popup.remove();
|
||||||
}, [popup]);
|
}, [map, popup]);
|
||||||
|
|
||||||
useExternalEvents(featureCollection);
|
useExternalEvents(featureCollection);
|
||||||
|
|
||||||
|
@ -99,17 +99,22 @@ export function GeoJSONLayer({
|
||||||
|
|
||||||
function useExternalEvents(featureCollection: FeatureCollection) {
|
function useExternalEvents(featureCollection: FeatureCollection) {
|
||||||
const fitBounds = useFitBounds();
|
const fitBounds = useFitBounds();
|
||||||
const onFeatureFocus = useCallback(({ detail }) => {
|
const onFeatureFocus = useCallback(
|
||||||
|
({ detail }) => {
|
||||||
const { id } = detail;
|
const { id } = detail;
|
||||||
const feature = findFeature(featureCollection, id);
|
const feature = findFeature(featureCollection, id);
|
||||||
if (feature) {
|
if (feature) {
|
||||||
fitBounds(getBounds(feature.geometry));
|
fitBounds(getBounds(feature.geometry));
|
||||||
}
|
}
|
||||||
}, []);
|
},
|
||||||
|
[featureCollection, fitBounds]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fitBounds(featureCollection.bbox as LngLatBoundsLike);
|
fitBounds(featureCollection.bbox as LngLatBoundsLike);
|
||||||
}, []);
|
// We only want to zoom on bbox on component mount.
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [fitBounds]);
|
||||||
|
|
||||||
useEvent('map:feature:focus', onFeatureFocus);
|
useEvent('map:feature:focus', onFeatureFocus);
|
||||||
}
|
}
|
||||||
|
@ -139,7 +144,7 @@ function LineStringLayer({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
paint: lineStringSelectionLine
|
paint: lineStringSelectionLine
|
||||||
});
|
});
|
||||||
}, []);
|
}, [map, layerId, sourceId, feature]);
|
||||||
|
|
||||||
useMapEvent('mouseenter', onMouseEnter, layerId);
|
useMapEvent('mouseenter', onMouseEnter, layerId);
|
||||||
useMapEvent('mouseleave', onMouseLeave, layerId);
|
useMapEvent('mouseleave', onMouseLeave, layerId);
|
||||||
|
@ -172,7 +177,7 @@ function PointLayer({
|
||||||
type: 'circle',
|
type: 'circle',
|
||||||
paint: pointSelectionCircle
|
paint: pointSelectionCircle
|
||||||
});
|
});
|
||||||
}, []);
|
}, [map, layerId, sourceId, feature]);
|
||||||
|
|
||||||
useMapEvent('mouseenter', onMouseEnter, layerId);
|
useMapEvent('mouseenter', onMouseEnter, layerId);
|
||||||
useMapEvent('mouseleave', onMouseLeave, layerId);
|
useMapEvent('mouseleave', onMouseLeave, layerId);
|
||||||
|
@ -212,7 +217,7 @@ function PolygonLayer({
|
||||||
type: 'fill',
|
type: 'fill',
|
||||||
paint: polygonSelectionFill
|
paint: polygonSelectionFill
|
||||||
});
|
});
|
||||||
}, []);
|
}, [map, layerId, lineLayerId, sourceId, feature]);
|
||||||
|
|
||||||
useMapEvent('mouseenter', onMouseEnter, layerId);
|
useMapEvent('mouseenter', onMouseEnter, layerId);
|
||||||
useMapEvent('mouseleave', onMouseLeave, layerId);
|
useMapEvent('mouseleave', onMouseLeave, layerId);
|
||||||
|
|
|
@ -5,7 +5,8 @@ import React, {
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
createContext
|
createContext,
|
||||||
|
useCallback
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import maplibre, { Map, Style, NavigationControl } from 'maplibre-gl';
|
import maplibre, { Map, Style, NavigationControl } from 'maplibre-gl';
|
||||||
|
|
||||||
|
@ -37,11 +38,14 @@ export function MapLibre({ children, header, footer, layers }: MapLibreProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
const [map, setMap] = useState<Map | null>();
|
const [map, setMap] = useState<Map | null>();
|
||||||
|
|
||||||
const onStyleChange = (style: Style) => {
|
const onStyleChange = useCallback(
|
||||||
|
(style: Style) => {
|
||||||
if (map) {
|
if (map) {
|
||||||
map.setStyle(style);
|
map.setStyle(style);
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
|
[map]
|
||||||
|
);
|
||||||
const { style, ...mapStyleProps } = useStyle(layers, onStyleChange);
|
const { style, ...mapStyleProps } = useStyle(layers, onStyleChange);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -56,7 +60,7 @@ export function MapLibre({ children, header, footer, layers }: MapLibreProps) {
|
||||||
setMap(map);
|
setMap(map);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, []);
|
}, [map, style, isSupported]);
|
||||||
|
|
||||||
if (!isSupported) {
|
if (!isSupported) {
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -12,16 +12,22 @@ import { useMapLibre } from './MapLibre';
|
||||||
|
|
||||||
export function useFitBounds() {
|
export function useFitBounds() {
|
||||||
const map = useMapLibre();
|
const map = useMapLibre();
|
||||||
return useCallback((bbox: LngLatBoundsLike) => {
|
return useCallback(
|
||||||
|
(bbox: LngLatBoundsLike) => {
|
||||||
map.fitBounds(bbox, { padding: 100 });
|
map.fitBounds(bbox, { padding: 100 });
|
||||||
}, []);
|
},
|
||||||
|
[map]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useFlyTo() {
|
export function useFlyTo() {
|
||||||
const map = useMapLibre();
|
const map = useMapLibre();
|
||||||
return useCallback((zoom: number, center: [number, number]) => {
|
return useCallback(
|
||||||
|
(zoom: number, center: [number, number]) => {
|
||||||
map.flyTo({ zoom, center });
|
map.flyTo({ zoom, center });
|
||||||
}, []);
|
},
|
||||||
|
[map]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEvent(eventName: string, callback: EventListener) {
|
export function useEvent(eventName: string, callback: EventListener) {
|
||||||
|
@ -104,7 +110,7 @@ export function useStyle(
|
||||||
[styleId, enabledLayers]
|
[styleId, enabledLayers]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => onStyleChange(style), [style]);
|
useEffect(() => onStyleChange(style), [onStyleChange, style]);
|
||||||
|
|
||||||
return { style, layers, setStyle, setLayerEnabled, setLayerOpacity };
|
return { style, layers, setStyle, setLayerEnabled, setLayerOpacity };
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue