mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Tag API implementation
This commit is contained in:
parent
18bb8e6ad2
commit
b0e611c9c5
@ -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">
|
||||
|
||||
@ -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) =>
|
||||
|
||||
@ -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' />
|
||||
</>
|
||||
}
|
||||
|
||||
|
||||
@ -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 (
|
||||
<></>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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
43
src/Utils/RandomColor.ts
Normal 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('')
|
||||
@ -15,8 +15,7 @@
|
||||
"noImplicitThis": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": true,
|
||||
|
||||
"noUnusedParameters": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"exclude": ["node_modules", "dist", "example", "rollup.config.mjss"],
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user