mirror of
https://github.com/utopia-os/utopia-ui.git
synced 2025-12-13 07:46:10 +00:00
Merge main
This commit is contained in:
commit
0c60f9fc7f
@ -1,2 +1,3 @@
|
||||
node_modules/
|
||||
dist/
|
||||
examples/
|
||||
43
.eslintrc.js
43
.eslintrc.js
@ -12,7 +12,7 @@ module.exports = {
|
||||
'plugin:import/recommended',
|
||||
'plugin:import/typescript',
|
||||
// 'plugin:promise/recommended',
|
||||
// 'plugin:security/recommended-legacy',
|
||||
'plugin:security/recommended-legacy',
|
||||
'plugin:react/recommended',
|
||||
],
|
||||
parserOptions: {
|
||||
@ -23,9 +23,9 @@ module.exports = {
|
||||
plugins: [
|
||||
'@typescript-eslint',
|
||||
'import',
|
||||
// 'promise',
|
||||
// 'security',
|
||||
// 'no-catch-all',
|
||||
'promise',
|
||||
'security',
|
||||
'no-catch-all',
|
||||
'react',
|
||||
'react-hooks',
|
||||
],
|
||||
@ -44,7 +44,7 @@ module.exports = {
|
||||
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
|
||||
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
|
||||
'react/react-in-jsx-scope': 'off', // Disable requirement for React import
|
||||
// 'no-catch-all/no-catch-all': 'error',
|
||||
'no-catch-all/no-catch-all': 'error',
|
||||
'no-console': 'error',
|
||||
'no-debugger': 'error',
|
||||
camelcase: 'error',
|
||||
@ -79,7 +79,7 @@ module.exports = {
|
||||
'import/no-relative-parent-imports': [
|
||||
'error',
|
||||
{
|
||||
ignore: ['#[src,root,components,utils]/*'],
|
||||
ignore: ['#[src,types,root,components,utils]/*'],
|
||||
},
|
||||
],
|
||||
'import/no-self-import': 'error',
|
||||
@ -122,21 +122,21 @@ module.exports = {
|
||||
},
|
||||
],
|
||||
'import/prefer-default-export': 'off',
|
||||
// // promise
|
||||
// 'promise/catch-or-return': 'error',
|
||||
// 'promise/no-return-wrap': 'error',
|
||||
// 'promise/param-names': 'error',
|
||||
// 'promise/always-return': 'error',
|
||||
// 'promise/no-native': 'off',
|
||||
// 'promise/no-nesting': 'warn',
|
||||
// 'promise/no-promise-in-callback': 'warn',
|
||||
// 'promise/no-callback-in-promise': 'warn',
|
||||
// 'promise/avoid-new': 'warn',
|
||||
// 'promise/no-new-statics': 'error',
|
||||
// 'promise/no-return-in-finally': 'warn',
|
||||
// 'promise/valid-params': 'warn',
|
||||
// 'promise/prefer-await-to-callbacks': 'error',
|
||||
// 'promise/no-multiple-resolved': 'error',
|
||||
// promise
|
||||
'promise/catch-or-return': 'error',
|
||||
'promise/no-return-wrap': 'error',
|
||||
'promise/param-names': 'error',
|
||||
'promise/always-return': 'error',
|
||||
'promise/no-native': 'off',
|
||||
'promise/no-nesting': 'warn',
|
||||
'promise/no-promise-in-callback': 'warn',
|
||||
'promise/no-callback-in-promise': 'warn',
|
||||
'promise/avoid-new': 'warn',
|
||||
'promise/no-new-statics': 'error',
|
||||
'promise/no-return-in-finally': 'warn',
|
||||
'promise/valid-params': 'warn',
|
||||
'promise/prefer-await-to-callbacks': 'error',
|
||||
'promise/no-multiple-resolved': 'error',
|
||||
},
|
||||
overrides: [
|
||||
{
|
||||
@ -156,6 +156,7 @@ module.exports = {
|
||||
'plugin:@typescript-eslint/strict',
|
||||
],
|
||||
rules: {
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
// allow explicitly defined dangling promises
|
||||
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
|
||||
'no-void': ['error', { allowAsStatement: true }],
|
||||
|
||||
3
.github/workflows/test.lint.pr.yml
vendored
3
.github/workflows/test.lint.pr.yml
vendored
@ -33,6 +33,7 @@ jobs:
|
||||
docker
|
||||
release
|
||||
workflow
|
||||
source
|
||||
other
|
||||
# Configure that a scope must always be provided.
|
||||
requireScope: true
|
||||
@ -73,4 +74,4 @@ jobs:
|
||||
# special "[WIP]" prefix to indicate this state. This will avoid the
|
||||
# validation of the PR title and the pull request checks remain pending.
|
||||
# Note that a second check will be reported if this is enabled.
|
||||
wip: true
|
||||
wip: true
|
||||
|
||||
108
CODE_OF_CONDUCT.md
Normal file
108
CODE_OF_CONDUCT.md
Normal file
@ -0,0 +1,108 @@
|
||||
# Code of Conduct
|
||||
|
||||
## Purpose
|
||||
|
||||
Our community is dedicated to fostering an open, inclusive, and respectful environment. This Code of Conduct outlines our expectations for all participants and the steps for addressing any unacceptable behavior. By adhering to these guidelines, we can create a space where collaboration and learning thrive.
|
||||
|
||||
---
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Ensure that your contributions are relevant to the discussion and meeting agendas.
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Repeatedly deviating from the agenda during meetings, interrupting discussions, or monopolizing speaking time with irrelevant or incoherent contributions.
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
---
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines when determining consequences for violations of this Code of Conduct.
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact:** Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence:** A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact:** A violation through a single incident or series of actions.
|
||||
|
||||
**Consequence:** A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact:** A serious violation of community standards, including sustained inappropriate behavior.
|
||||
|
||||
**Consequence:** A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact:** Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence:** A permanent ban from any sort of public interaction within the community.
|
||||
|
||||
---
|
||||
|
||||
## Conflict Resolution Process
|
||||
|
||||
To address and resolve conflicts effectively, the community will follow these steps:
|
||||
|
||||
### Step 1: Direct Communication
|
||||
|
||||
If a conflict arises, the involved parties are encouraged to address the issue directly and respectfully with one another to seek resolution. This conversation should focus on the behavior or issue at hand, avoiding personal attacks or assumptions.
|
||||
|
||||
### Step 2: Mediation
|
||||
|
||||
If direct communication does not resolve the issue, either party may request mediation from a neutral community leader. The mediator will facilitate a structured conversation to help clarify misunderstandings and find common ground.
|
||||
|
||||
### Step 3: Formal Review
|
||||
|
||||
If the conflict remains unresolved, the issue will be escalated to the community leadership team for review. The leadership team will gather relevant information, including accounts from all parties involved, and make a decision based on the Code of Conduct and the community’s best interests.
|
||||
|
||||
### Step 4: Resolution and Follow-Up
|
||||
|
||||
The leadership team will communicate their decision and any actions to be taken to the involved parties. Follow-up will be conducted to ensure that the resolution is effective and that no further issues arise.
|
||||
|
||||
---
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies to all community spaces, including but not limited to:
|
||||
|
||||
- Online forums, repositories, and communication channels.
|
||||
- In-person or virtual meetings and events.
|
||||
- External channels where the community is represented (e.g., social media).
|
||||
|
||||
---
|
||||
|
||||
## Reporting
|
||||
|
||||
If you experience or witness behavior that violates this Code of Conduct, please report it to the community leaders via [info@utopia-lab.org](mailto:info@utopia-lab.org). All reports will be handled with discretion and confidentiality.
|
||||
|
||||
---
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
|
||||
|
||||
3
Components.svg
Normal file
3
Components.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 15 KiB |
115
README.md
115
README.md
@ -1,125 +1,36 @@
|
||||
# Utopia UI [](https://www.npmjs.com/package/utopia-ui)
|
||||
# Utopia UI [](https://www.npmjs.com/package/utopia-ui)  
|
||||
|
||||
**UI Framework for Real-Life-Networking-Apps**
|
||||
|
||||
*Real change happens in real life when we meet in person and connect as local communities manifesting their ideas with the earth. When we help each other to step out of capitalism and individualism and start building common infrastructure to meet human needs in harmony with Mother Earth.*
|
||||
*Real change happens in real life when we meet in person and connect as local communities manifesting their ideas with the earth. When we help each other to step out of our bubbles at home and start building common infrastructure to meet human needs in harmony with Mother Earth.*
|
||||
|
||||
*That is why Utopia UI exists. It is a UI kit for minimalist, fast, intuitive and mobile-first map apps, as a tool for local connection and decentralised networking. It can work with any backend or p2p database and any kind of data structure.*
|
||||
*That is why Utopia UI exists. It is a UI kit for minimalist, fast, intuitive and mobile-first map apps, as a tool for local connection and decentralised networking. We believe in maps as the perfect link between digital tools and real life action*
|
||||
|
||||
*It can work with any backend or p2p database and any kind of data structure.*
|
||||
|
||||
## Mission
|
||||
Utopia UIs mission is to provide open source building blocks to create beautiful applications with a focus on real life impact, local communities and gamification.
|
||||
|
||||
The building blocks are designed to allow different networks and communities to assemble their map and app for their specific needs and purpose.
|
||||
|
||||
Utopia Game is one of the apps made with Utopia UI. It is an attempt to use gamification to get users to take action and make the map even more alive. Check it out at [utopia-game.org](https://utopia-game.org/) or see the code in the [repository](https://github.com/utopia-os/utopia-game)
|
||||
It is the base of [Utopia Map](https://github.com/utopia-os/utopia-map) and [Utopia Game](https://github.com/utopia-os/utopia-game).
|
||||
|
||||
## Features
|
||||
|
||||
* Interactive Component Map with customizable Layers (like Projects, Event, People)
|
||||
* Flexible API-Interface to make it work with every backend or p2p database
|
||||
* Create, Update, Delete Items
|
||||
* User Authentification API-Interface
|
||||
* User Profiles
|
||||
* App Shell
|
||||
* User authentification API-Interface
|
||||
* Customizable Profiles for users and other items
|
||||
* App shell with navigation bar and sidebar
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Basic Map
|
||||
In this tutorial we learn how we create a basic React app with a Map component using [utopia-ui](https://github.com/utopia-os/utopia-ui) library.
|
||||
See our [`/exampes`](/examples)
|
||||
|
||||
For this tutorial we use Vite to create an empty React app called "utopia-static-map"
|
||||
## Components
|
||||
|
||||
```shell
|
||||
npm create vite@latest utopia-static-map -- --template react
|
||||
```
|
||||
|
||||
We open our new app in the terminal and install the [utopia-ui](https://github.com/utopia-os/utopia-ui) package
|
||||
|
||||
```shell
|
||||
cd utopia-static-map
|
||||
npm install utopia-ui
|
||||
```
|
||||
|
||||
We open our `src/App.jsx` and we replace the content with
|
||||
|
||||
```jsx
|
||||
import { UtopiaMap } from "utopia-ui"
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<UtopiaMap center={[50.6, 9.5]} zoom={5} height='100dvh' width="100dvw">
|
||||
</UtopiaMap>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
```
|
||||
|
||||
Then we start the development server to check out the result in our browser:
|
||||
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And can open our first map app in the browser 🙂
|
||||
|
||||
### Static Layers
|
||||
|
||||
Now we add some static layer.
|
||||
|
||||
First we put some sample data in a new file called `src/sample-data.js`
|
||||
|
||||
```javascript
|
||||
export const places = [{
|
||||
"id": 51,
|
||||
"name": "Stadtgemüse",
|
||||
"text": "Stadtgemüse Fulda ist eine Gemüsegärtnerei in Maberzell, die es sich zur Aufgabe gemacht hat, die Stadt und seine Bewohner:innen mit regionalem, frischem und natürlich angebautem Gemüse mittels Gemüsekisten zu versorgen. Es gibt also jede Woche, von Frühjahr bis Herbst, angepasst an die Saison eine Kiste mit schmackhaftem und frischem Gemüse für euch, welche ihr direkt vor Ort abholen könnt. \r\n\r\nhttps://stadtgemuese-fulda.de",
|
||||
"position": { "type": "Point", "coordinates": [9.632435, 50.560342] },
|
||||
},
|
||||
{
|
||||
"id": 166,
|
||||
"name": "Weidendom",
|
||||
"text": "free camping",
|
||||
"position": { "type": "Point", "coordinates": [9.438793, 50.560112] },
|
||||
}];
|
||||
|
||||
export const events = [
|
||||
{
|
||||
"id": 423,
|
||||
"name": "Hackathon",
|
||||
"text": "still in progress",
|
||||
"position": { "type": "Point", "coordinates": [10.5, 51.62] },
|
||||
"start": "2022-03-25T12:00:00",
|
||||
"end": "2022-05-12T12:00:00",
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
We want to create two Layers. One we want to call *Places* and the other *Events*
|
||||
|
||||
we import our sample data to the `src/App.jsx`
|
||||
|
||||
```jsx
|
||||
import { events, places } from "./sample-data"
|
||||
```
|
||||
and than we create our two `<Layer>` inside of our `<UtopiaMap>` component
|
||||
```jsx
|
||||
<UtopiaMap center={[50.6, 15.5]} zoom={5} height='100dvh' width="100dvw">
|
||||
<Layer
|
||||
name='events'
|
||||
markerIcon='calendar'
|
||||
markerShape='square'
|
||||
markerDefaultColor='#700'
|
||||
data={events} />
|
||||
<Layer
|
||||
name='places'
|
||||
markerIcon='point'
|
||||
markerShape='circle'
|
||||
markerDefaultColor='#007'
|
||||
data={places} />
|
||||
</UtopiaMap>
|
||||
|
||||
```
|
||||

|
||||
|
||||
## Map Component
|
||||
The map shows various Layers (like places, events, profiles ...) of Items at their respective position whith nice and informative Popup and Profiles.
|
||||
|
||||
24
examples/1-basic-map/.gitignore
vendored
Normal file
24
examples/1-basic-map/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1
examples/1-basic-map/.nvmrc
Normal file
1
examples/1-basic-map/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v18.19.1
|
||||
42
examples/1-basic-map/README.md
Normal file
42
examples/1-basic-map/README.md
Normal file
@ -0,0 +1,42 @@
|
||||
# Example 1: Basic Map
|
||||
|
||||
In this example we see how we create a basic React app with a Map component using [utopia-ui](https://github.com/utopia-os/utopia-ui) library.
|
||||
|
||||
For this example we use Vite to create an empty React app called "utopia-static-map"
|
||||
|
||||
```shell
|
||||
npm create vite@latest utopia-static-map -- --template react-ts
|
||||
```
|
||||
|
||||
We open our new app in the terminal and install the [utopia-ui](https://github.com/utopia-os/utopia-ui) package
|
||||
|
||||
```shell
|
||||
cd utopia-static-map
|
||||
npm install utopia-ui
|
||||
```
|
||||
|
||||
We open our `src/App.tsx` and we replace the content with
|
||||
|
||||
```tsx
|
||||
import { UtopiaMap } from "utopia-ui"
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<UtopiaMap center={[50.6, 9.5]} zoom={5} height='100dvh' width="100dvw">
|
||||
</UtopiaMap>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
|
||||
```
|
||||
|
||||
Then we start the development server to check out the result in our browser:
|
||||
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And can open our first map app in the browser 🙂
|
||||
|
||||
In [Tutorial 2](../2-static-layers/) we gonna add some static data to our map
|
||||
25
examples/1-basic-map/eslint.config.js
Normal file
25
examples/1-basic-map/eslint.config.js
Normal file
@ -0,0 +1,25 @@
|
||||
import js from '@eslint/js'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
},
|
||||
},
|
||||
)
|
||||
13
examples/1-basic-map/index.html
Normal file
13
examples/1-basic-map/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
5350
examples/1-basic-map/package-lock.json
generated
Normal file
5350
examples/1-basic-map/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
examples/1-basic-map/package.json
Normal file
30
examples/1-basic-map/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "tutorial-1-basic-map",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"utopia-ui": "^3.0.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
1
examples/1-basic-map/public/vite.svg
Normal file
1
examples/1-basic-map/public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
10
examples/1-basic-map/src/App.tsx
Normal file
10
examples/1-basic-map/src/App.tsx
Normal file
@ -0,0 +1,10 @@
|
||||
import { UtopiaMap } from "utopia-ui"
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<UtopiaMap center={[50.6, 9.5]} zoom={5} height='100dvh' width="100dvw">
|
||||
</UtopiaMap>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
9
examples/1-basic-map/src/main.tsx
Normal file
9
examples/1-basic-map/src/main.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
1
examples/1-basic-map/src/vite-env.d.ts
vendored
Normal file
1
examples/1-basic-map/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
26
examples/1-basic-map/tsconfig.app.json
Normal file
26
examples/1-basic-map/tsconfig.app.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
examples/1-basic-map/tsconfig.json
Normal file
7
examples/1-basic-map/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
examples/1-basic-map/tsconfig.node.json
Normal file
24
examples/1-basic-map/tsconfig.node.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
examples/1-basic-map/vite.config.ts
Normal file
7
examples/1-basic-map/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
24
examples/2-static-layers/.gitignore
vendored
Normal file
24
examples/2-static-layers/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
1
examples/2-static-layers/.nvmrc
Normal file
1
examples/2-static-layers/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v18.19.1
|
||||
62
examples/2-static-layers/README.md
Normal file
62
examples/2-static-layers/README.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Example 2: Static Layers
|
||||
|
||||
[Example 1](/1-basic-map) shows us how we create a basic map app with [utopia-ui](https://github.com/utopia-os/utopia-ui). Now we add some static layer.
|
||||
|
||||
First we put some sample data in a new file called `src/sample-data.ts`
|
||||
|
||||
```javascript
|
||||
export const places = [{
|
||||
"id": 51,
|
||||
"name": "Stadtgemüse",
|
||||
"text": "Stadtgemüse Fulda ist eine Gemüsegärtnerei in Maberzell, die es sich zur Aufgabe gemacht hat, die Stadt und seine Bewohner:innen mit regionalem, frischem und natürlich angebautem Gemüse mittels Gemüsekisten zu versorgen. Es gibt also jede Woche, von Frühjahr bis Herbst, angepasst an die Saison eine Kiste mit schmackhaftem und frischem Gemüse für euch, welche ihr direkt vor Ort abholen könnt. \r\n\r\nhttps://stadtgemuese-fulda.de",
|
||||
"position": { "type": "Point", "coordinates": [9.632435, 50.560342] },
|
||||
},
|
||||
{
|
||||
"id": 166,
|
||||
"name": "Weidendom",
|
||||
"text": "free camping",
|
||||
"position": { "type": "Point", "coordinates": [9.438793, 50.560112] },
|
||||
}];
|
||||
|
||||
export const events = [
|
||||
{
|
||||
"id": 423,
|
||||
"name": "Hackathon",
|
||||
"text": "still in progress",
|
||||
"position": { "type": "Point", "coordinates": [10.5, 51.62] },
|
||||
"start": "2022-03-25T12:00:00",
|
||||
"end": "2022-05-12T12:00:00",
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
We want to create two Layers. One we want to call *Places* and the other *Events*
|
||||
|
||||
we import our sample data to the `src/App.tsx`
|
||||
|
||||
```tsx
|
||||
import { events, places } from "./sample-data"
|
||||
```
|
||||
and than we create our two `<Layer>` inside of our `<UtopiaMap>` component
|
||||
```tsx
|
||||
<UtopiaMap center={[50.6, 15.5]} zoom={5} height='100dvh' width="100dvw">
|
||||
<Layer
|
||||
name='events'
|
||||
markerIcon='calendar'
|
||||
markerShape='square'
|
||||
markerDefaultColor='#700'
|
||||
data={events} />
|
||||
<Layer
|
||||
name='places'
|
||||
markerIcon='point'
|
||||
markerShape='circle'
|
||||
markerDefaultColor='#007'
|
||||
data={places} />
|
||||
</UtopiaMap>
|
||||
```
|
||||
|
||||
And we see our map with two layers:
|
||||
|
||||
```shell
|
||||
npm run dev
|
||||
```
|
||||
25
examples/2-static-layers/eslint.config.js
Normal file
25
examples/2-static-layers/eslint.config.js
Normal file
@ -0,0 +1,25 @@
|
||||
import js from '@eslint/js'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
||||
},
|
||||
},
|
||||
)
|
||||
13
examples/2-static-layers/index.html
Normal file
13
examples/2-static-layers/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite + React + TS</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
5350
examples/2-static-layers/package-lock.json
generated
Normal file
5350
examples/2-static-layers/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
examples/2-static-layers/package.json
Normal file
30
examples/2-static-layers/package.json
Normal file
@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "tutorial-2-static-layer",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"utopia-ui": "^3.0.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.17.0",
|
||||
"@types/react": "^18.3.18",
|
||||
"@types/react-dom": "^18.3.5",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.16",
|
||||
"globals": "^15.14.0",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.18.2",
|
||||
"vite": "^6.0.5"
|
||||
}
|
||||
}
|
||||
1
examples/2-static-layers/public/vite.svg
Normal file
1
examples/2-static-layers/public/vite.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
23
examples/2-static-layers/src/App.tsx
Normal file
23
examples/2-static-layers/src/App.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import { UtopiaMap, Layer } from "utopia-ui"
|
||||
import { events, places } from "./sample-data"
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<UtopiaMap center={[50.6, 15.5]} zoom={5} height='100dvh' width="100dvw">
|
||||
<Layer
|
||||
name='events'
|
||||
markerIcon='calendar'
|
||||
markerShape='square'
|
||||
markerDefaultColor='#700'
|
||||
data={events} />
|
||||
<Layer
|
||||
name='places'
|
||||
markerIcon='point'
|
||||
markerShape='circle'
|
||||
markerDefaultColor='#007'
|
||||
data={places} />
|
||||
</UtopiaMap>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
9
examples/2-static-layers/src/main.tsx
Normal file
9
examples/2-static-layers/src/main.tsx
Normal file
@ -0,0 +1,9 @@
|
||||
import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
)
|
||||
23
examples/2-static-layers/src/sample-data.ts
Normal file
23
examples/2-static-layers/src/sample-data.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export const places = [{
|
||||
"id": 51,
|
||||
"name": "Stadtgemüse",
|
||||
"text": "Stadtgemüse Fulda ist eine Gemüsegärtnerei in Maberzell, die es sich zur Aufgabe gemacht hat, die Stadt und seine Bewohner:innen mit regionalem, frischem und natürlich angebautem Gemüse mittels Gemüsekisten zu versorgen. Es gibt also jede Woche, von Frühjahr bis Herbst, angepasst an die Saison eine Kiste mit schmackhaftem und frischem Gemüse für euch, welche ihr direkt vor Ort abholen könnt. \r\n\r\nhttps://stadtgemuese-fulda.de",
|
||||
"position": { "type": "Point", "coordinates": [9.632435, 50.560342] },
|
||||
},
|
||||
{
|
||||
"id": 166,
|
||||
"name": "Weidendom",
|
||||
"text": "free camping",
|
||||
"position": { "type": "Point", "coordinates": [9.438793, 50.560112] },
|
||||
}];
|
||||
|
||||
export const events = [
|
||||
{
|
||||
"id": 423,
|
||||
"name": "Hackathon",
|
||||
"text": "still in progress",
|
||||
"position": { "type": "Point", "coordinates": [10.5, 51.62] },
|
||||
"start": "2022-03-25T12:00:00",
|
||||
"end": "2022-05-12T12:00:00",
|
||||
}
|
||||
]
|
||||
1
examples/2-static-layers/src/vite-env.d.ts
vendored
Normal file
1
examples/2-static-layers/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
26
examples/2-static-layers/tsconfig.app.json
Normal file
26
examples/2-static-layers/tsconfig.app.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
examples/2-static-layers/tsconfig.json
Normal file
7
examples/2-static-layers/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
examples/2-static-layers/tsconfig.node.json
Normal file
24
examples/2-static-layers/tsconfig.node.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
7
examples/2-static-layers/vite.config.ts
Normal file
7
examples/2-static-layers/vite.config.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
130
package-lock.json
generated
130
package-lock.json
generated
@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "utopia-ui",
|
||||
"version": "3.0.19",
|
||||
"version": "3.0.34",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "utopia-ui",
|
||||
"version": "3.0.19",
|
||||
"version": "3.0.34",
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.17",
|
||||
@ -27,7 +27,8 @@
|
||||
"react-toastify": "^9.1.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"tributejs": "^5.1.3",
|
||||
"tw-elements": "^1.0.0"
|
||||
"tw-elements": "^1.0.0",
|
||||
"utopia-ui": "^3.0.35"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
|
||||
@ -45,9 +46,12 @@
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-security": "^3.0.1",
|
||||
"eslint-plugin-yml": "^1.14.0",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^3.3.3",
|
||||
@ -1351,9 +1355,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.3",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
|
||||
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
@ -2371,6 +2375,16 @@
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-no-catch-all": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-no-catch-all/-/eslint-plugin-no-catch-all-1.1.0.tgz",
|
||||
"integrity": "sha512-VkP62jLTmccPrFGN/W6V7a3SEwdtTZm+Su2k4T3uyJirtkm0OMMm97h7qd8pRFAHus/jQg9FpUpLRc7sAylBEQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"eslint": ">=2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-prettier": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz",
|
||||
@ -2408,7 +2422,6 @@
|
||||
"integrity": "sha512-57Zzfw8G6+Gq7axm2Pdo3gW/Rx3h9Yywgn61uE/3elTCOePEHVrn2i5CdfBwA1BLK0Q0WqctICIUSqXZW/VprQ==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
|
||||
},
|
||||
@ -2488,6 +2501,22 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-security": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-3.0.1.tgz",
|
||||
"integrity": "sha512-XjVGBhtDZJfyuhIxnQ/WMm385RbX3DBu7H1J7HNNhmB2tnGxMeqVSnYv79oAj992ayvIBZghsymwkYFS6cGH4Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"safe-regex": "^2.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/eslint"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-yml": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-yml/-/eslint-plugin-yml-1.14.0.tgz",
|
||||
@ -4759,9 +4788,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.8",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
|
||||
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@ -6015,6 +6044,23 @@
|
||||
"react": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/react-photo-album": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-photo-album/-/react-photo-album-3.0.2.tgz",
|
||||
"integrity": "sha512-w3+8i6aj9l1jRfcubgVbAlBGSdtiXcqWdcwZcH4/Bavc+v7X7h+S3TkQ723pvDABjhaaxS168g9ECEBP6xnKrQ==",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": ">=18",
|
||||
"react": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "6.16.0",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.16.0.tgz",
|
||||
@ -6092,6 +6138,16 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/regexp-tree": {
|
||||
"version": "0.1.27",
|
||||
"resolved": "https://registry.npmjs.org/regexp-tree/-/regexp-tree-0.1.27.tgz",
|
||||
"integrity": "sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"regexp-tree": "bin/regexp-tree"
|
||||
}
|
||||
},
|
||||
"node_modules/regexp.prototype.flags": {
|
||||
"version": "1.5.3",
|
||||
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
|
||||
@ -6405,6 +6461,16 @@
|
||||
"integrity": "sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/safe-regex": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-2.1.1.tgz",
|
||||
"integrity": "sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"regexp-tree": "~0.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/safe-regex-test": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.3.tgz",
|
||||
@ -7304,6 +7370,38 @@
|
||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
|
||||
},
|
||||
"node_modules/utopia-ui": {
|
||||
"version": "3.0.35",
|
||||
"resolved": "https://registry.npmjs.org/utopia-ui/-/utopia-ui-3.0.35.tgz",
|
||||
"integrity": "sha512-PtXvwskYuS4ro/gRWoNkKvn/lC0vW6m9ipwnXb7a4u/95wGWDXajhTh70MbAujTszxSH2iBRY6ZTsSFuREHCJQ==",
|
||||
"dependencies": {
|
||||
"@heroicons/react": "^2.0.17",
|
||||
"@tanstack/react-query": "^5.17.8",
|
||||
"@types/offscreencanvas": "^2019.7.1",
|
||||
"axios": "^1.6.5",
|
||||
"date-fns": "^3.3.1",
|
||||
"leaflet": "^1.9.4",
|
||||
"leaflet.locatecontrol": "^0.79.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-colorful": "^5.6.1",
|
||||
"react-image-crop": "^10.1.8",
|
||||
"react-leaflet": "^4.2.1",
|
||||
"react-leaflet-cluster": "^2.1.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-photo-album": "^3.0.2",
|
||||
"react-router-dom": "^6.16.0",
|
||||
"react-string-replace": "^1.1.1",
|
||||
"react-toastify": "^9.1.3",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"tributejs": "^5.1.3",
|
||||
"tw-elements": "^1.0.0",
|
||||
"yet-another-react-lightbox": "^3.21.7"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vfile": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.1.tgz",
|
||||
@ -7483,6 +7581,18 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"node_modules/yet-another-react-lightbox": {
|
||||
"version": "3.21.7",
|
||||
"resolved": "https://registry.npmjs.org/yet-another-react-lightbox/-/yet-another-react-lightbox-3.21.7.tgz",
|
||||
"integrity": "sha512-dcdokNuCIl92f0Vl+uzeKULnQhztIGpoZFUMvtVNUPmtwsQWpqWufeieDPeg9JtFyVCcbj4vYw3V00DS0QNoWA==",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "utopia-ui",
|
||||
"version": "3.0.19",
|
||||
"version": "3.0.34",
|
||||
"description": "Reuseable React Components to build mapping apps for real life communities and networks",
|
||||
"repository": "https://github.com/utopia-os/utopia-ui",
|
||||
"homepage:": "https://utopia-os.org/",
|
||||
@ -33,9 +33,12 @@
|
||||
"eslint-import-resolver-typescript": "^3.6.3",
|
||||
"eslint-plugin-import": "^2.31.0",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-no-catch-all": "^1.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"eslint-plugin-react": "^7.31.8",
|
||||
"eslint-plugin-react-hooks": "^4.6.0",
|
||||
"eslint-plugin-security": "^3.0.1",
|
||||
"eslint-plugin-yml": "^1.14.0",
|
||||
"postcss": "^8.4.21",
|
||||
"prettier": "^3.3.3",
|
||||
@ -76,6 +79,7 @@
|
||||
"#components/*": "./src/Components/*",
|
||||
"#utils/*": "./src/Utils/*",
|
||||
"#src/*": "./src/*",
|
||||
"#types/*": "./types/*",
|
||||
"#root/*": "./*"
|
||||
}
|
||||
}
|
||||
|
||||
@ -21,6 +21,9 @@ export default {
|
||||
external: [
|
||||
'react',
|
||||
'react-dom',
|
||||
'react-markdown',
|
||||
'react/jsx-runtime',
|
||||
'remark-breaks',
|
||||
'leaflet',
|
||||
'react-leaflet',
|
||||
'react-toastify',
|
||||
@ -34,5 +37,16 @@ export default {
|
||||
'prop-types',
|
||||
'leaflet/dist/leaflet.css',
|
||||
'@heroicons/react/20/solid',
|
||||
'@heroicons/react/24/outline/ChevronRightIcon',
|
||||
'@heroicons/react/24/outline',
|
||||
'date-fns',
|
||||
'@heroicons/react/24/outline/InformationCircleIcon',
|
||||
'@heroicons/react/24/outline/QuestionMarkCircleIcon',
|
||||
'@heroicons/react/24/outline/ChevronDownIcon',
|
||||
'axios',
|
||||
'react-image-crop',
|
||||
'react-image-crop/dist/ReactCrop.css',
|
||||
'react-colorful',
|
||||
'leaflet.locatecontrol/dist/L.Control.Locate.css',
|
||||
],
|
||||
}
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { AssetsApi } from '#src/types'
|
||||
|
||||
import { ContextWrapper } from './ContextWrapper'
|
||||
import NavBar from './NavBar'
|
||||
import { SetAppState } from './SetAppState'
|
||||
|
||||
import type { AssetsApi } from '#types/AssetsApi'
|
||||
|
||||
export function AppShell({
|
||||
appName,
|
||||
children,
|
||||
|
||||
@ -28,6 +28,7 @@ export const ContextWrapper = ({ children }) => {
|
||||
try {
|
||||
// eslint-disable-next-line react-hooks/rules-of-hooks
|
||||
location = useLocation()
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (e) {
|
||||
location = null
|
||||
}
|
||||
|
||||
@ -1,14 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
import QuestionMarkIcon from '@heroicons/react/24/outline/QuestionMarkCircleIcon'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { Link, useLocation } from 'react-router-dom'
|
||||
@ -16,7 +5,8 @@ import { toast } from 'react-toastify'
|
||||
|
||||
import { useAuth } from '#components/Auth'
|
||||
import { useItems } from '#components/Map/hooks/useItems'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export default function NavBar({ appName, userType }: { appName: string; userType: string }) {
|
||||
const { isAuthenticated, user, logout } = useAuth()
|
||||
@ -30,19 +20,19 @@ export default function NavBar({ appName, userType }: { appName: string; userTyp
|
||||
items.find((i) => i.user_created?.id === user.id && i.layer?.itemType.name === userType)
|
||||
profile
|
||||
? setUserProfile(profile)
|
||||
: setUserProfile({ id: crypto.randomUUID(), name: user?.first_name, text: '' })
|
||||
: setUserProfile({ id: crypto.randomUUID(), name: user?.first_name ?? '', text: '' })
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [user, items])
|
||||
|
||||
useEffect(() => {}, [userProfile])
|
||||
// useEffect(() => {}, [userProfile])
|
||||
|
||||
const nameRef = useRef<any>(null)
|
||||
const nameRef = useRef<HTMLHeadingElement>(null)
|
||||
const [nameWidth, setNameWidth] = useState<number>(0)
|
||||
const location = useLocation()
|
||||
const [showNav, setShowNav] = useState<boolean>(false)
|
||||
|
||||
useEffect(() => {
|
||||
showNav && nameRef && setNameWidth(nameRef.current.scrollWidth)
|
||||
showNav && nameRef.current && setNameWidth(nameRef.current.scrollWidth)
|
||||
}, [nameRef, appName, showNav])
|
||||
|
||||
useEffect(() => {
|
||||
@ -51,8 +41,8 @@ export default function NavBar({ appName, userType }: { appName: string; userTyp
|
||||
embedded !== 'true' && setShowNav(true)
|
||||
}, [location])
|
||||
|
||||
const onLogout = () => {
|
||||
toast.promise(logout(), {
|
||||
const onLogout = async () => {
|
||||
await toast.promise(logout(), {
|
||||
success: {
|
||||
render() {
|
||||
return 'Bye bye'
|
||||
@ -62,7 +52,7 @@ export default function NavBar({ appName, userType }: { appName: string; userTyp
|
||||
},
|
||||
error: {
|
||||
render({ data }) {
|
||||
return `${data}`
|
||||
return JSON.stringify(data)
|
||||
},
|
||||
},
|
||||
pending: 'logging out ..',
|
||||
@ -122,7 +112,7 @@ export default function NavBar({ appName, userType }: { appName: string; userTyp
|
||||
to={`${userProfile.id && '/item/' + userProfile.id}`}
|
||||
className='tw-flex tw-items-center'
|
||||
>
|
||||
{userProfile?.image && (
|
||||
{userProfile.image && (
|
||||
<div className='tw-avatar'>
|
||||
<div className='tw-w-10 tw-rounded-full'>
|
||||
<img src={'https://api.utopia-lab.org/assets/' + userProfile.image} />
|
||||
@ -155,7 +145,7 @@ export default function NavBar({ appName, userType }: { appName: string; userTyp
|
||||
<li>
|
||||
<a
|
||||
onClick={() => {
|
||||
onLogout()
|
||||
void onLogout()
|
||||
}}
|
||||
>
|
||||
Logout
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { AssetsApi } from '#src/types'
|
||||
|
||||
import { useSetAppState } from './hooks/useAppState'
|
||||
|
||||
import type { AssetsApi } from '#types/AssetsApi'
|
||||
|
||||
export const SetAppState = ({
|
||||
assetsApi,
|
||||
userType,
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useCallback, useState, createContext, useContext } from 'react'
|
||||
|
||||
import { AssetsApi } from '#src/types'
|
||||
import type { AssetsApi } from '#types/AssetsApi'
|
||||
|
||||
interface AppState {
|
||||
assetsApi: AssetsApi
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useCallback, useState, createContext, useContext } from 'react'
|
||||
|
||||
import { AssetsApi } from '#src/types'
|
||||
import type { AssetsApi } from '#types/AssetsApi'
|
||||
|
||||
type UseAssetManagerResult = ReturnType<typeof useAssetsManager>
|
||||
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
@ -7,7 +5,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
import { createContext, useState, useContext, useEffect } from 'react'
|
||||
|
||||
import { UserApi, UserItem } from '#src/types'
|
||||
import type { UserApi } from '#types/UserApi'
|
||||
import type { UserItem } from '#types/UserItem'
|
||||
|
||||
interface AuthProviderProps {
|
||||
userApi: UserApi
|
||||
@ -69,6 +68,7 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
||||
setLoading(false)
|
||||
return me
|
||||
} else return undefined
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
setLoading(false)
|
||||
return undefined
|
||||
@ -79,7 +79,7 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => {
|
||||
setLoading(true)
|
||||
try {
|
||||
const res = await userApi.login(credentials.email, credentials.password)
|
||||
setToken(res.access_token)
|
||||
setToken(res?.access_token)
|
||||
return await loadUser()
|
||||
} catch (error: any) {
|
||||
setLoading(false)
|
||||
|
||||
@ -1,5 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { useEffect } from 'react'
|
||||
|
||||
export function Modal({
|
||||
|
||||
@ -1,12 +1,12 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { useAuth } from '#components/Auth'
|
||||
import { useItems } from '#components/Map/hooks/useItems'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import { useQuestsOpen, useSetQuestOpen } from './hooks/useQuests'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export function Quests() {
|
||||
const questsOpen = useQuestsOpen()
|
||||
const setQuestsOpen = useSetQuestOpen()
|
||||
|
||||
@ -70,6 +70,7 @@ export const Autocomplete = ({
|
||||
break
|
||||
case 'Enter':
|
||||
if (filteredSuggestions.length > 0) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
onSelected(filteredSuggestions[heighlightedSuggestion])
|
||||
setHeighlightedSuggestion(0)
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ type TextAreaProps = {
|
||||
inputStyle?: string
|
||||
defaultValue: string
|
||||
placeholder?: string
|
||||
required?: boolean
|
||||
updateFormValue?: (value: string) => void
|
||||
}
|
||||
|
||||
@ -30,6 +31,7 @@ export function TextAreaInput({
|
||||
inputStyle,
|
||||
defaultValue,
|
||||
placeholder,
|
||||
required = true,
|
||||
updateFormValue,
|
||||
}: TextAreaProps) {
|
||||
const ref = useRef<HTMLTextAreaElement>(null)
|
||||
@ -90,7 +92,7 @@ export function TextAreaInput({
|
||||
</label>
|
||||
) : null}
|
||||
<textarea
|
||||
required
|
||||
required={required}
|
||||
ref={ref}
|
||||
value={inputValue}
|
||||
name={dataField}
|
||||
|
||||
@ -13,6 +13,8 @@ type InputTextProps = {
|
||||
defaultValue?: string
|
||||
placeholder?: string
|
||||
autocomplete?: string
|
||||
pattern?: string
|
||||
required?: boolean
|
||||
updateFormValue?: (value: string) => void
|
||||
}
|
||||
|
||||
@ -26,6 +28,8 @@ export function TextInput({
|
||||
defaultValue,
|
||||
placeholder,
|
||||
autocomplete,
|
||||
pattern,
|
||||
required = true,
|
||||
updateFormValue,
|
||||
}: InputTextProps) {
|
||||
const [inputValue, setInputValue] = useState<string>(defaultValue || '')
|
||||
@ -50,7 +54,8 @@ export function TextInput({
|
||||
</label>
|
||||
) : null}
|
||||
<input
|
||||
required
|
||||
required={required}
|
||||
pattern={pattern}
|
||||
type={type || 'text'}
|
||||
name={dataField}
|
||||
value={inputValue}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { node, string } from 'prop-types'
|
||||
import { Children, cloneElement, isValidElement, useEffect } from 'react'
|
||||
|
||||
import { Item } from '#src/types'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const ItemForm = ({
|
||||
children,
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { node, string } from 'prop-types'
|
||||
import { Children, cloneElement, isValidElement } from 'react'
|
||||
|
||||
import { Item } from '#src/types'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const ItemView = ({ children, item }: { children?: React.ReactNode; item?: Item }) => {
|
||||
return (
|
||||
|
||||
@ -7,12 +7,10 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { Popup } from 'leaflet'
|
||||
import { Children, isValidElement, useEffect, useState } from 'react'
|
||||
import { Marker, Tooltip, useMap, useMapEvents } from 'react-leaflet'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { Item, LayerProps, Tag } from '#src/types'
|
||||
import { encodeTag } from '#utils/FormatTags'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||
@ -32,6 +30,11 @@ import { useAddTag, useAllTagsLoaded, useGetItemTags, useTags } from './hooks/us
|
||||
import { ItemFormPopup } from './Subcomponents/ItemFormPopup'
|
||||
import { ItemViewPopup } from './Subcomponents/ItemViewPopup'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
import type { Tag } from '#types/Tag'
|
||||
import type { Popup } from 'leaflet'
|
||||
|
||||
export const Layer = ({
|
||||
data,
|
||||
children,
|
||||
@ -196,6 +199,7 @@ export const Layer = ({
|
||||
} else {
|
||||
if (window.location.pathname.split('/')[1]) {
|
||||
const id = window.location.pathname.split('/')[1]
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const ref = leafletRefs[id]
|
||||
if (ref?.marker && ref.item.layer?.name === name) {
|
||||
ref.marker &&
|
||||
@ -261,20 +265,27 @@ export const Layer = ({
|
||||
)
|
||||
.map((item: Item) => {
|
||||
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
if (getValue(item, itemTextField)) item[itemTextField] = getValue(item, itemTextField)
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
else item[itemTextField] = ''
|
||||
|
||||
if (item.tags) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
item[itemTextField] = item[itemTextField] + '\n\n'
|
||||
item.tags.map((tag) => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
return (item[itemTextField] = item[itemTextField] + `#${encodeTag(tag)} `)
|
||||
}
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
return item[itemTextField]
|
||||
})
|
||||
}
|
||||
|
||||
if (allTagsLoaded && allItemsLoaded) {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
item[itemTextField].match(hashTagRegex)?.map((tag) => {
|
||||
if (
|
||||
!tags.find(
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useAuth } from '#components/Auth'
|
||||
import { ItemsApi, Permission } from '#src/types'
|
||||
|
||||
import { useSetPermissionData, useSetPermissionApi, useSetAdminRole } from './hooks/usePermissions'
|
||||
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
import type { Permission } from '#types/Permission'
|
||||
|
||||
export function Permissions({
|
||||
data,
|
||||
api,
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { useLayers } from '#components/Map/hooks/useLayers'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
|
||||
|
||||
@ -22,7 +22,6 @@ import { useItems } from '#components/Map/hooks/useItems'
|
||||
import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
|
||||
import { useTags } from '#components/Map/hooks/useTags'
|
||||
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
||||
import { Item } from '#src/types'
|
||||
import { decodeTag } from '#utils/FormatTags'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
import MarkerIconFactory from '#utils/MarkerIconFactory'
|
||||
@ -30,6 +29,8 @@ import MarkerIconFactory from '#utils/MarkerIconFactory'
|
||||
import { LocateControl } from './LocateControl'
|
||||
import { SidebarControl } from './SidebarControl'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const SearchControl = () => {
|
||||
const windowDimensions = useWindowDimensions()
|
||||
const [popupOpen, setPopupOpen] = useState(false)
|
||||
@ -63,6 +64,7 @@ export const SearchControl = () => {
|
||||
try {
|
||||
const { data } = await axios.get(`https://photon.komoot.io/api/?q=${value}&limit=5`)
|
||||
setGeoResults(data.features)
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
@ -331,6 +333,7 @@ export const SearchControl = () => {
|
||||
|
||||
function isGeoCoordinate(input) {
|
||||
const geokoordinatenRegex =
|
||||
// eslint-disable-next-line security/detect-unsafe-regex
|
||||
/^[-+]?([1-8]?\d(\.\d+)?|90(\.0+)?),\s*[-+]?(180(\.0+)?|((1[0-7]\d)|([1-9]?\d))(\.\d+)?)$/
|
||||
return geokoordinatenRegex.test(input)
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react'
|
||||
@ -17,10 +16,12 @@ import { TextInput } from '#components/Input/TextInput'
|
||||
import { useResetFilterTags } from '#components/Map/hooks/useFilter'
|
||||
import { useAddItem, useItems, useRemoveItem, useUpdateItem } from '#components/Map/hooks/useItems'
|
||||
import { useAddTag, useTags } from '#components/Map/hooks/useTags'
|
||||
import { Geometry, Item, ItemFormPopupProps } from '#src/types'
|
||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||
import { randomColor } from '#utils/RandomColor'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
|
||||
|
||||
export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
const [spinner, setSpinner] = useState(false)
|
||||
|
||||
@ -50,7 +51,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
formItem[input.name] = input.value
|
||||
}
|
||||
})
|
||||
formItem.position = new Geometry(props.position.lng, props.position.lat)
|
||||
formItem.position = { type: 'Point', coordinates: [props.position.lng, props.position.lat] }
|
||||
evt.preventDefault()
|
||||
setSpinner(true)
|
||||
|
||||
@ -70,6 +71,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
try {
|
||||
await props.layer.api?.updateItem!({ ...formItem, id: props.item.id })
|
||||
success = true
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
@ -101,6 +103,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
name: formItem.name ? formItem.name : user?.first_name,
|
||||
}))
|
||||
success = true
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
@ -109,8 +112,8 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
||||
if (!props.layer.onlyOnePerOwner || !item) {
|
||||
addItem({
|
||||
...formItem,
|
||||
name: formItem.name ? formItem.name : user?.first_name,
|
||||
user_created: user,
|
||||
name: (formItem.name ? formItem.name : user?.first_name) ?? '',
|
||||
user_created: user ?? undefined,
|
||||
type: props.layer.itemType,
|
||||
id: uuid,
|
||||
layer: props.layer,
|
||||
|
||||
@ -15,9 +15,11 @@ import { useNavigate } from 'react-router-dom'
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
import { Item, ItemsApi } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
|
||||
export function HeaderView({
|
||||
item,
|
||||
api,
|
||||
@ -103,6 +105,7 @@ export function HeaderView({
|
||||
<div className={`${avatar ? 'tw-ml-2' : ''} tw-overflow-hidden`}>
|
||||
<div
|
||||
className={`${big ? 'xl:tw-text-3xl tw-text-2xl' : 'tw-text-xl'} tw-font-semibold tw-truncate`}
|
||||
title={title}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
|
||||
@ -3,9 +3,10 @@
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
||||
import { Item } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const PopupButton = ({
|
||||
url,
|
||||
parameterField,
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { Item } from '#src/types'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const PopupCheckboxInput = ({
|
||||
dataField,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
import { TextInput } from '#components/Input'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface StartEndInputProps {
|
||||
item?: Item
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { TextAreaInput } from '#components/Input'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const PopupTextAreaInput = ({
|
||||
dataField,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { TextInput } from '#components/Input'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const PopupTextInput = ({
|
||||
dataField,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
import { Item } from '#src/types'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const StartEndView = ({ item }: { item?: Item }) => {
|
||||
return (
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
/* eslint-disable no-console */
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
@ -13,12 +11,14 @@ import remarkBreaks from 'remark-breaks'
|
||||
|
||||
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
|
||||
import { useTags } from '#components/Map/hooks/useTags'
|
||||
import { Item } from '#src/types'
|
||||
import { decodeTag } from '#utils/FormatTags'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||
import { fixUrls, mailRegex } from '#utils/ReplaceURLs'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
export const TextView = ({
|
||||
item,
|
||||
truncate = false,
|
||||
@ -76,47 +76,54 @@ export const TextView = ({
|
||||
})
|
||||
}
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomH1 = ({ children }) => <h1 className='tw-text-xl tw-font-bold'>{children}</h1>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomH2 = ({ children }) => <h2 className='tw-text-lg tw-font-bold'>{children}</h2>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomH3 = ({ children }) => <h3 className='tw-text-base tw-font-bold'>{children}</h3>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomH4 = ({ children }) => <h4 className='tw-text-base tw-font-bold'>{children}</h4>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomH5 = ({ children }) => <h5 className='tw-text-sm tw-font-bold'>{children}</h5>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomH6 = ({ children }) => <h6 className='tw-text-sm tw-font-bold'>{children}</h6>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomParagraph = ({ children }) => <p className='!tw-my-2'>{children}</p>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomUnorderdList = ({ children }) => (
|
||||
<ul className='tw-list-disc tw-list-inside'>{children}</ul>
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomOrderdList = ({ children }) => (
|
||||
<ol className='tw-list-decimal tw-list-inside'>{children}</ol>
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomHorizontalRow = ({ children }) => <hr className='tw-border-current'>{children}</hr>
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const CustomImage = ({ alt, src, title }) => (
|
||||
<img className='tw-max-w-full tw-rounded tw-shadow' src={src} alt={alt} title={title} />
|
||||
)
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const CustomExternalLink = ({ href, children }) => (
|
||||
<a className='tw-font-bold tw-underline' href={href} target='_blank' rel='noreferrer'>
|
||||
{' '}
|
||||
{children}
|
||||
</a>
|
||||
)
|
||||
/* eslint-disable react/prop-types */
|
||||
const CustomHashTagLink = ({ children, tag, item }) => {
|
||||
|
||||
const CustomHashTagLink = ({
|
||||
children,
|
||||
tag,
|
||||
item,
|
||||
}: {
|
||||
children: string
|
||||
tag: Tag
|
||||
item?: Item
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
style={{ color: tag ? tag.color : '#faa', fontWeight: 'bold', cursor: 'pointer' }}
|
||||
key={tag ? tag.name + item.id : item.id}
|
||||
key={tag ? tag.name + item?.id : item?.id}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
addFilterTag(tag)
|
||||
@ -126,7 +133,6 @@ export const TextView = ({
|
||||
</a>
|
||||
)
|
||||
}
|
||||
/* eslint-enable react/prop-types */
|
||||
|
||||
// eslint-disable-next-line react/display-name
|
||||
const MemoizedVideoEmbed = memo(({ url }: { url: string }) => (
|
||||
@ -144,32 +150,34 @@ export const TextView = ({
|
||||
remarkPlugins={[remarkBreaks]}
|
||||
components={{
|
||||
p: CustomParagraph,
|
||||
a: ({ href, children }) => {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
a: ({ href, children }: { href: string; children: string }) => {
|
||||
const isYouTubeVideo = href?.startsWith('https://www.youtube.com/watch?v=')
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
const isRumbleVideo = href?.startsWith('https://rumble.com/embed/')
|
||||
|
||||
if (isYouTubeVideo) {
|
||||
// eslint-disable-next-line react/prop-types
|
||||
const videoId = href?.split('v=')[1].split('&')[0]
|
||||
const youtubeEmbedUrl = `https://www.youtube-nocookie.com/embed/${videoId}`
|
||||
|
||||
return <MemoizedVideoEmbed url={youtubeEmbedUrl}></MemoizedVideoEmbed>
|
||||
}
|
||||
if (isRumbleVideo) {
|
||||
return <MemoizedVideoEmbed url={href!}></MemoizedVideoEmbed>
|
||||
return <MemoizedVideoEmbed url={href}></MemoizedVideoEmbed>
|
||||
}
|
||||
// eslint-disable-next-line react/prop-types
|
||||
|
||||
if (href?.startsWith('#')) {
|
||||
console.log(href.slice(1).toLowerCase())
|
||||
console.log(tags)
|
||||
const tag = tags.find(
|
||||
(t) => t.name.toLowerCase() === decodeURI(href).slice(1).toLowerCase(),
|
||||
)
|
||||
return (
|
||||
<CustomHashTagLink tag={tag} item={item}>
|
||||
{children}
|
||||
</CustomHashTagLink>
|
||||
)
|
||||
if (tag)
|
||||
return (
|
||||
<CustomHashTagLink tag={tag} item={item}>
|
||||
{children}
|
||||
</CustomHashTagLink>
|
||||
)
|
||||
else return children
|
||||
} else {
|
||||
return <CustomExternalLink href={href}>{children}</CustomExternalLink>
|
||||
}
|
||||
|
||||
@ -15,12 +15,14 @@ import { toast } from 'react-toastify'
|
||||
|
||||
import { useRemoveItem, useUpdateItem } from '#components/Map/hooks/useItems'
|
||||
import { useSetSelectPosition } from '#components/Map/hooks/useSelectPosition'
|
||||
import { Item, ItemFormPopupProps } from '#src/types'
|
||||
import { timeAgo } from '#utils/TimeAgo'
|
||||
|
||||
import { HeaderView } from './ItemPopupComponents/HeaderView'
|
||||
import { TextView } from './ItemPopupComponents/TextView'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
|
||||
|
||||
export interface ItemViewPopupProps {
|
||||
item: Item
|
||||
children?: React.ReactNode
|
||||
@ -63,6 +65,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
|
||||
props.item.layer?.onlyOnePerOwner &&
|
||||
(await props.item.layer.api?.updateItem!({ id: props.item.id, position: null }))
|
||||
success = true
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
import { useEffect } from 'react'
|
||||
import { useLocation } from 'react-router-dom'
|
||||
|
||||
import { ItemsApi, Tag } from '#src/types'
|
||||
|
||||
import { useAddFilterTag, useFilterTags, useResetFilterTags } from './hooks/useFilter'
|
||||
import { useSetTagData, useSetTagApi, useTags } from './hooks/useTags'
|
||||
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
export function Tags({ data, api }: { data?: Tag[]; api?: ItemsApi<Tag> }) {
|
||||
const setTagData = useSetTagData()
|
||||
const setTagApi = useSetTagApi()
|
||||
|
||||
@ -1,7 +1,9 @@
|
||||
import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
||||
import { UtopiaMapProps } from '#src/types'
|
||||
|
||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||
|
||||
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import 'react-toastify/dist/ReactToastify.css'
|
||||
|
||||
|
||||
@ -23,8 +23,6 @@ import MarkerClusterGroup from 'react-leaflet-cluster'
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { ItemFormPopupProps, UtopiaMapProps } from '#src/types'
|
||||
|
||||
// eslint-disable-next-line import/no-unassigned-import
|
||||
import './UtopiaMap.css'
|
||||
|
||||
@ -46,6 +44,8 @@ import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
||||
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
||||
import { SelectPosition } from './Subcomponents/SelectPosition'
|
||||
|
||||
import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
|
||||
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
||||
import type { Feature, Geometry as GeoJSONGeometry } from 'geojson'
|
||||
|
||||
const mapDivRef = createRef()
|
||||
|
||||
@ -7,11 +7,12 @@
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||
import { useNavigate } from 'react-router-dom'
|
||||
|
||||
import { LayerProps, Tag } from '#src/types'
|
||||
|
||||
import { useLayers } from './useLayers'
|
||||
import useWindowDimensions from './useWindowDimension'
|
||||
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
type ActionType =
|
||||
| { type: 'ADD_TAG'; tag: Tag }
|
||||
| { type: 'REMOVE_TAG'; name: string }
|
||||
|
||||
@ -3,17 +3,16 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { Item, LayerProps } from '#src/types'
|
||||
|
||||
import { useAddLayer } from './useLayers'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
|
||||
type ActionType =
|
||||
| { type: 'ADD'; item: Item }
|
||||
| { type: 'UPDATE'; item: Item }
|
||||
@ -82,13 +81,11 @@ function useItemsManager(initialItems: Item[]): {
|
||||
},
|
||||
},
|
||||
})
|
||||
if (result) {
|
||||
result.map((item) => {
|
||||
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||
return null
|
||||
})
|
||||
setallItemsLoaded(true)
|
||||
}
|
||||
result.map((item) => {
|
||||
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||
return null
|
||||
})
|
||||
setallItemsLoaded(true)
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [])
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { useCallback, useReducer, createContext, useContext } from 'react'
|
||||
|
||||
import { LayerProps } from '#src/types'
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
|
||||
interface ActionType {
|
||||
type: 'ADD LAYER'
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
import { Marker, Popup } from 'leaflet'
|
||||
import { useCallback, useReducer, createContext, useContext } from 'react'
|
||||
|
||||
import { Item } from '#src/types'
|
||||
import type { Item } from '#types/Item'
|
||||
import type { Marker, Popup } from 'leaflet'
|
||||
|
||||
interface LeafletRef {
|
||||
item: Item
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
@ -10,7 +9,12 @@
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||
|
||||
import { useAuth } from '#components/Auth/useAuth'
|
||||
import { Item, ItemsApi, LayerProps, Permission, PermissionAction } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
import type { Permission } from '#types/Permission'
|
||||
import type { PermissionAction } from '#types/PermissionAction'
|
||||
|
||||
type ActionType = { type: 'ADD'; permission: Permission } | { type: 'REMOVE'; id: string }
|
||||
|
||||
@ -57,12 +61,10 @@ function usePermissionsManager(initialPermissions: Permission[]): {
|
||||
|
||||
const setPermissionApi = useCallback(async (api: ItemsApi<Permission>) => {
|
||||
const result = await api.getItems()
|
||||
if (result) {
|
||||
result.map((permission) => {
|
||||
dispatch({ type: 'ADD', permission })
|
||||
return null
|
||||
})
|
||||
}
|
||||
result.map((permission) => {
|
||||
dispatch({ type: 'ADD', permission })
|
||||
return null
|
||||
})
|
||||
}, [])
|
||||
|
||||
const setPermissionData = useCallback((data: Permission[]) => {
|
||||
|
||||
@ -8,15 +8,18 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import { LatLng } from 'leaflet'
|
||||
import { createContext, useContext, useEffect, useState } from 'react'
|
||||
import { toast } from 'react-toastify'
|
||||
|
||||
import { Geometry, Item, LayerProps, ItemFormPopupProps } from '#src/types'
|
||||
|
||||
import { useUpdateItem } from './useItems'
|
||||
import { useHasUserPermission } from './usePermissions'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
import type { Point } from 'geojson'
|
||||
import type { LatLng } from 'leaflet'
|
||||
|
||||
interface PolygonClickedProps {
|
||||
position: LatLng
|
||||
setItemFormPopup: React.Dispatch<React.SetStateAction<ItemFormPopupProps | null>>
|
||||
@ -67,7 +70,11 @@ function useSelectPositionManager(): {
|
||||
}
|
||||
if ('text' in selectPosition) {
|
||||
const position =
|
||||
mapClicked?.position.lng && new Geometry(mapClicked.position.lng, mapClicked.position.lat)
|
||||
mapClicked?.position.lng &&
|
||||
({
|
||||
type: 'Point',
|
||||
coordinates: [mapClicked.position.lng, mapClicked.position.lat],
|
||||
} as Point)
|
||||
position && itemUpdatePosition({ ...selectPosition, position })
|
||||
setSelectPosition(null)
|
||||
}
|
||||
@ -89,6 +96,7 @@ function useSelectPositionManager(): {
|
||||
position: null,
|
||||
})
|
||||
success = true
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
@ -113,6 +121,7 @@ function useSelectPositionManager(): {
|
||||
position: updatedItem.position,
|
||||
})
|
||||
success = true
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
@ -134,6 +143,7 @@ function useSelectPositionManager(): {
|
||||
try {
|
||||
await markerClicked.layer?.api?.updateItem!(updatedItem)
|
||||
success = true
|
||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||
} catch (error) {
|
||||
toast.error(error.toString())
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
@ -11,10 +10,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||
|
||||
import { Item, ItemsApi, Tag } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
type ActionType = { type: 'ADD'; tag: Tag } | { type: 'REMOVE'; id: string }
|
||||
|
||||
type UseTagManagerResult = ReturnType<typeof useTagsManager>
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
@ -15,7 +14,6 @@ import { useLayers } from '#components/Map/hooks/useLayers'
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
|
||||
import { MapOverlayPage } from '#components/Templates'
|
||||
import { Item, Tag } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
|
||||
@ -25,6 +23,9 @@ import { OnepagerForm } from './Templates/OnepagerForm'
|
||||
import { SimpleForm } from './Templates/SimpleForm'
|
||||
import { TabsForm } from './Templates/TabsForm'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
export function ProfileForm() {
|
||||
const [state, setState] = useState({
|
||||
color: '',
|
||||
@ -78,7 +79,7 @@ export function ProfileForm() {
|
||||
const layer = layers.find((l) => l.itemType.name === appState.userType)
|
||||
setItem({
|
||||
id: crypto.randomUUID(),
|
||||
name: user ? user.first_name : '',
|
||||
name: user?.first_name ?? '',
|
||||
text: '',
|
||||
layer,
|
||||
new: true,
|
||||
@ -148,58 +149,64 @@ export function ProfileForm() {
|
||||
backdrop
|
||||
className='tw-mx-4 tw-mt-4 tw-mb-4 tw-overflow-x-hidden tw-w-[calc(100%-32px)] md:tw-w-[calc(50%-32px)] tw-max-w-3xl !tw-left-auto tw-top-0 tw-bottom-0'
|
||||
>
|
||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||
<FormHeader item={item} state={state} setState={setState} />
|
||||
<form
|
||||
className='tw-h-full'
|
||||
onSubmit={(e) => {
|
||||
e.preventDefault()
|
||||
void onUpdateItem(
|
||||
state,
|
||||
item,
|
||||
tags,
|
||||
addTag,
|
||||
setLoading,
|
||||
navigate,
|
||||
updateItem,
|
||||
addItem,
|
||||
user,
|
||||
urlParams,
|
||||
)
|
||||
}}
|
||||
>
|
||||
<div className='tw-flex tw-flex-col tw-h-full'>
|
||||
<FormHeader item={item} state={state} setState={setState} />
|
||||
|
||||
{template === 'onepager' && (
|
||||
<OnepagerForm item={item} state={state} setState={setState}></OnepagerForm>
|
||||
)}
|
||||
{template === 'onepager' && (
|
||||
<OnepagerForm item={item} state={state} setState={setState}></OnepagerForm>
|
||||
)}
|
||||
|
||||
{template === 'simple' && <SimpleForm state={state} setState={setState}></SimpleForm>}
|
||||
{template === 'simple' && <SimpleForm state={state} setState={setState}></SimpleForm>}
|
||||
|
||||
{template === 'flex' && (
|
||||
<FlexForm item={item} state={state} setState={setState}></FlexForm>
|
||||
)}
|
||||
{template === 'flex' && (
|
||||
<FlexForm item={item} state={state} setState={setState}></FlexForm>
|
||||
)}
|
||||
|
||||
{template === 'tabs' && (
|
||||
<TabsForm
|
||||
loading={loading}
|
||||
item={item}
|
||||
state={state}
|
||||
setState={setState}
|
||||
updatePermission={updatePermission}
|
||||
linkItem={(id) => linkItem(id, item, updateItem)}
|
||||
unlinkItem={(id) => unlinkItem(id, item, updateItem)}
|
||||
setUrlParams={setUrlParams}
|
||||
></TabsForm>
|
||||
)}
|
||||
{template === 'tabs' && (
|
||||
<TabsForm
|
||||
loading={loading}
|
||||
item={item}
|
||||
state={state}
|
||||
setState={setState}
|
||||
updatePermission={updatePermission}
|
||||
linkItem={(id) => linkItem(id, item, updateItem)}
|
||||
unlinkItem={(id) => unlinkItem(id, item, updateItem)}
|
||||
setUrlParams={setUrlParams}
|
||||
></TabsForm>
|
||||
)}
|
||||
|
||||
<div className='tw-mt-4'>
|
||||
<button
|
||||
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
|
||||
onClick={() =>
|
||||
onUpdateItem(
|
||||
state,
|
||||
item,
|
||||
tags,
|
||||
addTag,
|
||||
setLoading,
|
||||
navigate,
|
||||
updateItem,
|
||||
addItem,
|
||||
user,
|
||||
urlParams,
|
||||
)
|
||||
}
|
||||
style={{
|
||||
backgroundColor: `${item.layer?.itemColorField && getValue(item, item.layer?.itemColorField) ? getValue(item, item.layer?.itemColorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor}`,
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
<div className='tw-mt-4'>
|
||||
<button
|
||||
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
|
||||
type='submit'
|
||||
style={{
|
||||
backgroundColor: `${item.layer?.itemColorField && getValue(item, item.layer?.itemColorField) ? getValue(item, item.layer?.itemColorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor}`,
|
||||
color: '#fff',
|
||||
}}
|
||||
>
|
||||
Update
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</MapOverlayPage>
|
||||
</>
|
||||
)
|
||||
|
||||
@ -21,7 +21,6 @@ import { useSelectPosition, useSetSelectPosition } from '#components/Map/hooks/u
|
||||
import { useTags } from '#components/Map/hooks/useTags'
|
||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
import { MapOverlayPage } from '#components/Templates'
|
||||
import { Item, ItemsApi, Tag } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
|
||||
@ -30,6 +29,10 @@ import { OnepagerView } from './Templates/OnepagerView'
|
||||
import { SimpleView } from './Templates/SimpleView'
|
||||
import { TabsView } from './Templates/TabsView'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
import type { ItemsApi } from '#types/ItemsApi'
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any> }) {
|
||||
const [item, setItem] = useState<Item>()
|
||||
const [updatePermission, setUpdatePermission] = useState<boolean>(false)
|
||||
@ -64,7 +67,9 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
||||
console.log(value)
|
||||
|
||||
setAttestations(value)
|
||||
return null
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||
.catch((error) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Error fetching items:', error)
|
||||
|
||||
@ -10,9 +10,10 @@ import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||
import DialogModal from '#components/Templates/DialogModal'
|
||||
import { Item } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export function ActionButton({
|
||||
item,
|
||||
triggerAddButton,
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||
import { useState, useCallback, useRef } from 'react'
|
||||
import { ReactCrop, centerCrop, makeAspectCrop } from 'react-image-crop'
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { TextInput } from '#components/Input'
|
||||
import { FormState } from '#src/types'
|
||||
|
||||
import type { FormState } from '#types/FormState'
|
||||
|
||||
export const ContactInfoForm = ({
|
||||
state,
|
||||
@ -22,6 +23,7 @@ export const ContactInfoForm = ({
|
||||
<TextInput
|
||||
placeholder='Email'
|
||||
type='email'
|
||||
required={false}
|
||||
defaultValue={state.contact}
|
||||
updateFormValue={(v) =>
|
||||
setState((prevState) => ({
|
||||
@ -41,6 +43,9 @@ export const ContactInfoForm = ({
|
||||
</label>
|
||||
<TextInput
|
||||
placeholder='Telefonnummer'
|
||||
type='tel'
|
||||
required={false}
|
||||
pattern='^\+?[0-9\s\-]{7,15}$'
|
||||
defaultValue={state.telephone}
|
||||
updateFormValue={(v) =>
|
||||
setState((prevState) => ({
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||
import { useEffect, useState } from 'react'
|
||||
@ -8,7 +7,8 @@ import { Link } from 'react-router-dom'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import { useItems } from '#components/Map/hooks/useItems'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const ContactInfoView = ({ item, heading }: { item: Item; heading: string }) => {
|
||||
const appState = useAppState()
|
||||
|
||||
@ -44,6 +44,7 @@ export const FormHeader = ({ item, state, setState }) => {
|
||||
/>
|
||||
<TextInput
|
||||
placeholder='Subtitle'
|
||||
required={false}
|
||||
defaultValue={item?.subname ? item.subname : ''}
|
||||
updateFormValue={(v) =>
|
||||
setState((prevState) => ({
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import SocialShareBar from './SocialShareBar'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const GroupSubHeaderView = ({
|
||||
item,
|
||||
shareBaseUrl,
|
||||
|
||||
@ -4,7 +4,9 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import ComboBoxInput from '#components/Input/ComboBoxInput'
|
||||
import { Item, FormState } from '#src/types'
|
||||
|
||||
import type { FormState } from '#types/FormState'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
interface groupType {
|
||||
groupTypes_id: {
|
||||
@ -30,13 +32,16 @@ export const GroupSubheaderForm = ({
|
||||
useEffect(() => {
|
||||
if (groupTypes && groupStates) {
|
||||
const groupType = groupTypes.find((gt) => gt.groupTypes_id.name === state.group_type)
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(state.group_type)
|
||||
const customImage = !groupTypes.some(
|
||||
(gt) => gt.groupTypes_id.image === state.image || !state.image,
|
||||
)
|
||||
setState((prevState) => ({
|
||||
...prevState,
|
||||
color: groupType?.groupTypes_id.color || groupTypes[0].groupTypes_id.color,
|
||||
marker_icon: groupType?.groupTypes_id.markerIcon || groupTypes[0].groupTypes_id.markerIcon,
|
||||
image: groupType?.groupTypes_id.image || groupTypes[0].groupTypes_id.image,
|
||||
image: customImage
|
||||
? state.image
|
||||
: groupType?.groupTypes_id.image || groupTypes[0].groupTypes_id.image,
|
||||
status: state.status || groupStates[0],
|
||||
group_type: state.group_type || groupTypes[0].groupTypes_id.name,
|
||||
}))
|
||||
|
||||
@ -8,9 +8,10 @@
|
||||
import { useEffect } from 'react'
|
||||
|
||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||
import { Item } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export function LinkedItemsHeaderView({
|
||||
item,
|
||||
unlinkCallback,
|
||||
|
||||
34
src/Components/Profile/Subcomponents/MarkdownHint.tsx
Normal file
34
src/Components/Profile/Subcomponents/MarkdownHint.tsx
Normal file
@ -0,0 +1,34 @@
|
||||
import { useState } from 'react'
|
||||
|
||||
export const MarkdownHint = () => {
|
||||
const [expended, setExpended] = useState<boolean>(false)
|
||||
return (
|
||||
<div
|
||||
onClick={() => setExpended(true)}
|
||||
title='Markdown is supported'
|
||||
className='flex tw-flex-row tw-text-gray-400 tw-cursor-pointer tw-items-center'
|
||||
>
|
||||
<svg
|
||||
aria-hidden='true'
|
||||
height='16'
|
||||
viewBox='0 0 16 16'
|
||||
version='1.1'
|
||||
width='16'
|
||||
data-view-component='true'
|
||||
className='octicon octicon-markdown'
|
||||
fill='rgb(156 163 175 / var(--tw-text-opacity))'
|
||||
>
|
||||
<path d='M14.85 3c.63 0 1.15.52 1.14 1.15v7.7c0 .63-.51 1.15-1.15 1.15H1.15C.52 13 0 12.48 0 11.84V4.15C0 3.52.52 3 1.15 3ZM9 11V5H7L5.5 7 4 5H2v6h2V8l1.5 1.92L7 8v3Zm2.99.5L14.5 8H13V5h-2v3H9.5Z'></path>
|
||||
</svg>
|
||||
{expended && (
|
||||
<a
|
||||
href='https://www.markdownguide.org/cheat-sheet/#basic-syntax'
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
<span className='Button-label tw-ml-1'>Markdown is support</span>{' '}
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -1,7 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||
import { LayerProps } from '#src/types'
|
||||
|
||||
import type { LayerProps } from '#types/LayerProps'
|
||||
|
||||
export function PlusButton({
|
||||
layer,
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||
import { PopupStartEndInput } from '#components/Map'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const ProfileStartEndForm = ({
|
||||
item,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { StartEndView } from '#components/Map'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const ProfileStartEndView = ({ item }: { item: Item }) => {
|
||||
return (
|
||||
|
||||
@ -5,9 +5,12 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
|
||||
import { TextAreaInput } from '#components/Input'
|
||||
import { FormState } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import { MarkdownHint } from './MarkdownHint'
|
||||
|
||||
import type { FormState } from '#types/FormState'
|
||||
|
||||
export const ProfileTextForm = ({
|
||||
state,
|
||||
setState,
|
||||
@ -15,6 +18,7 @@ export const ProfileTextForm = ({
|
||||
heading,
|
||||
size,
|
||||
hideInputLabel,
|
||||
required,
|
||||
}: {
|
||||
state: FormState
|
||||
setState: React.Dispatch<React.SetStateAction<any>>
|
||||
@ -22,6 +26,7 @@ export const ProfileTextForm = ({
|
||||
heading: string
|
||||
size: string
|
||||
hideInputLabel: boolean
|
||||
required?: boolean
|
||||
}) => {
|
||||
const [field, setField] = useState<string>(dataField || 'text')
|
||||
|
||||
@ -33,12 +38,15 @@ export const ProfileTextForm = ({
|
||||
|
||||
return (
|
||||
<div className='tw-h-full tw-flex tw-flex-col tw-mt-4'>
|
||||
<label
|
||||
htmlFor='nextAppointment'
|
||||
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||
>
|
||||
{heading || 'Text'}:
|
||||
</label>
|
||||
<div className='tw-flex tw-justify-between tw-items-center'>
|
||||
<label
|
||||
htmlFor='nextAppointment'
|
||||
className='tw-block tw-text-sm tw-font-medium tw-text-gray-500 tw-mb-1'
|
||||
>
|
||||
{heading || 'Text'}:
|
||||
</label>
|
||||
<MarkdownHint />
|
||||
</div>
|
||||
<TextAreaInput
|
||||
placeholder={'...'}
|
||||
defaultValue={getValue(state, field)}
|
||||
@ -51,6 +59,7 @@ export const ProfileTextForm = ({
|
||||
labelStyle={hideInputLabel ? 'tw-hidden' : ''}
|
||||
containerStyle={size === 'full' ? 'tw-grow tw-h-full' : ''}
|
||||
inputStyle={size === 'full' ? 'tw-h-full' : 'tw-h-24'}
|
||||
required={required}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||
import { TextView } from '#components/Map'
|
||||
import { Item } from '#src/types'
|
||||
import { getValue } from '#utils/GetValue'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const ProfileTextView = ({
|
||||
item,
|
||||
dataField,
|
||||
|
||||
@ -16,7 +16,9 @@ const SocialShareBar = ({
|
||||
.writeText(url)
|
||||
.then(() => {
|
||||
toast.success('link copied to clipboard')
|
||||
return null
|
||||
})
|
||||
// eslint-disable-next-line promise/prefer-await-to-callbacks
|
||||
.catch((error: never) => {
|
||||
toast.error('Fehler beim Kopieren des Links: ', error)
|
||||
})
|
||||
|
||||
@ -84,6 +84,7 @@ const SocialShareButton = ({
|
||||
url: string
|
||||
title: string
|
||||
}) => {
|
||||
// eslint-disable-next-line security/detect-object-injection
|
||||
const config = platformConfigs[platform]
|
||||
|
||||
if (!config) {
|
||||
|
||||
@ -8,10 +8,11 @@ import { useEffect, useState } from 'react'
|
||||
|
||||
import { Autocomplete } from '#components/Input/Autocomplete'
|
||||
import { useTags } from '#components/Map/hooks/useTags'
|
||||
import { Tag } from '#src/types'
|
||||
import { decodeTag, encodeTag } from '#utils/FormatTags'
|
||||
import { randomColor } from '#utils/RandomColor'
|
||||
|
||||
import type { Tag } from '#types/Tag'
|
||||
|
||||
// eslint-disable-next-line react/prop-types
|
||||
export const TagsWidget = ({ placeholder, containerStyle, defaultTags, onUpdate }) => {
|
||||
const [input, setInput] = useState('')
|
||||
|
||||
@ -7,7 +7,9 @@ import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoFo
|
||||
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'
|
||||
import { ProfileStartEndForm } from '#components/Profile/Subcomponents/ProfileStartEndForm'
|
||||
import { ProfileTextForm } from '#components/Profile/Subcomponents/ProfileTextForm'
|
||||
import { Item, FormState } from '#src/types'
|
||||
|
||||
import type { FormState } from '#types/FormState'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
const componentMap = {
|
||||
groupSubheaders: GroupSubheaderForm,
|
||||
|
||||
@ -6,7 +6,8 @@ import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoVi
|
||||
import { GroupSubHeaderView } from '#components/Profile/Subcomponents/GroupSubHeaderView'
|
||||
import { ProfileStartEndView } from '#components/Profile/Subcomponents/ProfileStartEndView'
|
||||
import { ProfileTextView } from '#components/Profile/Subcomponents/ProfileTextView'
|
||||
import { Item } from '#src/types'
|
||||
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
const componentMap = {
|
||||
groupSubheaders: GroupSubHeaderView,
|
||||
|
||||
@ -3,7 +3,9 @@
|
||||
import { TextAreaInput } from '#components/Input'
|
||||
import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoForm'
|
||||
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'
|
||||
import { FormState, Item } from '#src/types'
|
||||
|
||||
import type { FormState } from '#types/FormState'
|
||||
import type { Item } from '#types/Item'
|
||||
|
||||
export const OnepagerForm = ({
|
||||
item,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user