Tag API implementation

This commit is contained in:
Anton 2023-08-27 20:25:52 +02:00
parent 18bb8e6ad2
commit b0e611c9c5
8 changed files with 141 additions and 73 deletions

View File

@ -28,35 +28,36 @@ export function TextAreaInput({ labelTitle, dataField, labelStyle, containerStyl
const init = useRef(false)
const tags = useTags();
const values: KeyValue[] = [];
let values: KeyValue[] = [];
tags.map(tag => {
values.push({ key: tag.id, value: tag.id, color: tag.color })
})
var tribute = new Tribute({
containerClass: 'tw-z-500 tw-bg-white tw-p-2 tw-rounded',
containerClass: 'tw-z-500 tw-bg-white tw-p-2 tw-rounded-lg tw-shadow',
selectClass: 'tw-font-bold',
trigger: "#",
values: values,
noMatchTemplate: () => {
return ""
},
menuItemTemplate: function (item) {
console.log(item);
return `<span style="color: ${item.original.color}; padding: 5px; boarder-radius: 3px;">#${item.string}</span>`;
},
}
});
useEffect(() => {
if (!init.current) {
if (ref.current) {
console.log("check");
tribute.attach(ref.current);
}
init.current = true;
}
}, [ref])
return (
<div className={`tw-form-control tw-w-full ${containerStyle ? containerStyle : ""}`}>
{labelTitle ? <label className="tw-label">

View File

@ -9,41 +9,29 @@ import { useEffect, useState } from 'react'
import { useAddLayer } from './hooks/useLayers'
import { ItemFormPopupProps, ItemFormPopup } from './Subcomponents/ItemFormPopup'
import { toast } from 'react-toastify'
import { hashTagRegex } from '../../Utils/HeighlightTags'
export const Layer = (props: LayerProps) => {
const [itemFormPopup, setItemFormPopup] = useState<ItemFormPopupProps | null>(null);
const tags = useTags();
// create a JS-Map with all Tags
const tagMap = new Map(tags?.map(key => [key.id, key]));
// returns all tags for passed item
const getTags = (item: Item) => {
const regex = /(^|\B)#(?![0-9_]+\b)([a-zA-Z0-9_]{1,30})(\b|\r)/g;
const strings = item.text.toLocaleLowerCase().match(regex);
const tags: Tag[] = [];
strings?.map(tag => {
if (tagMap.has(tag.slice(1))) { tags.push(tagMap.get(tag.slice(1))!) }
})
return tags;
};
const items = useItems();
const addItem = useAddItem()
const addLayer = useAddLayer();
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) {
@ -51,6 +39,10 @@ export const Layer = (props: LayerProps) => {
addItem(item);
}
})
if (props.api) {
addLayer(props);
}
if(props.api) {
const result = await toast.promise(
@ -69,28 +61,18 @@ export const Layer = (props: LayerProps) => {
});
}
}
if (props.api) {
addLayer(props);
}
}
useEffect(() => {
resetItems(props);
loadItems();
}, [props.data, props.api])
return (
<>
{items &&
items.filter(item => item.layer?.name === props.name)?.map((place: Item) => {
const tags = getTags(place);
const tags = getItemTags(place);
let color1 = "#666";
let color2 = "RGBA(35, 31, 32, 0.2)";
if (tags[0]) {
@ -99,12 +81,8 @@ export const Layer = (props: LayerProps) => {
if (tags[1]) {
color2 = tags[1].color;
}
return (
<Marker icon={MarkerIconFactory(props.markerShape, color1, color2, props.markerIcon)} key={place.id} position={[place.position.coordinates[1], place.position.coordinates[0]]}>
{
(props.children && React.Children.toArray(props.children).some(child => React.isValidElement(child) && child.props.__TYPE === "ItemView") ?
React.Children.toArray(props.children).map((child) =>

View File

@ -6,6 +6,9 @@ import { useAddItem, useUpdateItem } from '../hooks/useItems'
import { Geometry, LayerProps, Item, ItemsApi } from '../../../types'
import { TextAreaInput } from '../../Input/TextAreaInput'
import { TextInput } from '../../Input/TextInput'
import { hashTagRegex } from '../../../Utils/HeighlightTags'
import { useAddTag } from '../hooks/useTags'
import { randomColor } from '../../../Utils/RandomColor'
export interface ItemFormPopupProps {
position: LatLng,
@ -18,15 +21,17 @@ export interface ItemFormPopupProps {
export function ItemFormPopup(props: ItemFormPopupProps) {
const formRef = useRef<HTMLFormElement>(null);
const [spinner, setSpinner] = useState(false);
const formRef = useRef<HTMLFormElement>(null);
const map = useMap();
const addItem = useAddItem();
const updateItem = useUpdateItem();
const addTag = useAddTag();
const handleSubmit = async (evt: any) => {
const formItem: Item = {} as Item;
Array.from(evt.target).forEach((input: HTMLInputElement) => {
@ -38,6 +43,11 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
evt.preventDefault();
setSpinner(true);
formItem.text.toLocaleLowerCase().match(hashTagRegex)?.map(tag=> {
addTag({id: tag.slice(1), color: randomColor()})
});
if (props.item) {
formItem['id'] = props.item.id;
await props.api?.updateItem!(formItem);
@ -66,8 +76,6 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
}
}
useEffect(() => {
resetPopup();
}, [props.position])
@ -78,7 +86,6 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
remove: () => {
setTimeout(function () {
resetPopup()
}, 100);
}
}}
@ -94,12 +101,12 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
React.Children.toArray(props.children).map((child) =>
React.isValidElement<{ item: Item, test: string }>(child) ?
React.cloneElement(child, { item: props.item }) : ""
React.cloneElement(child, { item: props.item, key: props.position.toString() }) : ""
)
:
<>
<TextAreaInput placeholder="Text" dataField="text" defaultValue={props.item ? props.item.text : ""} inputStyle='tw-h-40 tw-mt-5' />
<TextAreaInput key={props.position.toString()} placeholder="Text" dataField="text" defaultValue={props.item ? props.item.text : ""} inputStyle='tw-h-40 tw-mt-5' />
</>
}

View File

@ -1,16 +1,16 @@
import * as React from 'react'
import { useEffect } from 'react';
import { Tag } from '../../types';
import { useAddTag } from './hooks/useTags'
import { ItemsApi, Tag } from '../../types';
import { useAddTag, useSetTagData, useSetTagApi } from './hooks/useTags'
export function Tags({data, api} : {data?: Tag[], api?: ItemsApi<Tag>}) {
const setTagData = useSetTagData();
const setTagApi = useSetTagApi();
export function Tags({data} : {data: Tag[]}) {
const addTag = useAddTag();
useEffect(() => {
data.map(tag => {
tag.id = tag.id.toLocaleLowerCase();
addTag(tag)
})
}, [addTag, data])
data && setTagData(data);
api && setTagApi(api);
}, [api, data])
return (
<></>

View File

@ -1,6 +1,7 @@
import { useCallback, useReducer, createContext, useContext } from "react";
import * as React from "react";
import { Tag } from "../../../types";
import { ItemsApi, Tag } from "../../../types";
import { toast } from "react-toastify";
type ActionType =
| { type: "ADD"; tag: Tag }
@ -11,13 +12,17 @@ type UseTagManagerResult = ReturnType<typeof useTagsManager>;
const TagContext = createContext<UseTagManagerResult>({
tags: [],
addTag: () => { },
removeTag: () => { }
removeTag: () => { },
setTagApi: () => { },
setTagData: () => { }
});
function useTagsManager(initialTags: Tag[]): {
tags: Tag[];
addTag: (tag: Tag) => void;
removeTag: (id: string) => void;
setTagApi: (api: ItemsApi<Tag>) => void;
setTagData: (data: Tag[]) => void;
} {
const [tags, dispatch] = useReducer((state: Tag[], action: ActionType) => {
switch (action.type) {
@ -38,20 +43,47 @@ function useTagsManager(initialTags: Tag[]): {
}
}, initialTags);
const addTag = useCallback((tag: Tag) => {
const [api, setApi] = React.useState<ItemsApi<Tag> | undefined>(undefined)
const setTagApi = useCallback(async (api: ItemsApi<Tag>) => {
setApi(api);
const result = await api.getItems();
if (result) {
result.map(tag => {
tag.id = tag.id.toLocaleLowerCase();
dispatch({ type: "ADD", tag })
})
}
}, [])
const setTagData = useCallback((data: Tag[]) => {
data.map(tag => {
tag.id = tag.id.toLocaleLowerCase();
dispatch({ type: "ADD", tag })
})
}, []);
const addTag = (tag: Tag) => {
dispatch({
type: "ADD",
tag,
});
}, []);
if (!tags.find((t) => t.id === tag.id)) {
api?.createItem && api.createItem(tag);
}
};
const removeTag = useCallback((id: string) => {
dispatch({
type: "REMOVE",
id,
});
api?.deleteItem && api.deleteItem(id);
}, []);
return { tags, addTag, removeTag };
return { tags, addTag, removeTag, setTagApi, setTagData };
}
export const TagsProvider: React.FunctionComponent<{
@ -75,4 +107,14 @@ export const useAddTag = (): UseTagManagerResult["addTag"] => {
export const useRemoveTag = (): UseTagManagerResult["removeTag"] => {
const { removeTag } = useContext(TagContext);
return removeTag;
};
};
export const useSetTagApi = (): UseTagManagerResult["setTagApi"] => {
const { setTagApi } = useContext(TagContext);
return setTagApi;
}
export const useSetTagData = (): UseTagManagerResult["setTagData"] => {
const { setTagData } = useContext(TagContext);
return setTagData;
}

View File

@ -1,12 +1,10 @@
import { Tag } from "../types";
export const hashTagRegex = /(#+[a-zA-Z0-9A-Za-zÀ-ÖØ-öø-ʸ(_)]{1,})/g;
export function heighlightTags(message: string, tags: Tag[]): string {
if (!message) return "";
const hashTagRegex = /(^|\B)#(?![0-9_]+\b)([a-zA-Z0-9_]{1,30})(\b|\r)/g;
message = message.replace(hashTagRegex, function (string) {
const tag = tags.find(t => t.id.toLowerCase() == string.slice(1).toLowerCase())
return `<span style="background-color: ${tag ? tag.color : '#aaa' };padding: 0px 5px;border-radius: 7px;cursor: pointer;color:#fff">` + string + '</span>'

43
src/Utils/RandomColor.ts Normal file
View File

@ -0,0 +1,43 @@
/**
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 = () => {
return hsvToRgb((Math.random()+golden_ratio_conjugate)%1,0.8, 0.7)
}
const golden_ratio_conjugate = 0.618033988749895;
function hsvToRgb(h, s, v){
var r, g, b;
var i = (Math.floor(h * 6));
var f = h * 6 - i;
var p = v * (1 - s);
var q = v * (1 - f * s);
var t = v * (1 - (1 - f) * s);
switch(i % 6){
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
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 hex = x.toString(16)
return hex.length === 1 ? '0' + hex : hex
}).join('')

View File

@ -15,8 +15,7 @@
"noImplicitThis": true,
"strictNullChecks": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noUnusedParameters": true
},
"include": ["src"],
"exclude": ["node_modules", "dist", "example", "rollup.config.mjss"],