refactoring hooks and api

This commit is contained in:
Anton 2023-08-29 13:11:39 +02:00
parent b0e611c9c5
commit c92b2be9fc
11 changed files with 580 additions and 434 deletions

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -18,5 +18,5 @@ export default {
}), }),
typescript() typescript()
], ],
external: ['react', 'react-dom', 'leaflet', 'react-leaflet', 'react-toastify' , 'react-toastify/dist/ReactToastify.css', 'tw-elements' ,'react-router-dom', 'react-leaflet-cluster', '@tanstack/react-query', 'leaflet/dist/leaflet.css', '@heroicons/react/20/solid'] external: ['react', 'react-dom', 'leaflet', 'react-leaflet', 'react-toastify' , 'react-toastify/dist/ReactToastify.css', 'tw-elements' ,'react-router-dom', 'react-leaflet-cluster', '@tanstack/react-query', 'tributejs', 'prop-types', 'leaflet/dist/leaflet.css', '@heroicons/react/20/solid']
} }

View File

@ -1,84 +1,47 @@
import * as React from 'react' import * as React from 'react'
import { Marker } from 'react-leaflet' import { Marker } from 'react-leaflet'
import { Item, Tag, LayerProps } from '../../types' import { Item, LayerProps } from '../../types'
import MarkerIconFactory from '../../Utils/MarkerIconFactory' import MarkerIconFactory from '../../Utils/MarkerIconFactory'
import { ItemViewPopup } from './Subcomponents/ItemViewPopup' import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
import { useTags } from './hooks/useTags' import { useItems, useResetItems, useSetItemsApi, useSetItemsData } from './hooks/useItems'
import { useAddItem, useItems, useResetItems } from './hooks/useItems'
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useAddLayer } from './hooks/useLayers'
import { ItemFormPopupProps, ItemFormPopup } from './Subcomponents/ItemFormPopup' import { ItemFormPopupProps, ItemFormPopup } from './Subcomponents/ItemFormPopup'
import { toast } from 'react-toastify' import { useFilterTags } from './hooks/useFilter'
import { hashTagRegex } from '../../Utils/HeighlightTags'
export const Layer = (props: LayerProps) => { export const Layer = (props: LayerProps) => {
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null); const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null);
const tags = useTags(); const filterTags = useFilterTags();
const items = useItems(); const items = useItems();
const addItem = useAddItem() const setItemsApi = useSetItemsApi();
const addLayer = useAddLayer(); const setItemsData = useSetItemsData();
const resetItems = useResetItems(); const resetItems = useResetItems();
const getItemTags = (item: Item) : Tag[] => {
const itemTagStrings = item.text.toLocaleLowerCase().match(hashTagRegex);
const itemTags: Tag[] = [];
itemTagStrings?.map(tag => {
if (tags.find(t => t.id === tag.slice(1))) { itemTags.push(tags.find(t => t.id === tag.slice(1))!) }
})
return itemTags;
};
const loadItems = async () => {
props.data?.map(item => {
if (item.position) {
item.layer = props;
addItem(item);
}
})
if (props.api) {
addLayer(props);
}
if(props.api) {
const result = await toast.promise(
props.api!.getItems(),
{
pending: `loading ${props.name} ...`,
success: `${props.name} loaded` ,
error: `error while loading ${props.name}`
}
);
if (result) {
result.map(item => {
if (item.position) {
addItem(({ layer: props, api: props.api, ...item }));
}
});
}
}
}
useEffect(() => { useEffect(() => {
resetItems(props); resetItems(props);
loadItems(); props.data && setItemsData(props);
props.api && setItemsApi(props);
}, [props.data, props.api]) }, [props.data, props.api])
return ( return (
<> <>
{items && {items &&
items.filter(item => item.layer?.name === props.name)?.map((place: Item) => { items.filter(item => item.layer?.name === props.name)?.filter(item => item)?.map((place: Item) => {
const tags = getItemTags(place); const tags = place.tags;
if(place.name === "docutopia")
console.log(tags);
let color1 = "#666"; let color1 = "#666";
let color2 = "RGBA(35, 31, 32, 0.2)"; let color2 = "RGBA(35, 31, 32, 0.2)";
if (tags[0]) { if (tags && tags[0]) {
color1 = tags[0].color; color1 = tags[0].color;
} }
if (tags[1]) { if (tags && tags[1]) {
color2 = tags[1].color; color2 = tags[1].color;
} }
return ( return (
@ -106,12 +69,12 @@ export const Layer = (props: LayerProps) => {
(props.children && React.Children.toArray(props.children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemForm") ? (props.children && React.Children.toArray(props.children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemForm") ?
React.Children.toArray(props.children).map((child) => React.Children.toArray(props.children).map((child) =>
React.isValidElement(child) && child.props.__TYPE === "ItemForm" ? React.isValidElement(child) && child.props.__TYPE === "ItemForm" ?
<ItemFormPopup key={props.setItemFormPopup?.name} position={props.itemFormPopup!.position} layer={props.itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={props.itemFormPopup!.item} api={props.api} >{child}</ItemFormPopup> <ItemFormPopup key={props.setItemFormPopup?.name} position={props.itemFormPopup!.position} layer={props.itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={props.itemFormPopup!.item} >{child}</ItemFormPopup>
: "" : ""
) )
: :
<> <>
<ItemFormPopup position={props.itemFormPopup!.position} layer={props.itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={props.itemFormPopup!.item} api={props.api} /> <ItemFormPopup position={props.itemFormPopup!.position} layer={props.itemFormPopup!.layer} setItemFormPopup={setItemFormPopup} item={props.itemFormPopup!.item} />
</>) </>)
} }
</> </>

View File

@ -14,7 +14,6 @@ export interface ItemFormPopupProps {
position: LatLng, position: LatLng,
layer: LayerProps, layer: LayerProps,
item?: Item, item?: Item,
api?: ItemsApi<any>,
children?: React.ReactNode, children?: React.ReactNode,
setItemFormPopup: React.Dispatch<React.SetStateAction<any>> setItemFormPopup: React.Dispatch<React.SetStateAction<any>>
} }
@ -47,22 +46,13 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
addTag({id: tag.slice(1), color: randomColor()}) addTag({id: tag.slice(1), color: randomColor()})
}); });
if (props.item) { if (props.item) {
formItem['id'] = props.item.id; await updateItem({...props.item, ...formItem});
await props.api?.updateItem!(formItem);
formItem['api'] = props.api;
formItem['layer'] = props.layer;
await updateItem(formItem);
setSpinner(false); setSpinner(false);
map.closePopup(); map.closePopup();
} }
else { else {
formItem['id'] = crypto.randomUUID(); await addItem({...formItem, id: crypto.randomUUID(), layer: props.layer});
await props.api?.createItem!(formItem);
formItem['api'] = props.api;
formItem['layer'] = props.layer;
await addItem(formItem);
setSpinner(false); setSpinner(false);
map.closePopup(); map.closePopup();
} }

View File

@ -19,7 +19,7 @@ export function HeaderView({ item, setItemFormPopup }: {
const removeItemFromMap = (event: React.MouseEvent<HTMLElement>) => { const removeItemFromMap = (event: React.MouseEvent<HTMLElement>) => {
setLoading(true); setLoading(true);
item.api?.deleteItem!(item.id) item.layer.api?.deleteItem!(item.id)
.then(() => removeItem(item)) .then(() => removeItem(item))
.then(() => map.closePopup()) .then(() => map.closePopup())
.then(()=>setLoading(false)) .then(()=>setLoading(false))
@ -41,7 +41,7 @@ export function HeaderView({ item, setItemFormPopup }: {
<b className="tw-text-xl tw-font-bold">{item.name}</b> <b className="tw-text-xl tw-font-bold">{item.name}</b>
</div> </div>
<div className='tw-col-span-1'> <div className='tw-col-span-1'>
{item.api && {item.layer.api &&
<div className="tw-dropdown tw-dropdown-bottom"> <div className="tw-dropdown tw-dropdown-bottom">
<label tabIndex={0} className="tw-btn tw-m-1 tw-bg-white hover:tw-bg-white tw-text-gray-500 hover:tw-text-gray-700 tw-leading-3 tw-border-none tw-min-h-0 tw-h-4"> <label tabIndex={0} className="tw-btn tw-m-1 tw-bg-white hover:tw-bg-white tw-text-gray-500 hover:tw-text-gray-700 tw-leading-3 tw-border-none tw-min-h-0 tw-h-4">
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
@ -49,7 +49,7 @@ export function HeaderView({ item, setItemFormPopup }: {
</svg> </svg>
</label> </label>
<ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box"> <ul tabIndex={0} className="tw-dropdown-content tw-menu tw-p-2 tw-shadow tw-bg-base-100 tw-rounded-box">
{item.api.updateItem && <li> {item.layer.api.updateItem && <li>
<a className='tw-bg-white hover:tw-bg-gray-300 !tw-text-blue-800 hover:tw-text-gray-700' onClick={openEditPopup}> <a className='tw-bg-white hover:tw-bg-gray-300 !tw-text-blue-800 hover:tw-text-gray-700' onClick={openEditPopup}>
<svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="tw-h-5 tw-w-5" viewBox="0 0 20 20" fill="currentColor">
<path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" /> <path d="M13.586 3.586a2 2 0 112.828 2.828l-.793.793-2.828-2.828.793-.793zM11.379 5.793L3 14.172V17h2.828l8.38-8.379-2.83-2.828z" />
@ -57,7 +57,7 @@ export function HeaderView({ item, setItemFormPopup }: {
</a> </a>
</li>} </li>}
{item.api.deleteItem && <li> {item.layer.api.deleteItem && <li>
<a className='tw-bg-white hover:tw-bg-gray-300 !tw-text-red-800 hover:tw-text-red-950' onClick={removeItemFromMap}> <a className='tw-bg-white hover:tw-bg-gray-300 !tw-text-red-800 hover:tw-text-red-950' onClick={removeItemFromMap}>
{loading ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span> {loading ? <span className="tw-loading tw-loading-spinner tw-loading-sm"></span>
: :

View File

@ -0,0 +1,84 @@
import { useCallback, useReducer, createContext, useContext } from "react";
import * as React from "react";
import {Tag} from "../../../types";
type ActionType =
| { type: "ADD"; tag: Tag }
| { type: "REMOVE"; id: string };
type UseTagManagerResult = ReturnType<typeof useTagsManager>;
const TagContext = createContext<UseTagManagerResult>({
filterTags: [],
addFilterTag: () => { },
removeFilterTag: () => { },
});
function useTagsManager(initialTags: Tag[]): {
filterTags: Tag[];
addFilterTag: (tag: Tag) => void;
removeFilterTag: (id: string) => void;
} {
const [filterTags, dispatch] = useReducer((state: Tag[], action: ActionType) => {
switch (action.type) {
case "ADD":
const exist = state.find((tag) =>
tag.id === action.tag.id ? true : false
);
if (!exist) return [
...state,
action.tag,
];
else return state;
case "REMOVE":
return state.filter(({ id }) => id !== action.id);
default:
throw new Error();
}
}, initialTags);
const addFilterTag = (tag: Tag) => {
dispatch({
type: "ADD",
tag,
});
};
const removeFilterTag = useCallback((id: string) => {
dispatch({
type: "REMOVE",
id,
});
}, []);
return { filterTags, addFilterTag, removeFilterTag };
}
export const TagsProvider: React.FunctionComponent<{
initialTags: Tag[], children?: React.ReactNode
}> = ({ initialTags, children }) => (
<TagContext.Provider value={useTagsManager(initialTags)}>
{children}
</TagContext.Provider>
);
export const useFilterTags = (): Tag[] => {
const { filterTags } = useContext(TagContext);
return filterTags;
};
export const useAddFilterTag = (): UseTagManagerResult["addFilterTag"] => {
const { addFilterTag } = useContext(TagContext);
return addFilterTag;
};
export const useRemoveFilterTag = (): UseTagManagerResult["removeFilterTag"] => {
const { removeFilterTag } = useContext(TagContext);
return removeFilterTag;
};

View File

@ -1,69 +1,124 @@
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext, useEffect } from "react";
import * as React from "react"; import * as React from "react";
import { Item, LayerProps } from "../../../types"; import { Item, ItemsApi, LayerProps, Tag } from "../../../types";
import { toast } from "react-toastify";
import { useAddLayer } from "./useLayers";
import { useTags } from "./useTags";
import { hashTagRegex } from "../../../Utils/HeighlightTags";
type ActionType = type ActionType =
| { type: "ADD"; item: Item } | { type: "ADD"; item: Item }
| { type: "UPDATE"; item: Item } | { type: "UPDATE"; item: Item }
| { type: "REMOVE"; item: Item } | { type: "REMOVE"; item: Item }
| { type: "RESET"; layer: LayerProps }; | { type: "RESET"; layer: LayerProps }
| { type: "ADD_TAGS" };
type UseItemManagerResult = ReturnType<typeof useItemsManager>; type UseItemManagerResult = ReturnType<typeof useItemsManager>;
const ItemContext = createContext<UseItemManagerResult>({ const ItemContext = createContext<UseItemManagerResult>({
items: [], items: [],
addItem: () => {}, addItem: () => { },
updateItem: () => {}, updateItem: () => { },
removeItem: () => {}, removeItem: () => { },
resetItems: () => {} resetItems: () => { },
setItemsApi: () => { },
setItemsData: () => { },
}); });
function useItemsManager (initialItems: Item[]): { function useItemsManager(initialItems: Item[]): {
items: Item[]; items: Item[];
addItem: (item: Item) => void; addItem: (item: Item) => void;
updateItem: (item: Item) => void; updateItem: (item: Item) => void;
removeItem: (item: Item) => void; removeItem: (item: Item) => void;
resetItems: (layer: LayerProps) => void; resetItems: (layer: LayerProps) => void;
setItemsApi: (layer: LayerProps) => void;
setItemsData: (layer: LayerProps) => void;
} { } {
const addLayer = useAddLayer();
const tags = useTags();
const [items, dispatch] = useReducer((state: Item[], action: ActionType) => { const [items, dispatch] = useReducer((state: Item[], action: ActionType) => {
switch (action.type) { switch (action.type) {
case "ADD": case "ADD":
const exist = state.find((item) => const exist = state.find((item) =>
item.id === action.item.id ? true : false item.id === action.item.id ? true : false
); );
if (!exist) return [ if (!exist) return [
...state, ...state,
action.item, action.item,
]; ];
else return state; else return state;
case "UPDATE": case "UPDATE":
return state.map((item) => { return state.map((item) => {
if (item.id === action.item.id) { if (item.id === action.item.id) {
return action.item return action.item
} }
return item return item
}); });
case "REMOVE": case "REMOVE":
return state.filter(item => item !== action.item); return state.filter(item => item !== action.item);
case "RESET": case "RESET":
return state.filter(item => item.layer.name !== action.layer.name); return state.filter(item => item.layer.name !== action.layer.name);
default: case "ADD_TAGS":
throw new Error(); return state.map(item => {
} const itemTagStrings = item.text.toLocaleLowerCase().match(hashTagRegex);
const itemTags: Tag[] = [];
itemTagStrings?.map(tag => {
if (tags.find(t => t.id === tag.slice(1))) { itemTags.push(tags.find(t => t.id === tag.slice(1))!) }
})
return { ...item, tags: itemTags }
})
default:
throw new Error();
}
}, initialItems); }, initialItems);
const addItem = useCallback((item: Item) => {
const setItemsApi = useCallback(async (layer: LayerProps) => {
layer.api?.createItem && addLayer(layer);
const result = await toast.promise(
layer.api!.getItems(),
{
pending: `loading ${layer.name} ...`,
success: `${layer.name} loaded`,
error: `error while loading ${layer.name}`
}
);
if (result) {
result.map(item => {
dispatch({ type: "ADD", item: { ...item, layer: layer } });
})
}
dispatch({ type: "ADD_TAGS" })
}, [])
const setItemsData = useCallback((layer: LayerProps) => {
layer.data?.map(item => {
dispatch({ type: "ADD", item: { ...item, layer: layer } })
})
dispatch({ type: "ADD_TAGS" })
}, []);
const addItem = useCallback(async (item: Item) => {
await item.layer.api?.createItem!(item);
dispatch({ dispatch({
type: "ADD", type: "ADD",
item, item,
}); });
dispatch({ type: "ADD_TAGS" })
}, []); }, []);
const updateItem = useCallback((item: Item) => { const updateItem = useCallback(async (item: Item) => {
await item.layer.api?.updateItem!(item);
dispatch({ dispatch({
type: "UPDATE", type: "UPDATE",
item, item,
}); });
dispatch({ type: "ADD_TAGS" })
}, []); }, []);
const removeItem = useCallback((item: Item) => { const removeItem = useCallback((item: Item) => {
@ -80,11 +135,16 @@ function useItemsManager (initialItems: Item[]): {
}); });
}, []); }, []);
return { items, updateItem, addItem, removeItem, resetItems }; useEffect(() => {
dispatch({ type: "ADD_TAGS" })
}, [tags])
return { items, updateItem, addItem, removeItem, resetItems, setItemsApi, setItemsData };
} }
export const ItemsProvider: React.FunctionComponent<{ export const ItemsProvider: React.FunctionComponent<{
initialItems: Item[], children?: React.ReactNode initialItems: Item[], children?: React.ReactNode
}> = ({ initialItems, children }) => ( }> = ({ initialItems, children }) => (
<ItemContext.Provider value={useItemsManager(initialItems)}> <ItemContext.Provider value={useItemsManager(initialItems)}>
{children} {children}
@ -109,9 +169,19 @@ export const useUpdateItem = (): UseItemManagerResult["updateItem"] => {
export const useRemoveItem = (): UseItemManagerResult["removeItem"] => { export const useRemoveItem = (): UseItemManagerResult["removeItem"] => {
const { removeItem } = useContext(ItemContext); const { removeItem } = useContext(ItemContext);
return removeItem; return removeItem;
}; };
export const useResetItems = (): UseItemManagerResult["resetItems"] => { export const useResetItems = (): UseItemManagerResult["resetItems"] => {
const { resetItems } = useContext(ItemContext); const { resetItems } = useContext(ItemContext);
return resetItems; return resetItems;
};
export const useSetItemsApi = (): UseItemManagerResult["setItemsApi"] => {
const { setItemsApi } = useContext(ItemContext);
return setItemsApi;
};
export const useSetItemsData = (): UseItemManagerResult["setItemsData"] => {
const { setItemsData } = useContext(ItemContext);
return setItemsData;
}; };

View File

@ -1,10 +1,9 @@
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext } from "react";
import * as React from "react"; import * as React from "react";
import { Item, LayerProps } from "../../../types"; import { LayerProps } from "../../../types";
type ActionType = type ActionType =
| { type: "ADD LAYER"; layer: LayerProps } | { type: "ADD LAYER"; layer: LayerProps }
| { type: "ADD ITEM"; item: Item; layer: LayerProps };
type UseItemManagerResult = ReturnType<typeof useLayerManager>; type UseItemManagerResult = ReturnType<typeof useLayerManager>;

View File

@ -1,7 +1,6 @@
import { useCallback, useReducer, createContext, useContext } from "react"; import { useCallback, useReducer, createContext, useContext } from "react";
import * as React from "react"; import * as React from "react";
import { ItemsApi, Tag } from "../../../types"; import { ItemsApi, Tag } from "../../../types";
import { toast } from "react-toastify";
type ActionType = type ActionType =
| { type: "ADD"; tag: Tag } | { type: "ADD"; tag: Tag }
@ -43,7 +42,7 @@ function useTagsManager(initialTags: Tag[]): {
} }
}, initialTags); }, initialTags);
const [api, setApi] = React.useState<ItemsApi<Tag> | undefined>(undefined) const [api, setApi] = React.useState<ItemsApi<Tag>>({} as ItemsApi<Tag>)
const setTagApi = useCallback(async (api: ItemsApi<Tag>) => { const setTagApi = useCallback(async (api: ItemsApi<Tag>) => {
setApi(api); setApi(api);

View File

@ -1,30 +1,18 @@
/**
export const randomColor = (brightness) => {
function randomChannel(brightness){
var r = 255-brightness;
var n = 0|((Math.random() * r) + brightness);
var s = n.toString(16);
return (s.length==1) ? '0'+s : s;
}
return '#' + randomChannel(brightness) + randomChannel(brightness) + randomChannel(brightness);
}
*/
export const randomColor = () => { export const randomColor = () => {
return hsvToRgb((Math.random()+golden_ratio_conjugate)%1,0.8, 0.7) return hsvToHex((Math.random() + golden_ratio_conjugate) % 1, 0.8, 0.7)
} }
const golden_ratio_conjugate = 0.618033988749895; const golden_ratio_conjugate = 0.618033988749895;
function hsvToRgb(h, s, v){ function hsvToHex(h, s, v) {
var r, g, b; var r, g, b;
var i = (Math.floor(h * 6)); var i = (Math.floor(h * 6));
var f = h * 6 - i; var f = h * 6 - i;
var p = v * (1 - s); var p = v * (1 - s);
var q = v * (1 - f * s); var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s); var t = v * (1 - (1 - f) * s);
switch(i % 6){ switch (i % 6) {
case 0: r = v, g = t, b = p; break; case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break; case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break; case 2: r = p, g = v, b = t; break;
@ -33,11 +21,11 @@ const golden_ratio_conjugate = 0.618033988749895;
case 5: r = v, g = p, b = q; break; case 5: r = v, g = p, b = q; break;
} }
return rgbToHex(Math.round(r*255), Math.round(g*255), Math.round(b*255)) return rgbToHex(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255))
} }
const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => { const rgbToHex = (r, g, b) => '#' + [r, g, b].map(x => {
const hex = x.toString(16) const hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex return hex.length === 1 ? '0' + hex : hex
}).join('') }).join('')

View File

@ -20,7 +20,6 @@ export interface LayerProps {
markerIcon: string, markerIcon: string,
markerShape: string, markerShape: string,
markerDefaultColor: string, markerDefaultColor: string,
tags?: Tag[],
api?: ItemsApi<any>, api?: ItemsApi<any>,
setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>, setItemFormPopup?: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>,
itemFormPopup?: ItemFormPopupProps | null itemFormPopup?: ItemFormPopupProps | null
@ -35,8 +34,8 @@ export class Item {
date_updated?: string | null; date_updated?: string | null;
start?: string; start?: string;
end?: string; end?: string;
tags?: number[];
api?: ItemsApi<any>; api?: ItemsApi<any>;
tags?: Tag[];
[key: string]: any; [key: string]: any;
constructor(id:string|number,name:string,text:string,position:Geometry, layer?: LayerProps, api?: ItemsApi<any>){ constructor(id:string|number,name:string,text:string,position:Geometry, layer?: LayerProps, api?: ItemsApi<any>){
this.id = id; this.id = id;