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
a3d7fa8496
@ -1,3 +1,5 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
examples/
|
examples/
|
||||||
|
docs/
|
||||||
|
coverage/
|
||||||
@ -109,7 +109,12 @@ module.exports = {
|
|||||||
'import/no-duplicates': 'error',
|
'import/no-duplicates': 'error',
|
||||||
'import/no-named-default': 'error',
|
'import/no-named-default': 'error',
|
||||||
'import/no-namespace': 'error',
|
'import/no-namespace': 'error',
|
||||||
'import/no-unassigned-import': 'error',
|
'import/no-unassigned-import': [
|
||||||
|
'error',
|
||||||
|
{
|
||||||
|
allow: ['**/*.css'],
|
||||||
|
},
|
||||||
|
],
|
||||||
'import/order': [
|
'import/order': [
|
||||||
'error',
|
'error',
|
||||||
{
|
{
|
||||||
56
.github/workflows/deploy.docs.yml
vendored
Normal file
56
.github/workflows/deploy.docs.yml
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
name: deploy:docs
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
# Specify runner + build & upload the static files as an artifact
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
with:
|
||||||
|
node-version-file: './.tool-versions'
|
||||||
|
- name: Install Dependencies & Build Library
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm link
|
||||||
|
working-directory: ./
|
||||||
|
|
||||||
|
- name: Build static files
|
||||||
|
id: build
|
||||||
|
run: npm install && npm run docs:generate
|
||||||
|
working-directory: ./
|
||||||
|
|
||||||
|
- name: Upload static files as artifact
|
||||||
|
id: deployment
|
||||||
|
uses: actions/upload-pages-artifact@v3.0.1
|
||||||
|
with:
|
||||||
|
path: docs/
|
||||||
|
|
||||||
|
# Deploy job
|
||||||
|
deploy:
|
||||||
|
# Add a dependency to the build job
|
||||||
|
needs: build
|
||||||
|
|
||||||
|
# Grant GITHUB_TOKEN the permissions required to make a Pages deployment
|
||||||
|
permissions:
|
||||||
|
pages: write # to deploy to Pages
|
||||||
|
id-token: write # to verify the deployment originates from an appropriate source
|
||||||
|
|
||||||
|
# Deploy to the github-pages environment
|
||||||
|
environment:
|
||||||
|
name: github-pages
|
||||||
|
url: ${{ steps.deployment.outputs.page_url }}
|
||||||
|
|
||||||
|
# Specify runner + deployment step
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Deploy to GitHub Pages
|
||||||
|
id: deployment
|
||||||
|
uses: actions/deploy-pages@v4.0.5
|
||||||
32
.github/workflows/test.build.yml
vendored
32
.github/workflows/test.build.yml
vendored
@ -28,6 +28,34 @@ jobs:
|
|||||||
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
with:
|
with:
|
||||||
node-version-file: './.tool-versions'
|
node-version-file: './.tool-versions'
|
||||||
- name: Build
|
- name: Install Dependencies & Build Library
|
||||||
run: npm install && npm run build
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
npm link
|
||||||
working-directory: ./
|
working-directory: ./
|
||||||
|
|
||||||
|
build-examples:
|
||||||
|
if: needs.files-changed.outputs.build == 'true'
|
||||||
|
name: Test Example Apps
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
app: [examples/1-basic-map, examples/2-static-layers] # Aktualisierte Pfade der Beispiel-Apps
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout Repository
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Set Up Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: 18
|
||||||
|
|
||||||
|
- name: Link Utopia-UI in Example App
|
||||||
|
run: |
|
||||||
|
cd ${{ matrix.app }}
|
||||||
|
npm install
|
||||||
|
npm link utopia-ui
|
||||||
|
npm run build
|
||||||
|
|||||||
52
.github/workflows/test.docs.yml
vendored
Normal file
52
.github/workflows/test.docs.yml
vendored
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
name: test:docs
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
files-changed:
|
||||||
|
name: Detect File Changes - docs
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
docs: ${{ steps.filter.outputs.docs }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
docs:
|
||||||
|
- '.github/workflows/**/*'
|
||||||
|
- '**/*'
|
||||||
|
|
||||||
|
# build:
|
||||||
|
# if: needs.files-changed.outputs.frontend == 'true'
|
||||||
|
# name: Build - Frontend
|
||||||
|
# needs: files-changed
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
# - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
# with:
|
||||||
|
# node-version-file: './.tool-versions'
|
||||||
|
# - name: Frontend | Build
|
||||||
|
# run: npm install && npm run build
|
||||||
|
# working-directory: ./frontend
|
||||||
|
|
||||||
|
docs:
|
||||||
|
if: needs.files-changed.outputs.docs == 'true'
|
||||||
|
name: Docs
|
||||||
|
needs: files-changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
env:
|
||||||
|
COVERAGE_REQUIRED: 0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
with:
|
||||||
|
node-version-file: './.tool-versions'
|
||||||
|
- name: Docs
|
||||||
|
run: |
|
||||||
|
npm install
|
||||||
|
npm run docs:generate
|
||||||
|
./scripts/docs-coverage.sh
|
||||||
|
working-directory: ./
|
||||||
75
.github/workflows/test.lint.examples.yml
vendored
Normal file
75
.github/workflows/test.lint.examples.yml
vendored
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
name: test::examples
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
files-changed:
|
||||||
|
name: Detect File Changes - lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
lint: ${{ steps.filter.outputs.lint }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
lint:
|
||||||
|
- '.github/workflows/**/*'
|
||||||
|
- '**/*'
|
||||||
|
|
||||||
|
# build:
|
||||||
|
# if: needs.files-changed.outputs.frontend == 'true'
|
||||||
|
# name: Build - Frontend
|
||||||
|
# needs: files-changed
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
# - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
# with:
|
||||||
|
# node-version-file: './.tool-versions'
|
||||||
|
# - name: Frontend | Build
|
||||||
|
# run: npm install && npm run build
|
||||||
|
# working-directory: ./frontend
|
||||||
|
|
||||||
|
lint-example-1-basic-map:
|
||||||
|
if: needs.files-changed.outputs.lint == 'true'
|
||||||
|
name: Lint Example 1 - Basic Map
|
||||||
|
needs: files-changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
with:
|
||||||
|
node-version-file: './.tool-versions'
|
||||||
|
- name: Lint
|
||||||
|
run: npm install && npm run lint
|
||||||
|
working-directory: ./examples/1-basic-map
|
||||||
|
|
||||||
|
lint-example-2-static-layers:
|
||||||
|
if: needs.files-changed.outputs.lint == 'true'
|
||||||
|
name: Lint Example 2 - Static Layers
|
||||||
|
needs: files-changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
with:
|
||||||
|
node-version-file: './.tool-versions'
|
||||||
|
- name: Lint
|
||||||
|
run: npm install && npm run lint
|
||||||
|
working-directory: ./examples/2-static-layers
|
||||||
|
|
||||||
|
# unit:
|
||||||
|
# if: needs.files-changed.outputs.frontend == 'true'
|
||||||
|
# name: Unit - Frontend
|
||||||
|
# needs: files-changed
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
# - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
# with:
|
||||||
|
# node-version-file: './.tool-versions'
|
||||||
|
# - name: Frontend | Unit
|
||||||
|
# run: npm install && npm run test:unit
|
||||||
|
# working-directory: ./frontend
|
||||||
33
.github/workflows/test.unit.yml
vendored
Normal file
33
.github/workflows/test.unit.yml
vendored
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
name: test:unit
|
||||||
|
|
||||||
|
on: push
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
files-changed:
|
||||||
|
name: Detect File Changes - unit
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
outputs:
|
||||||
|
unit: ${{ steps.filter.outputs.unit }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||||
|
id: filter
|
||||||
|
with:
|
||||||
|
filters: |
|
||||||
|
unit:
|
||||||
|
- '.github/workflows/**/*'
|
||||||
|
- '**/*'
|
||||||
|
|
||||||
|
unit:
|
||||||
|
if: needs.files-changed.outputs.unit == 'true'
|
||||||
|
name: Unit
|
||||||
|
needs: files-changed
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
|
||||||
|
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
|
||||||
|
with:
|
||||||
|
node-version-file: './.tool-versions'
|
||||||
|
- name: Unit
|
||||||
|
run: npm install && npm run test:unit
|
||||||
|
working-directory: ./
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -89,3 +89,6 @@ lerna-debug.log
|
|||||||
# System Files
|
# System Files
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# docs
|
||||||
|
/docs
|
||||||
11
cypress.config.ts
Normal file
11
cypress.config.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { defineConfig } from 'cypress'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
component: {
|
||||||
|
devServer: {
|
||||||
|
framework: 'react',
|
||||||
|
bundler: 'vite',
|
||||||
|
},
|
||||||
|
specPattern: ['**/**/*.cy.{ts,tsx}'],
|
||||||
|
},
|
||||||
|
})
|
||||||
37
cypress/support/commands.ts
Normal file
37
cypress/support/commands.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
// ***********************************************
|
||||||
|
// This example commands.ts shows you how to
|
||||||
|
// create various custom commands and overwrite
|
||||||
|
// existing commands.
|
||||||
|
//
|
||||||
|
// For more comprehensive examples of custom
|
||||||
|
// commands please read more here:
|
||||||
|
// https://on.cypress.io/custom-commands
|
||||||
|
// ***********************************************
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a parent command --
|
||||||
|
// Cypress.Commands.add('login', (email, password) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a child command --
|
||||||
|
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This is a dual command --
|
||||||
|
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// -- This will overwrite an existing command --
|
||||||
|
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
|
||||||
|
//
|
||||||
|
// declare global {
|
||||||
|
// namespace Cypress {
|
||||||
|
// interface Chainable {
|
||||||
|
// login(email: string, password: string): Chainable<void>
|
||||||
|
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||||
|
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
|
||||||
|
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
12
cypress/support/component-index.html
Normal file
12
cypress/support/component-index.html
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||||
|
<title>Components App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div data-cy-root></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
38
cypress/support/component.ts
Normal file
38
cypress/support/component.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// ***********************************************************
|
||||||
|
// This example support/component.ts is processed and
|
||||||
|
// loaded automatically before your test files.
|
||||||
|
//
|
||||||
|
// This is a great place to put global configuration and
|
||||||
|
// behavior that modifies Cypress.
|
||||||
|
//
|
||||||
|
// You can change the location of this file or turn off
|
||||||
|
// automatically serving support files with the
|
||||||
|
// 'supportFile' configuration option.
|
||||||
|
//
|
||||||
|
// You can read more here:
|
||||||
|
// https://on.cypress.io/configuration
|
||||||
|
// ***********************************************************
|
||||||
|
|
||||||
|
// Import commands.js using ES2015 syntax:
|
||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
|
import './commands'
|
||||||
|
|
||||||
|
import { mount } from 'cypress/react'
|
||||||
|
|
||||||
|
// Augment the Cypress namespace to include type definitions for
|
||||||
|
// your custom command.
|
||||||
|
// Alternatively, can be defined in cypress/support/component.d.ts
|
||||||
|
// with a <reference path="./component" /> at the top of your spec.
|
||||||
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||||
|
namespace Cypress {
|
||||||
|
interface Chainable {
|
||||||
|
mount: typeof mount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Cypress.Commands.add('mount', mount)
|
||||||
|
|
||||||
|
// Example use:
|
||||||
|
// cy.mount(<MyComponent />)
|
||||||
2434
examples/2-static-layers/package-lock.json
generated
2434
examples/2-static-layers/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -11,8 +11,7 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1"
|
||||||
"utopia-ui": "^3.0.35"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.17.0",
|
||||||
|
|||||||
8164
package-lock.json
generated
8164
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
57
package.json
57
package.json
@ -1,15 +1,31 @@
|
|||||||
{
|
{
|
||||||
"name": "utopia-ui",
|
"name": "utopia-ui",
|
||||||
"version": "3.0.34",
|
"version": "3.0.59",
|
||||||
"description": "Reuseable React Components to build mapping apps for real life communities and networks",
|
"description": "Reuseable React Components to build mapping apps for real life communities and networks",
|
||||||
"repository": "https://github.com/utopia-os/utopia-ui",
|
"repository": "https://github.com/utopia-os/utopia-ui",
|
||||||
"homepage:": "https://utopia-os.org/",
|
"homepage": "https://utopia-os.org/",
|
||||||
"module": "dist/index.js",
|
"module": "./dist/index.esm.js",
|
||||||
"types": "dist/index.d.ts",
|
"main": "./dist/index.cjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.esm.js",
|
||||||
|
"require": "./dist/index.cjs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "rollup -c",
|
"build": "rollup -c",
|
||||||
"start": "rollup -c -w",
|
"start": "rollup -c -w",
|
||||||
"test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 ."
|
"test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
|
||||||
|
"lint": "npm run test:lint:eslint",
|
||||||
|
"lintfix": "npm run test:lint:eslint -- --fix",
|
||||||
|
"test:component": "cypress run --component --browser electron",
|
||||||
|
"test:unit": "npm run test:unit:dev -- run --coverage",
|
||||||
|
"test:unit:dev": "vitest",
|
||||||
|
"docs:generate": "typedoc --plugin typedoc-plugin-coverage src/index.tsx",
|
||||||
|
"update": "npx npm-check-updates"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"dist"
|
"dist"
|
||||||
@ -19,13 +35,22 @@
|
|||||||
"license": "GPL-3.0-only",
|
"license": "GPL-3.0-only",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
|
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
|
||||||
|
"@rollup/plugin-alias": "^5.1.1",
|
||||||
|
"@rollup/plugin-node-resolve": "^16.0.0",
|
||||||
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
|
"@testing-library/jest-dom": "^6.6.3",
|
||||||
|
"@testing-library/react": "^16.2.0",
|
||||||
"@types/geojson": "^7946.0.14",
|
"@types/geojson": "^7946.0.14",
|
||||||
"@types/leaflet": "^1.7.11",
|
"@types/leaflet": "^1.7.11",
|
||||||
|
"@types/leaflet.markercluster": "^1.5.5",
|
||||||
"@types/react": "^18.2.0",
|
"@types/react": "^18.2.0",
|
||||||
"@types/react-dom": "^18.0.5",
|
"@types/react-dom": "^18.0.5",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^5.62.0",
|
||||||
|
"@vitejs/plugin-react": "^4.3.4",
|
||||||
|
"@vitest/coverage-v8": "^3.0.5",
|
||||||
"autoprefixer": "^10.4.14",
|
"autoprefixer": "^10.4.14",
|
||||||
|
"cypress": "^14.0.3",
|
||||||
"daisyui": "^4.6.1",
|
"daisyui": "^4.6.1",
|
||||||
"eslint": "^8.24.0",
|
"eslint": "^8.24.0",
|
||||||
"eslint-config-prettier": "^9.1.0",
|
"eslint-config-prettier": "^9.1.0",
|
||||||
@ -41,15 +66,21 @@
|
|||||||
"eslint-plugin-react-refresh": "^0.4.18",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
"eslint-plugin-security": "^3.0.1",
|
"eslint-plugin-security": "^3.0.1",
|
||||||
"eslint-plugin-yml": "^1.14.0",
|
"eslint-plugin-yml": "^1.14.0",
|
||||||
|
"globals": "^15.14.0",
|
||||||
|
"happy-dom": "^16.8.1",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"react": "^18.2.0",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"rollup": "^2.75.7",
|
"rollup": "^4.34.6",
|
||||||
|
"rollup-plugin-dts": "^6.1.1",
|
||||||
"rollup-plugin-postcss": "^4.0.2",
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
"rollup-plugin-typescript2": "^0.32.1",
|
|
||||||
"tailwindcss": "^3.3.1",
|
"tailwindcss": "^3.3.1",
|
||||||
"typescript": "^4.7.4"
|
"typedoc": "^0.27.6",
|
||||||
|
"typedoc-plugin-coverage": "^3.4.1",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"vite": "^6.0.11",
|
||||||
|
"vitest": "^3.0.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
@ -64,17 +95,19 @@
|
|||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
"leaflet.locatecontrol": "^0.79.0",
|
"leaflet.locatecontrol": "^0.79.0",
|
||||||
"prop-types": "^15.8.1",
|
"prop-types": "^15.8.1",
|
||||||
|
"radash": "^12.1.0",
|
||||||
"react-colorful": "^5.6.1",
|
"react-colorful": "^5.6.1",
|
||||||
"react-image-crop": "^10.1.8",
|
"react-image-crop": "^10.1.8",
|
||||||
"react-leaflet": "^4.2.1",
|
"react-leaflet": "^4.2.1",
|
||||||
"react-leaflet-cluster": "^2.1.0",
|
"react-leaflet-cluster": "^2.1.0",
|
||||||
"react-markdown": "^9.0.1",
|
"react-markdown": "^9.0.1",
|
||||||
|
"react-photo-album": "^3.0.2",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.16.0",
|
||||||
"react-string-replace": "^1.1.1",
|
"react-string-replace": "^1.1.1",
|
||||||
"react-toastify": "^9.1.3",
|
"react-toastify": "^9.1.3",
|
||||||
"remark-breaks": "^4.0.0",
|
"remark-breaks": "^4.0.0",
|
||||||
"tributejs": "^5.1.3",
|
"tw-elements": "^1.0.0",
|
||||||
"tw-elements": "^1.0.0"
|
"yet-another-react-lightbox": "^3.21.7"
|
||||||
},
|
},
|
||||||
"imports": {
|
"imports": {
|
||||||
"#components/*": "./src/Components/*",
|
"#components/*": "./src/Components/*",
|
||||||
|
|||||||
@ -1,22 +1,45 @@
|
|||||||
import postcss from 'rollup-plugin-postcss'
|
import path from 'path'
|
||||||
import typescript from 'rollup-plugin-typescript2'
|
import { fileURLToPath } from 'url'
|
||||||
|
|
||||||
export default {
|
import alias from '@rollup/plugin-alias'
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import typescript from '@rollup/plugin-typescript'
|
||||||
|
import { dts } from 'rollup-plugin-dts'
|
||||||
|
import postcss from 'rollup-plugin-postcss'
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url)
|
||||||
|
const __dirname = path.dirname(__filename)
|
||||||
|
|
||||||
|
const aliasConfig = alias({
|
||||||
|
entries: [{ find: '#types', replacement: path.resolve(__dirname, 'src/types') }],
|
||||||
|
})
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
input: 'src/index.tsx',
|
input: 'src/index.tsx',
|
||||||
output: [
|
output: [
|
||||||
{
|
{
|
||||||
dir: 'dist/',
|
file: 'dist/index.esm.js',
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
exports: 'named',
|
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
strict: false,
|
},
|
||||||
|
{
|
||||||
|
file: 'dist/index.cjs',
|
||||||
|
format: 'cjs',
|
||||||
|
sourcemap: true,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
plugins: [
|
plugins: [
|
||||||
|
aliasConfig,
|
||||||
|
resolve({
|
||||||
|
extensions: ['.ts', '.tsx'],
|
||||||
|
}),
|
||||||
postcss({
|
postcss({
|
||||||
plugins: [],
|
plugins: [],
|
||||||
}),
|
}),
|
||||||
typescript(),
|
typescript({
|
||||||
|
tsconfig: './tsconfig.json',
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
external: [
|
external: [
|
||||||
'react',
|
'react',
|
||||||
@ -48,5 +71,22 @@ export default {
|
|||||||
'react-image-crop/dist/ReactCrop.css',
|
'react-image-crop/dist/ReactCrop.css',
|
||||||
'react-colorful',
|
'react-colorful',
|
||||||
'leaflet.locatecontrol/dist/L.Control.Locate.css',
|
'leaflet.locatecontrol/dist/L.Control.Locate.css',
|
||||||
|
'yet-another-react-lightbox',
|
||||||
|
'react-photo-album',
|
||||||
],
|
],
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
input: 'src/index.tsx',
|
||||||
|
output: [{ file: 'dist/index.d.ts', format: 'es' }],
|
||||||
|
plugins: [
|
||||||
|
aliasConfig,
|
||||||
|
dts({
|
||||||
|
respectExternal: true,
|
||||||
|
compilerOptions: {
|
||||||
|
skipLibCheck: true,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
external: [/\.css$/, /\.d\.ts$/], // ✅ `.d.ts` als extern behandeln
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|||||||
9
scripts/docs-coverage.sh
Executable file
9
scripts/docs-coverage.sh
Executable file
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
COVERAGE=$(sed -nE 's/.*>([0-9]{1,3})%<.*/\1/p' docs/coverage.svg | head -1)
|
||||||
|
|
||||||
|
if (( $COVERAGE >= $COVERAGE_REQUIRED )) then
|
||||||
|
exit 0;
|
||||||
|
else
|
||||||
|
echo "Coverage: $COVERAGE/$COVERAGE_REQUIRED%";
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
2
setupTest.ts
Normal file
2
setupTest.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
|
import '@testing-library/jest-dom'
|
||||||
@ -62,7 +62,7 @@ export default function NavBar({ appName, userType }: { appName: string; userTyp
|
|||||||
if (showNav) {
|
if (showNav) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className='tw-navbar tw-bg-base-100 tw-z-[10000] tw-shadow-xl tw-relative'>
|
<div className='tw-navbar tw-bg-base-100 tw-z-[9998] tw-shadow-xl tw-relative'>
|
||||||
<button
|
<button
|
||||||
className='tw-btn tw-btn-square tw-btn-ghost'
|
className='tw-btn tw-btn-square tw-btn-ghost'
|
||||||
data-te-sidenav-toggle-ref
|
data-te-sidenav-toggle-ref
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
/* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
||||||
import { useEffect, useRef, useState } from 'react'
|
import { useEffect, useRef, useState } from 'react'
|
||||||
import Tribute from 'tributejs'
|
|
||||||
|
|
||||||
import { useTags } from '#components/Map/hooks/useTags'
|
import { useTags } from '#components/Map/hooks/useTags'
|
||||||
|
|
||||||
@ -37,9 +36,6 @@ export function TextAreaInput({
|
|||||||
const ref = useRef<HTMLTextAreaElement>(null)
|
const ref = useRef<HTMLTextAreaElement>(null)
|
||||||
const [inputValue, setInputValue] = useState<string>(defaultValue)
|
const [inputValue, setInputValue] = useState<string>(defaultValue)
|
||||||
|
|
||||||
// prevent react18 from calling useEffect twice
|
|
||||||
const init = useRef(false)
|
|
||||||
|
|
||||||
const tags = useTags()
|
const tags = useTags()
|
||||||
|
|
||||||
const values: KeyValue[] = []
|
const values: KeyValue[] = []
|
||||||
@ -48,30 +44,6 @@ export function TextAreaInput({
|
|||||||
values.push({ key: tag.name, value: tag.name, color: tag.color })
|
values.push({ key: tag.name, value: tag.name, color: tag.color })
|
||||||
})
|
})
|
||||||
|
|
||||||
const tribute = new Tribute({
|
|
||||||
containerClass: 'tw-z-3000 tw-bg-base-100 tw-p-2 tw-rounded-lg tw-shadow',
|
|
||||||
selectClass: 'tw-font-bold',
|
|
||||||
trigger: '#',
|
|
||||||
values,
|
|
||||||
menuShowMinLength: 3,
|
|
||||||
noMatchTemplate: () => {
|
|
||||||
return ''
|
|
||||||
},
|
|
||||||
menuItemTemplate: function (item) {
|
|
||||||
return `<span style="color: ${item.original.color}; padding: 5px; border-radius: 3px;">#${item.string}</span>`
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!init.current) {
|
|
||||||
if (ref.current) {
|
|
||||||
tribute.attach(ref.current)
|
|
||||||
}
|
|
||||||
init.current = true
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [ref])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setInputValue(defaultValue)
|
setInputValue(defaultValue)
|
||||||
}, [defaultValue])
|
}, [defaultValue])
|
||||||
|
|||||||
66
src/Components/Input/TextInput.cy.tsx
Normal file
66
src/Components/Input/TextInput.cy.tsx
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
/// <reference types="cypress" />
|
||||||
|
import { mount } from 'cypress/react'
|
||||||
|
|
||||||
|
import { TextInput } from './TextInput'
|
||||||
|
|
||||||
|
describe('<TextInput />', () => {
|
||||||
|
it('renders with default props', () => {
|
||||||
|
mount(<TextInput />)
|
||||||
|
cy.get('input').should('have.attr', 'type', 'text')
|
||||||
|
cy.get('input').should('have.attr', 'placeholder', '')
|
||||||
|
cy.get('input').should('have.attr', 'required')
|
||||||
|
cy.get('input').should('have.class', 'tw-input')
|
||||||
|
cy.get('input').should('have.class', 'tw-input-bordered')
|
||||||
|
cy.get('input').should('have.class', 'tw-w-full')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with given labelTitle', () => {
|
||||||
|
mount(<TextInput labelTitle='Test Title' />)
|
||||||
|
cy.get('label').should('contain.text', 'Test Title')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders with given type', () => {
|
||||||
|
mount(<TextInput type='email' />)
|
||||||
|
cy.get('input').should('have.attr', 'type', 'email')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts user input', () => {
|
||||||
|
mount(<TextInput dataField='test-input' />)
|
||||||
|
cy.get('input[name="test-input"]').type('Hello Test')
|
||||||
|
cy.get('input[name="test-input"]').should('have.value', 'Hello Test')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders a label, if labelTitle is set', () => {
|
||||||
|
mount(<TextInput dataField='test-input' labelTitle='Test Label' />)
|
||||||
|
cy.contains('Test Label').should('exist')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('handles default value correctly', () => {
|
||||||
|
mount(<TextInput dataField='test-input' defaultValue='Default Value' />)
|
||||||
|
cy.get('input[name="test-input"]').should('have.value', 'Default Value')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('calls updateFormValue on change', () => {
|
||||||
|
const onChangeSpy = cy.spy().as('updateFormValueSpy')
|
||||||
|
mount(<TextInput dataField='test-input' updateFormValue={onChangeSpy} />)
|
||||||
|
cy.get('input[name="test-input"]').type('Test')
|
||||||
|
cy.get('@updateFormValueSpy').should('have.been.calledWith', 'Test')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts a specific input type', () => {
|
||||||
|
mount(<TextInput dataField='test-input' type='email' />)
|
||||||
|
cy.get('input[name="test-input"]').should('have.attr', 'type', 'email')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('respects the autocomplete attribute', () => {
|
||||||
|
mount(<TextInput dataField='test-input' autocomplete='off' />)
|
||||||
|
cy.get('input[name="test-input"]').should('have.attr', 'autocomplete', 'off')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('updates form value on change', () => {
|
||||||
|
const updateFormValue = cy.stub()
|
||||||
|
mount(<TextInput updateFormValue={updateFormValue} />)
|
||||||
|
cy.get('input').type('Hello')
|
||||||
|
cy.wrap(updateFormValue).should('have.been.calledWith', 'Hello')
|
||||||
|
})
|
||||||
|
})
|
||||||
32
src/Components/Input/TextInput.spec.tsx
Normal file
32
src/Components/Input/TextInput.spec.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { render, screen, fireEvent } from '@testing-library/react'
|
||||||
|
import { describe, it, expect, beforeEach, vi } from 'vitest'
|
||||||
|
|
||||||
|
import { TextInput } from './TextInput'
|
||||||
|
|
||||||
|
describe('<TextInput />', () => {
|
||||||
|
let wrapper = render(<TextInput />)
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
wrapper = render(<TextInput />)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('renders properly', () => {
|
||||||
|
expect(wrapper.container.firstChild).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('handleChange', () => {
|
||||||
|
it('calls updateFormValue with new value', () => {
|
||||||
|
const updateFormValue = vi.fn()
|
||||||
|
wrapper.rerender(<TextInput updateFormValue={updateFormValue} />)
|
||||||
|
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'test' } })
|
||||||
|
expect(updateFormValue).toBeCalledWith('test')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('labelTitle', () => {
|
||||||
|
it('sets label', () => {
|
||||||
|
wrapper.rerender(<TextInput labelTitle='My Title' />)
|
||||||
|
expect(wrapper.container.firstChild).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
38
src/Components/Input/__snapshots__/TextInput.spec.tsx.snap
Normal file
38
src/Components/Input/__snapshots__/TextInput.spec.tsx.snap
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||||
|
|
||||||
|
exports[`<TextInput /> > labelTitle > sets label 1`] = `
|
||||||
|
<div
|
||||||
|
class="tw-form-control undefined"
|
||||||
|
>
|
||||||
|
<label
|
||||||
|
class="tw-label"
|
||||||
|
>
|
||||||
|
<span
|
||||||
|
class="tw-label-text tw-text-base-content undefined"
|
||||||
|
>
|
||||||
|
My Title
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
<input
|
||||||
|
class="tw-input tw-input-bordered tw-w-full "
|
||||||
|
placeholder=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`<TextInput /> > renders properly 1`] = `
|
||||||
|
<div
|
||||||
|
class="tw-form-control undefined"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
class="tw-input tw-input-bordered tw-w-full "
|
||||||
|
placeholder=""
|
||||||
|
required=""
|
||||||
|
type="text"
|
||||||
|
value=""
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
@ -1,4 +1,3 @@
|
|||||||
import { node, string } from 'prop-types'
|
|
||||||
import { Children, cloneElement, isValidElement, useEffect } from 'react'
|
import { Children, cloneElement, isValidElement, useEffect } from 'react'
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
@ -33,11 +32,4 @@ export const ItemForm = ({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemForm.propTypes = {
|
ItemForm.__TYPE = 'ItemForm'
|
||||||
children: node,
|
|
||||||
__TYPE: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemForm.defaultProps = {
|
|
||||||
__TYPE: 'ItemForm',
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
import { node, string } from 'prop-types'
|
|
||||||
import { Children, cloneElement, isValidElement } from 'react'
|
import { Children, cloneElement, isValidElement } from 'react'
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
@ -8,18 +7,11 @@ export const ItemView = ({ children, item }: { children?: React.ReactNode; item?
|
|||||||
<div>
|
<div>
|
||||||
{children
|
{children
|
||||||
? Children.toArray(children).map((child) =>
|
? Children.toArray(children).map((child) =>
|
||||||
isValidElement<{ item: Item }>(child) ? cloneElement(child, { item }) : '',
|
isValidElement<{ item: Item }>(child) ? cloneElement(child, { item }) : null,
|
||||||
)
|
)
|
||||||
: ''}
|
: null}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemView.propTypes = {
|
ItemView.__TYPE = 'ItemView'
|
||||||
children: node,
|
|
||||||
__TYPE: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
ItemView.defaultProps = {
|
|
||||||
__TYPE: 'ItemView',
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,18 +1,9 @@
|
|||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
||||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
import { Children, isValidElement, useEffect, useState } from 'react'
|
import { Children, isValidElement, useEffect, useState } from 'react'
|
||||||
import { Marker, Tooltip, useMap, useMapEvents } from 'react-leaflet'
|
import { Marker, Tooltip } from 'react-leaflet'
|
||||||
import { useLocation } from 'react-router-dom'
|
|
||||||
|
|
||||||
import { encodeTag } from '#utils/FormatTags'
|
import { encodeTag } from '#utils/FormatTags'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||||
import MarkerIconFactory from '#utils/MarkerIconFactory'
|
import MarkerIconFactory from '#utils/MarkerIconFactory'
|
||||||
import { randomColor } from '#utils/RandomColor'
|
import { randomColor } from '#utils/RandomColor'
|
||||||
@ -34,6 +25,7 @@ import type { Item } from '#types/Item'
|
|||||||
import type { LayerProps } from '#types/LayerProps'
|
import type { LayerProps } from '#types/LayerProps'
|
||||||
import type { Tag } from '#types/Tag'
|
import type { Tag } from '#types/Tag'
|
||||||
import type { Popup } from 'leaflet'
|
import type { Popup } from 'leaflet'
|
||||||
|
import type { ReactElement, ReactNode } from 'react'
|
||||||
|
|
||||||
export const Layer = ({
|
export const Layer = ({
|
||||||
data,
|
data,
|
||||||
@ -42,23 +34,12 @@ export const Layer = ({
|
|||||||
menuIcon = 'MapPinIcon',
|
menuIcon = 'MapPinIcon',
|
||||||
menuText = 'add new place',
|
menuText = 'add new place',
|
||||||
menuColor = '#2E7D32',
|
menuColor = '#2E7D32',
|
||||||
markerIcon = 'circle-solid',
|
markerIcon = 'point',
|
||||||
markerShape = 'circle',
|
markerShape = 'circle',
|
||||||
markerDefaultColor = '#777',
|
markerDefaultColor = '#777',
|
||||||
markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)',
|
markerDefaultColor2 = 'RGBA(35, 31, 32, 0.2)',
|
||||||
api,
|
api,
|
||||||
itemType,
|
itemType,
|
||||||
itemNameField = 'name',
|
|
||||||
itemSubnameField,
|
|
||||||
itemTextField = 'text',
|
|
||||||
itemAvatarField,
|
|
||||||
itemColorField,
|
|
||||||
itemOwnerField,
|
|
||||||
itemLatitudeField = 'position.coordinates.1',
|
|
||||||
itemLongitudeField = 'position.coordinates.0',
|
|
||||||
itemTagsField,
|
|
||||||
itemOffersField,
|
|
||||||
itemNeedsField,
|
|
||||||
onlyOnePerOwner = false,
|
onlyOnePerOwner = false,
|
||||||
customEditLink,
|
customEditLink,
|
||||||
customEditParameter,
|
customEditParameter,
|
||||||
@ -79,8 +60,6 @@ export const Layer = ({
|
|||||||
const addPopup = useAddPopup()
|
const addPopup = useAddPopup()
|
||||||
const leafletRefs = useLeafletRefs()
|
const leafletRefs = useLeafletRefs()
|
||||||
|
|
||||||
const location = useLocation()
|
|
||||||
|
|
||||||
const allTagsLoaded = useAllTagsLoaded()
|
const allTagsLoaded = useAllTagsLoaded()
|
||||||
const allItemsLoaded = useAllItemsLoaded()
|
const allItemsLoaded = useAllItemsLoaded()
|
||||||
|
|
||||||
@ -92,8 +71,6 @@ export const Layer = ({
|
|||||||
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
|
const [newTagsToAdd, setNewTagsToAdd] = useState<Tag[]>([])
|
||||||
const [tagsReady, setTagsReady] = useState<boolean>(false)
|
const [tagsReady, setTagsReady] = useState<boolean>(false)
|
||||||
|
|
||||||
const map = useMap()
|
|
||||||
|
|
||||||
const isLayerVisible = useIsLayerVisible()
|
const isLayerVisible = useIsLayerVisible()
|
||||||
|
|
||||||
const isGroupTypeVisible = useIsGroupTypeVisible()
|
const isGroupTypeVisible = useIsGroupTypeVisible()
|
||||||
@ -115,16 +92,8 @@ export const Layer = ({
|
|||||||
markerDefaultColor2,
|
markerDefaultColor2,
|
||||||
api,
|
api,
|
||||||
itemType,
|
itemType,
|
||||||
itemNameField,
|
|
||||||
itemSubnameField,
|
|
||||||
itemTextField,
|
|
||||||
itemAvatarField,
|
|
||||||
itemColorField,
|
|
||||||
itemOwnerField,
|
|
||||||
itemTagsField,
|
|
||||||
itemOffersField,
|
|
||||||
itemNeedsField,
|
|
||||||
onlyOnePerOwner,
|
onlyOnePerOwner,
|
||||||
|
// Can we just use editCallback for all cases?
|
||||||
customEditLink,
|
customEditLink,
|
||||||
customEditParameter,
|
customEditParameter,
|
||||||
// eslint-disable-next-line camelcase
|
// eslint-disable-next-line camelcase
|
||||||
@ -132,6 +101,7 @@ export const Layer = ({
|
|||||||
listed,
|
listed,
|
||||||
setItemFormPopup,
|
setItemFormPopup,
|
||||||
itemFormPopup,
|
itemFormPopup,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
clusterRef,
|
clusterRef,
|
||||||
})
|
})
|
||||||
api &&
|
api &&
|
||||||
@ -148,15 +118,6 @@ export const Layer = ({
|
|||||||
markerDefaultColor2,
|
markerDefaultColor2,
|
||||||
api,
|
api,
|
||||||
itemType,
|
itemType,
|
||||||
itemNameField,
|
|
||||||
itemSubnameField,
|
|
||||||
itemTextField,
|
|
||||||
itemAvatarField,
|
|
||||||
itemColorField,
|
|
||||||
itemOwnerField,
|
|
||||||
itemTagsField,
|
|
||||||
itemOffersField,
|
|
||||||
itemNeedsField,
|
|
||||||
onlyOnePerOwner,
|
onlyOnePerOwner,
|
||||||
customEditLink,
|
customEditLink,
|
||||||
customEditParameter,
|
customEditParameter,
|
||||||
@ -165,69 +126,12 @@ export const Layer = ({
|
|||||||
listed,
|
listed,
|
||||||
setItemFormPopup,
|
setItemFormPopup,
|
||||||
itemFormPopup,
|
itemFormPopup,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
clusterRef,
|
clusterRef,
|
||||||
})
|
})
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [data, api])
|
}, [data, api])
|
||||||
|
|
||||||
useMapEvents({
|
|
||||||
popupopen: (e) => {
|
|
||||||
const item = Object.entries(leafletRefs).find((r) => r[1].popup === e.popup)?.[1].item
|
|
||||||
if (item?.layer?.name === name && window.location.pathname.split('/')[1] !== item.id) {
|
|
||||||
const params = new URLSearchParams(window.location.search)
|
|
||||||
if (!location.pathname.includes('/item/')) {
|
|
||||||
window.history.pushState(
|
|
||||||
{},
|
|
||||||
'',
|
|
||||||
`/${item.id}` + `${params.toString() !== '' ? `?${params}` : ''}`,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
let title = ''
|
|
||||||
if (item.name) title = item.name
|
|
||||||
else if (item.layer.itemNameField) title = getValue(item, item.layer.itemNameField)
|
|
||||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const openPopup = () => {
|
|
||||||
if (
|
|
||||||
window.location.pathname.split('/').length <= 1 ||
|
|
||||||
window.location.pathname.split('/')[1] === ''
|
|
||||||
) {
|
|
||||||
map.closePopup()
|
|
||||||
} 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 &&
|
|
||||||
clusterRef.hasLayer(ref.marker) &&
|
|
||||||
clusterRef?.zoomToShowLayer(ref.marker, () => {
|
|
||||||
ref.marker.openPopup()
|
|
||||||
})
|
|
||||||
let title = ''
|
|
||||||
if (ref.item.name) title = ref.item.name
|
|
||||||
else if (ref.item.layer.itemNameField)
|
|
||||||
title = getValue(ref.item.name, ref.item.layer.itemNameField)
|
|
||||||
document.title = `${document.title.split('-')[0]} - ${title}`
|
|
||||||
document
|
|
||||||
.querySelector('meta[property="og:title"]')
|
|
||||||
?.setAttribute('content', ref.item.name)
|
|
||||||
document
|
|
||||||
.querySelector('meta[property="og:description"]')
|
|
||||||
?.setAttribute('content', ref.item.text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
openPopup()
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [leafletRefs, location])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tagsReady) {
|
if (tagsReady) {
|
||||||
const processedTags = {}
|
const processedTags = {}
|
||||||
@ -264,29 +168,19 @@ export const Layer = ({
|
|||||||
visibleGroupTypes.length === 0,
|
visibleGroupTypes.length === 0,
|
||||||
)
|
)
|
||||||
.map((item: Item) => {
|
.map((item: Item) => {
|
||||||
if (getValue(item, itemLongitudeField) && getValue(item, itemLatitudeField)) {
|
if (item.position?.coordinates[0] && item.position?.coordinates[1]) {
|
||||||
// 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) {
|
if (item.tags) {
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
item.text += '\n\n'
|
||||||
item[itemTextField] = item[itemTextField] + '\n\n'
|
|
||||||
item.tags.map((tag) => {
|
item.tags.map((tag) => {
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
if (!item.text?.includes(`#${encodeTag(tag)}`)) {
|
||||||
if (!item[itemTextField].includes(`#${encodeTag(tag)}`)) {
|
item.text += `#${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.text
|
||||||
return item[itemTextField]
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
if (allTagsLoaded && allItemsLoaded) {
|
if (allTagsLoaded && allItemsLoaded) {
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
item.text?.match(hashTagRegex)?.map((tag) => {
|
||||||
item[itemTextField].match(hashTagRegex)?.map((tag) => {
|
|
||||||
if (
|
if (
|
||||||
!tags.find(
|
!tags.find(
|
||||||
(t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase(),
|
(t) => t.name.toLocaleLowerCase() === tag.slice(1).toLocaleLowerCase(),
|
||||||
@ -309,20 +203,19 @@ export const Layer = ({
|
|||||||
|
|
||||||
const itemTags = getItemTags(item)
|
const itemTags = getItemTags(item)
|
||||||
|
|
||||||
const latitude =
|
const latitude = item.position.coordinates[1]
|
||||||
itemLatitudeField && item ? getValue(item, itemLatitudeField) : undefined
|
const longitude = item.position.coordinates[0]
|
||||||
const longitude =
|
|
||||||
itemLongitudeField && item ? getValue(item, itemLongitudeField) : undefined
|
|
||||||
|
|
||||||
let color1 = markerDefaultColor
|
let color1 = markerDefaultColor
|
||||||
let color2 = markerDefaultColor2
|
let color2 = markerDefaultColor2
|
||||||
if (itemColorField && getValue(item, itemColorField) != null)
|
if (item.color) {
|
||||||
color1 = getValue(item, itemColorField)
|
color1 = item.color
|
||||||
else if (itemTags && itemTags[0]) {
|
} else if (itemTags[0]) {
|
||||||
color1 = itemTags[0].color
|
color1 = itemTags[0].color
|
||||||
}
|
}
|
||||||
if (itemTags && itemTags[0] && itemColorField) color2 = itemTags[0].color
|
if (itemTags[0] && item.color) {
|
||||||
else if (itemTags && itemTags[1]) {
|
color2 = itemTags[0].color
|
||||||
|
} else if (itemTags[1]) {
|
||||||
color2 = itemTags[1].color
|
color2 = itemTags[1].color
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@ -348,10 +241,10 @@ export const Layer = ({
|
|||||||
>
|
>
|
||||||
{children &&
|
{children &&
|
||||||
Children.toArray(children).some(
|
Children.toArray(children).some(
|
||||||
(child) => isValidElement(child) && child.props.__TYPE === 'ItemView',
|
(child) => isComponentWithType(child) && child.type.__TYPE === 'ItemView',
|
||||||
) ? (
|
) ? (
|
||||||
Children.toArray(children).map((child) =>
|
Children.toArray(children).map((child) =>
|
||||||
isValidElement(child) && child.props.__TYPE === 'ItemView' ? (
|
isComponentWithType(child) && child.type.__TYPE === 'ItemView' ? (
|
||||||
<ItemViewPopup
|
<ItemViewPopup
|
||||||
ref={(r) => {
|
ref={(r) => {
|
||||||
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) {
|
if (!(item.id in leafletRefs && leafletRefs[item.id].popup === r)) {
|
||||||
@ -364,9 +257,7 @@ export const Layer = ({
|
|||||||
>
|
>
|
||||||
{child}
|
{child}
|
||||||
</ItemViewPopup>
|
</ItemViewPopup>
|
||||||
) : (
|
) : null,
|
||||||
''
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -382,8 +273,9 @@ export const Layer = ({
|
|||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Tooltip offset={[0, -38]} direction='top'>
|
<Tooltip offset={[0, -38]} direction='top'>
|
||||||
{item.name ? item.name : getValue(item, itemNameField)}
|
{item.name}
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Marker>
|
</Marker>
|
||||||
)
|
)
|
||||||
@ -396,10 +288,10 @@ export const Layer = ({
|
|||||||
itemFormPopup.layer.name === name &&
|
itemFormPopup.layer.name === name &&
|
||||||
(children &&
|
(children &&
|
||||||
Children.toArray(children).some(
|
Children.toArray(children).some(
|
||||||
(child) => isValidElement(child) && child.props.__TYPE === 'ItemForm',
|
(child) => isComponentWithType(child) && child.type.__TYPE === 'ItemForm',
|
||||||
) ? (
|
) ? (
|
||||||
Children.toArray(children).map((child) =>
|
Children.toArray(children).map((child) =>
|
||||||
isValidElement(child) && child.props.__TYPE === 'ItemForm' ? (
|
isComponentWithType(child) && child.type.__TYPE === 'ItemForm' ? (
|
||||||
<ItemFormPopup
|
<ItemFormPopup
|
||||||
key={setItemFormPopup?.name}
|
key={setItemFormPopup?.name}
|
||||||
position={itemFormPopup.position}
|
position={itemFormPopup.position}
|
||||||
@ -426,3 +318,7 @@ export const Layer = ({
|
|||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isComponentWithType(node: ReactNode): node is ReactElement & { type: { __TYPE: string } } {
|
||||||
|
return isValidElement(node) && typeof node.type !== 'string' && '__TYPE' in node.type
|
||||||
|
}
|
||||||
|
|||||||
@ -57,6 +57,10 @@ export default function AddButton({
|
|||||||
onClick={() => {
|
onClick={() => {
|
||||||
triggerAction(layer)
|
triggerAction(layer)
|
||||||
}}
|
}}
|
||||||
|
onTouchEnd={(e) => {
|
||||||
|
triggerAction(layer)
|
||||||
|
e.preventDefault()
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src={layer.menuIcon}
|
src={layer.menuIcon}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import { useEffect, useRef, useState } from 'react'
|
|||||||
import { useMap, useMapEvents } from 'react-leaflet'
|
import { useMap, useMapEvents } from 'react-leaflet'
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
// eslint-disable-next-line import/no-unassigned-import
|
||||||
import 'leaflet.locatecontrol'
|
import 'leaflet.locatecontrol'
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import 'leaflet.locatecontrol/dist/L.Control.Locate.css'
|
import 'leaflet.locatecontrol/dist/L.Control.Locate.css'
|
||||||
|
|
||||||
// Converts leaflet.locatecontrol to a React Component
|
// Converts leaflet.locatecontrol to a React Component
|
||||||
|
|||||||
@ -23,7 +23,6 @@ import { useLeafletRefs } from '#components/Map/hooks/useLeafletRefs'
|
|||||||
import { useTags } from '#components/Map/hooks/useTags'
|
import { useTags } from '#components/Map/hooks/useTags'
|
||||||
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
||||||
import { decodeTag } from '#utils/FormatTags'
|
import { decodeTag } from '#utils/FormatTags'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
import MarkerIconFactory from '#utils/MarkerIconFactory'
|
import MarkerIconFactory from '#utils/MarkerIconFactory'
|
||||||
|
|
||||||
import { LocateControl } from './LocateControl'
|
import { LocateControl } from './LocateControl'
|
||||||
@ -73,12 +72,10 @@ export const SearchControl = () => {
|
|||||||
searchGeo()
|
searchGeo()
|
||||||
setItemsResults(
|
setItemsResults(
|
||||||
items.filter((item) => {
|
items.filter((item) => {
|
||||||
if (item.layer?.itemNameField) item.name = getValue(item, item.layer.itemNameField)
|
|
||||||
if (item.layer?.itemTextField) item.text = getValue(item, item.layer.itemTextField)
|
|
||||||
return (
|
return (
|
||||||
value.length > 2 &&
|
value.length > 2 &&
|
||||||
((item.layer?.listed && item.name.toLowerCase().includes(value.toLowerCase())) ||
|
((item.layer?.listed && item.name.toLowerCase().includes(value.toLowerCase())) ||
|
||||||
item.text.toLowerCase().includes(value.toLowerCase()))
|
item.text?.toLowerCase().includes(value.toLowerCase()))
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -189,7 +189,7 @@ export function ItemFormPopup(props: ItemFormPopupProps) {
|
|||||||
key={props.position.toString()}
|
key={props.position.toString()}
|
||||||
placeholder='Text'
|
placeholder='Text'
|
||||||
dataField='text'
|
dataField='text'
|
||||||
defaultValue={props.item ? props.item.text : ''}
|
defaultValue={props.item?.text ?? ''}
|
||||||
inputStyle='tw-h-40 tw-mt-5'
|
inputStyle='tw-h-40 tw-mt-5'
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -9,13 +9,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
import { useState } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||||
import DialogModal from '#components/Templates/DialogModal'
|
import DialogModal from '#components/Templates/DialogModal'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
import type { ItemsApi } from '#types/ItemsApi'
|
import type { ItemsApi } from '#types/ItemsApi'
|
||||||
@ -26,9 +25,6 @@ export function HeaderView({
|
|||||||
editCallback,
|
editCallback,
|
||||||
deleteCallback,
|
deleteCallback,
|
||||||
setPositionCallback,
|
setPositionCallback,
|
||||||
itemNameField,
|
|
||||||
itemSubnameField,
|
|
||||||
itemAvatarField,
|
|
||||||
loading,
|
loading,
|
||||||
hideMenu = false,
|
hideMenu = false,
|
||||||
big = false,
|
big = false,
|
||||||
@ -41,9 +37,6 @@ export function HeaderView({
|
|||||||
editCallback?: any
|
editCallback?: any
|
||||||
deleteCallback?: any
|
deleteCallback?: any
|
||||||
setPositionCallback?: any
|
setPositionCallback?: any
|
||||||
itemNameField?: string
|
|
||||||
itemAvatarField?: string
|
|
||||||
itemSubnameField?: string
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
hideMenu?: boolean
|
hideMenu?: boolean
|
||||||
big?: boolean
|
big?: boolean
|
||||||
@ -57,23 +50,17 @@ export function HeaderView({
|
|||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const appState = useAppState()
|
const appState = useAppState()
|
||||||
|
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setImageLoaded(false)
|
||||||
|
}, [item])
|
||||||
|
|
||||||
const avatar =
|
const avatar =
|
||||||
itemAvatarField && getValue(item, itemAvatarField)
|
item.image &&
|
||||||
? appState.assetsApi.url +
|
appState.assetsApi.url + item.image + `${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
|
||||||
getValue(item, itemAvatarField) +
|
const title = item.name
|
||||||
`${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
|
const subtitle = item.subname
|
||||||
: item.layer?.itemAvatarField &&
|
|
||||||
item &&
|
|
||||||
getValue(item, item.layer?.itemAvatarField) &&
|
|
||||||
appState.assetsApi.url +
|
|
||||||
getValue(item, item.layer?.itemAvatarField) +
|
|
||||||
`${big ? '?width=160&heigth=160' : '?width=80&heigth=80'}`
|
|
||||||
const title = itemNameField
|
|
||||||
? getValue(item, itemNameField)
|
|
||||||
: item.layer?.itemNameField && item && getValue(item, item.layer.itemNameField)
|
|
||||||
const subtitle = itemSubnameField
|
|
||||||
? getValue(item, itemSubnameField)
|
|
||||||
: item.layer?.itemSubnameField && item && getValue(item, item.layer.itemSubnameField)
|
|
||||||
|
|
||||||
const [address] = useState<string>('')
|
const [address] = useState<string>('')
|
||||||
|
|
||||||
@ -92,13 +79,21 @@ export function HeaderView({
|
|||||||
{avatar && (
|
{avatar && (
|
||||||
<div className='tw-avatar'>
|
<div className='tw-avatar'>
|
||||||
<div
|
<div
|
||||||
className={`${big ? 'tw-w-20' : 'tw-w-10'} tw-inline tw-items-center tw-justify-center overflow-hidden`}
|
className={`${
|
||||||
|
big ? 'tw-w-20' : 'tw-w-10'
|
||||||
|
} tw-inline tw-items-center tw-justify-center overflow-hidden`}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
className={'tw-w-full tw-h-full tw-object-cover tw-rounded-full'}
|
className={'tw-w-full tw-h-full tw-object-cover tw-rounded-full'}
|
||||||
src={avatar}
|
src={avatar}
|
||||||
alt={item.name + ' logo'}
|
alt={item.name + ' logo'}
|
||||||
|
onLoad={() => setImageLoaded(true)}
|
||||||
|
onError={() => setImageLoaded(false)}
|
||||||
|
style={{ display: imageLoaded ? 'block' : 'none' }}
|
||||||
/>
|
/>
|
||||||
|
{!imageLoaded && (
|
||||||
|
<div className='tw-w-full tw-h-full tw-bg-gray-200 tw-rounded-full' />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@ -154,7 +149,7 @@ export function HeaderView({
|
|||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
item.layer?.customEditLink
|
item.layer?.customEditLink
|
||||||
? navigate(
|
? navigate(
|
||||||
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${getValue(item, item.layer.customEditParameter)}${params && '?' + params}` : ''} `,
|
`${item.layer.customEditLink}${item.layer.customEditParameter ? `/${item.id}${params && '?' + params}` : ''} `,
|
||||||
)
|
)
|
||||||
: editCallback(e)
|
: editCallback(e)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
import { Link } from 'react-router-dom'
|
import { Link } from 'react-router-dom'
|
||||||
|
|
||||||
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
|
|
||||||
@ -11,23 +10,21 @@ export const PopupButton = ({
|
|||||||
url,
|
url,
|
||||||
parameterField,
|
parameterField,
|
||||||
text,
|
text,
|
||||||
colorField,
|
|
||||||
item,
|
item,
|
||||||
}: {
|
}: {
|
||||||
url: string
|
url: string
|
||||||
parameterField?: string
|
parameterField?: string
|
||||||
text: string
|
text: string
|
||||||
colorField?: string
|
|
||||||
item?: Item
|
item?: Item
|
||||||
}) => {
|
}) => {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
const getItemTags = useGetItemTags()
|
const getItemTags = useGetItemTags()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link to={`${url}/${parameterField ? getValue(item, parameterField) : ''}?${params}`}>
|
<Link to={`${url}/${parameterField ? item?.id : ''}?${params}`}>
|
||||||
<button
|
<button
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : item && getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor}`,
|
backgroundColor: `${item?.color ?? (item && (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : (item?.layer?.markerDefaultColor ?? '#000')))}`,
|
||||||
}}
|
}}
|
||||||
className='tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1'
|
className='tw-btn tw-text-white tw-btn-sm tw-float-right tw-mt-1'
|
||||||
>
|
>
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import remarkBreaks from 'remark-breaks'
|
|||||||
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
|
import { useAddFilterTag } from '#components/Map/hooks/useFilter'
|
||||||
import { useTags } from '#components/Map/hooks/useTags'
|
import { useTags } from '#components/Map/hooks/useTags'
|
||||||
import { decodeTag } from '#utils/FormatTags'
|
import { decodeTag } from '#utils/FormatTags'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||||
import { fixUrls, mailRegex } from '#utils/ReplaceURLs'
|
import { fixUrls, mailRegex } from '#utils/ReplaceURLs'
|
||||||
|
|
||||||
@ -21,32 +20,37 @@ import type { Tag } from '#types/Tag'
|
|||||||
|
|
||||||
export const TextView = ({
|
export const TextView = ({
|
||||||
item,
|
item,
|
||||||
|
itemId,
|
||||||
|
text,
|
||||||
truncate = false,
|
truncate = false,
|
||||||
itemTextField,
|
|
||||||
rawText,
|
rawText,
|
||||||
}: {
|
}: {
|
||||||
item?: Item
|
item?: Item
|
||||||
|
itemId: string
|
||||||
|
text?: string
|
||||||
truncate?: boolean
|
truncate?: boolean
|
||||||
itemTextField?: string
|
|
||||||
rawText?: string
|
rawText?: string
|
||||||
}) => {
|
}) => {
|
||||||
|
if (item) {
|
||||||
|
text = item.text
|
||||||
|
itemId = item.id
|
||||||
|
}
|
||||||
const tags = useTags()
|
const tags = useTags()
|
||||||
const addFilterTag = useAddFilterTag()
|
const addFilterTag = useAddFilterTag()
|
||||||
|
|
||||||
let text = ''
|
let innerText = ''
|
||||||
let replacedText = ''
|
let replacedText = ''
|
||||||
|
|
||||||
if (rawText) {
|
if (rawText) {
|
||||||
text = replacedText = rawText
|
innerText = replacedText = rawText
|
||||||
} else if (itemTextField && item) {
|
} else if (text) {
|
||||||
text = getValue(item, itemTextField)
|
innerText = text
|
||||||
} else {
|
|
||||||
text = item?.layer?.itemTextField && item ? getValue(item, item.layer.itemTextField) : ''
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (item && text && truncate) text = truncateText(removeMarkdownKeepLinksAndParagraphs(text), 100)
|
if (innerText && truncate)
|
||||||
|
innerText = truncateText(removeMarkdownKeepLinksAndParagraphs(innerText), 100)
|
||||||
|
|
||||||
if (item && text) replacedText = fixUrls(text)
|
if (innerText) replacedText = fixUrls(innerText)
|
||||||
|
|
||||||
if (replacedText) {
|
if (replacedText) {
|
||||||
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
|
replacedText = replacedText.replace(/(?<!\]?\()https?:\/\/[^\s)]+(?!\))/g, (url) => {
|
||||||
@ -114,16 +118,16 @@ export const TextView = ({
|
|||||||
const CustomHashTagLink = ({
|
const CustomHashTagLink = ({
|
||||||
children,
|
children,
|
||||||
tag,
|
tag,
|
||||||
item,
|
itemId,
|
||||||
}: {
|
}: {
|
||||||
children: string
|
children: string
|
||||||
tag: Tag
|
tag: Tag
|
||||||
item?: Item
|
itemId: string
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
style={{ color: tag ? tag.color : '#faa', fontWeight: 'bold', cursor: 'pointer' }}
|
style={{ color: tag ? tag.color : '#faa', fontWeight: 'bold', cursor: 'pointer' }}
|
||||||
key={tag ? tag.name + item?.id : item?.id}
|
key={tag ? tag.name + itemId : itemId}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation()
|
e.stopPropagation()
|
||||||
addFilterTag(tag)
|
addFilterTag(tag)
|
||||||
@ -173,7 +177,7 @@ export const TextView = ({
|
|||||||
)
|
)
|
||||||
if (tag)
|
if (tag)
|
||||||
return (
|
return (
|
||||||
<CustomHashTagLink tag={tag} item={item}>
|
<CustomHashTagLink tag={tag} itemId={itemId}>
|
||||||
{children}
|
{children}
|
||||||
</CustomHashTagLink>
|
</CustomHashTagLink>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -67,6 +67,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
|
|||||||
success = true
|
success = true
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
toast.error(error.toString())
|
toast.error(error.toString())
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -104,7 +105,7 @@ export const ItemViewPopup = forwardRef((props: ItemViewPopupProps, ref: any) =>
|
|||||||
: '',
|
: '',
|
||||||
)
|
)
|
||||||
) : (
|
) : (
|
||||||
<TextView item={props.item} />
|
<TextView text={props.item.text} itemId={props.item.id} />
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 tw-mt-1'>
|
<div className='tw-flex -tw-mb-1 tw-flex-row tw-mr-2 tw-mt-1'>
|
||||||
|
|||||||
@ -135,6 +135,27 @@
|
|||||||
width: 24px;
|
width: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shop-icon {
|
||||||
|
position: relative;
|
||||||
|
top: -34px;
|
||||||
|
left: 4px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plant-icon {
|
||||||
|
position: relative;
|
||||||
|
top: -34px;
|
||||||
|
left: 4px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.circle-dot-icon {
|
||||||
|
position: relative;
|
||||||
|
top: -36px;
|
||||||
|
left: 4px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
.leaflet-popup-scrolled {
|
.leaflet-popup-scrolled {
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,16 +1,46 @@
|
|||||||
|
import { LatLng } from 'leaflet'
|
||||||
|
import { MapContainer } from 'react-leaflet'
|
||||||
|
|
||||||
import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
import { ContextWrapper } from '#components/AppShell/ContextWrapper'
|
||||||
|
|
||||||
import { UtopiaMapInner } from './UtopiaMapInner'
|
import { UtopiaMapInner } from './UtopiaMapInner'
|
||||||
|
|
||||||
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import 'react-toastify/dist/ReactToastify.css'
|
import 'react-toastify/dist/ReactToastify.css'
|
||||||
|
|
||||||
function UtopiaMap(props: UtopiaMapProps) {
|
function UtopiaMap({
|
||||||
|
height = '500px',
|
||||||
|
width = '100%',
|
||||||
|
center = [50.6, 9.5],
|
||||||
|
zoom = 10,
|
||||||
|
children,
|
||||||
|
geo,
|
||||||
|
showFilterControl = false,
|
||||||
|
showGratitudeControl = false,
|
||||||
|
showLayerControl = true,
|
||||||
|
infoText,
|
||||||
|
donationWidget,
|
||||||
|
}: UtopiaMapProps) {
|
||||||
return (
|
return (
|
||||||
<ContextWrapper>
|
<ContextWrapper>
|
||||||
<UtopiaMapInner {...props} />
|
<MapContainer
|
||||||
|
style={{ height, width }}
|
||||||
|
center={new LatLng(center[0], center[1])}
|
||||||
|
zoom={zoom}
|
||||||
|
zoomControl={false}
|
||||||
|
maxZoom={19}
|
||||||
|
>
|
||||||
|
<UtopiaMapInner
|
||||||
|
geo={geo}
|
||||||
|
showFilterControl={showFilterControl}
|
||||||
|
showGratitudeControl={showGratitudeControl}
|
||||||
|
showLayerControl={showLayerControl}
|
||||||
|
infoText={infoText}
|
||||||
|
donationWidget={donationWidget}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</UtopiaMapInner>
|
||||||
|
</MapContainer>
|
||||||
</ContextWrapper>
|
</ContextWrapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,29 +6,20 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
import { LatLng } from 'leaflet'
|
import { Children, cloneElement, isValidElement, useEffect, useRef, useState } from 'react'
|
||||||
import {
|
import { TileLayer, useMapEvents, GeoJSON, useMap } from 'react-leaflet'
|
||||||
Children,
|
|
||||||
cloneElement,
|
|
||||||
createRef,
|
|
||||||
isValidElement,
|
|
||||||
useEffect,
|
|
||||||
useRef,
|
|
||||||
useState,
|
|
||||||
} from 'react'
|
|
||||||
import { TileLayer, MapContainer, useMapEvents, GeoJSON } from 'react-leaflet'
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
import MarkerClusterGroup from 'react-leaflet-cluster'
|
import MarkerClusterGroup from 'react-leaflet-cluster'
|
||||||
import { Outlet } from 'react-router-dom'
|
import { Outlet, useLocation } from 'react-router-dom'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import './UtopiaMap.css'
|
import './UtopiaMap.css'
|
||||||
|
|
||||||
|
import { containsUUID } from '#utils/ContainsUUID'
|
||||||
|
|
||||||
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
|
import { useClusterRef, useSetClusterRef } from './hooks/useClusterRef'
|
||||||
import { useAddVisibleLayer } from './hooks/useFilter'
|
import { useAddVisibleLayer } from './hooks/useFilter'
|
||||||
import { useLayers } from './hooks/useLayers'
|
import { useLayers } from './hooks/useLayers'
|
||||||
|
import { useLeafletRefs } from './hooks/useLeafletRefs'
|
||||||
import {
|
import {
|
||||||
useSelectPosition,
|
useSelectPosition,
|
||||||
useSetMapClicked,
|
useSetMapClicked,
|
||||||
@ -41,6 +32,7 @@ import { GratitudeControl } from './Subcomponents/Controls/GratitudeControl'
|
|||||||
import { LayerControl } from './Subcomponents/Controls/LayerControl'
|
import { LayerControl } from './Subcomponents/Controls/LayerControl'
|
||||||
import { SearchControl } from './Subcomponents/Controls/SearchControl'
|
import { SearchControl } from './Subcomponents/Controls/SearchControl'
|
||||||
import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
import { TagsControl } from './Subcomponents/Controls/TagsControl'
|
||||||
|
import { PopupButton } from './Subcomponents/ItemPopupComponents/PopupButton'
|
||||||
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
import { TextView } from './Subcomponents/ItemPopupComponents/TextView'
|
||||||
import { SelectPosition } from './Subcomponents/SelectPosition'
|
import { SelectPosition } from './Subcomponents/SelectPosition'
|
||||||
|
|
||||||
@ -48,21 +40,14 @@ import type { ItemFormPopupProps } from '#types/ItemFormPopupProps'
|
|||||||
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
import type { UtopiaMapProps } from '#types/UtopiaMapProps'
|
||||||
import type { Feature, Geometry as GeoJSONGeometry } from 'geojson'
|
import type { Feature, Geometry as GeoJSONGeometry } from 'geojson'
|
||||||
|
|
||||||
const mapDivRef = createRef()
|
|
||||||
|
|
||||||
export function UtopiaMapInner({
|
export function UtopiaMapInner({
|
||||||
height = '500px',
|
|
||||||
width = '100%',
|
|
||||||
center = [50.6, 9.5],
|
|
||||||
zoom = 10,
|
|
||||||
children,
|
children,
|
||||||
geo,
|
geo,
|
||||||
showFilterControl = false,
|
showFilterControl = false,
|
||||||
showGratitudeControl = false,
|
showGratitudeControl = false,
|
||||||
showLayerControl = true,
|
showLayerControl = true,
|
||||||
infoText,
|
donationWidget,
|
||||||
}: UtopiaMapProps) {
|
}: UtopiaMapProps) {
|
||||||
// Hooks that rely on contexts, called after ContextWrapper is provided
|
|
||||||
const selectNewItemPosition = useSelectPosition()
|
const selectNewItemPosition = useSelectPosition()
|
||||||
const setSelectNewItemPosition = useSetSelectPosition()
|
const setSelectNewItemPosition = useSetSelectPosition()
|
||||||
const setClusterRef = useSetClusterRef()
|
const setClusterRef = useSetClusterRef()
|
||||||
@ -72,6 +57,10 @@ export function UtopiaMapInner({
|
|||||||
|
|
||||||
const layers = useLayers()
|
const layers = useLayers()
|
||||||
const addVisibleLayer = useAddVisibleLayer()
|
const addVisibleLayer = useAddVisibleLayer()
|
||||||
|
const leafletRefs = useLeafletRefs()
|
||||||
|
|
||||||
|
const location = useLocation()
|
||||||
|
const map = useMap()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
layers.forEach((layer) => addVisibleLayer(layer))
|
layers.forEach((layer) => addVisibleLayer(layer))
|
||||||
@ -81,10 +70,22 @@ export function UtopiaMapInner({
|
|||||||
const init = useRef(false)
|
const init = useRef(false)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!init.current) {
|
if (!init.current) {
|
||||||
infoText &&
|
donationWidget &&
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
toast(<TextView rawText={infoText} />, { autoClose: false })
|
toast(
|
||||||
}, 4000)
|
<>
|
||||||
|
<TextView itemId='' rawText={'## Do you like this Map?'} />
|
||||||
|
<div>
|
||||||
|
<TextView
|
||||||
|
itemId=''
|
||||||
|
rawText={'Support us building free opensource maps and help us grow 🌱☀️'}
|
||||||
|
/>
|
||||||
|
<PopupButton url={'https://opencollective.com/utopia-project'} text={'Donate'} />
|
||||||
|
</div>
|
||||||
|
</>,
|
||||||
|
{ autoClose: false },
|
||||||
|
)
|
||||||
|
}, 600000)
|
||||||
init.current = true
|
init.current = true
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -105,9 +106,61 @@ export function UtopiaMapInner({
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useMapEvents({
|
||||||
|
popupopen: (e) => {
|
||||||
|
const item = Object.entries(leafletRefs).find((r) => r[1].popup === e.popup)?.[1].item
|
||||||
|
if (window.location.pathname.split('/')[1] !== item?.id) {
|
||||||
|
const params = new URLSearchParams(window.location.search)
|
||||||
|
if (!location.pathname.includes('/item/')) {
|
||||||
|
window.history.pushState(
|
||||||
|
{},
|
||||||
|
'',
|
||||||
|
`/${item?.id}` + `${params.toString() !== '' ? `?${params}` : ''}`,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let title = ''
|
||||||
|
if (item?.name) title = item.name
|
||||||
|
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const openPopup = () => {
|
||||||
|
if (!containsUUID(window.location.pathname)) {
|
||||||
|
map.closePopup()
|
||||||
|
} 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]
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
if (ref) {
|
||||||
|
clusterRef.hasLayer(ref.marker) &&
|
||||||
|
clusterRef?.zoomToShowLayer(ref.marker, () => {
|
||||||
|
ref.marker.openPopup()
|
||||||
|
})
|
||||||
|
let title = ''
|
||||||
|
if (ref.item.name) title = ref.item.name
|
||||||
|
document.title = `${document.title.split('-')[0]} - ${title}`
|
||||||
|
document
|
||||||
|
.querySelector('meta[property="og:title"]')
|
||||||
|
?.setAttribute('content', ref.item.name)
|
||||||
|
document
|
||||||
|
.querySelector('meta[property="og:description"]')
|
||||||
|
?.setAttribute('content', ref.item.text ?? '')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
openPopup()
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [leafletRefs, location])
|
||||||
|
|
||||||
const resetMetaTags = () => {
|
const resetMetaTags = () => {
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
if (!window.location.pathname.includes('/item/')) {
|
if (!containsUUID(window.location.pathname)) {
|
||||||
window.history.pushState({}, '', '/' + `${params.toString() !== '' ? `?${params}` : ''}`)
|
window.history.pushState({}, '', '/' + `${params.toString() !== '' ? `?${params}` : ''}`)
|
||||||
}
|
}
|
||||||
document.title = document.title.split('-')[0]
|
document.title = document.title.split('-')[0]
|
||||||
@ -129,14 +182,6 @@ export function UtopiaMapInner({
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}
|
className={`tw-h-full ${selectNewItemPosition != null ? 'crosshair-cursor-enabled' : undefined}`}
|
||||||
>
|
|
||||||
<MapContainer
|
|
||||||
ref={mapDivRef}
|
|
||||||
style={{ height, width }}
|
|
||||||
center={new LatLng(center[0], center[1])}
|
|
||||||
zoom={zoom}
|
|
||||||
zoomControl={false}
|
|
||||||
maxZoom={19}
|
|
||||||
>
|
>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<Control position='topLeft' zIndex='1000' absolute>
|
<Control position='topLeft' zIndex='1000' absolute>
|
||||||
@ -154,7 +199,7 @@ export function UtopiaMapInner({
|
|||||||
url='https://tile.osmand.net/hd/{z}/{x}/{y}.png'
|
url='https://tile.osmand.net/hd/{z}/{x}/{y}.png'
|
||||||
/>
|
/>
|
||||||
<MarkerClusterGroup
|
<MarkerClusterGroup
|
||||||
ref={(r) => setClusterRef(r)}
|
ref={(r) => setClusterRef(r as any)}
|
||||||
showCoverageOnHover
|
showCoverageOnHover
|
||||||
chunkedLoading
|
chunkedLoading
|
||||||
maxClusterRadius={50}
|
maxClusterRadius={50}
|
||||||
@ -185,7 +230,6 @@ export function UtopiaMapInner({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<MapEventListener />
|
<MapEventListener />
|
||||||
</MapContainer>
|
|
||||||
<AddButton triggerAction={setSelectNewItemPosition} />
|
<AddButton triggerAction={setSelectNewItemPosition} />
|
||||||
{selectNewItemPosition != null && (
|
{selectNewItemPosition != null && (
|
||||||
<SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />
|
<SelectPosition setSelectNewItemPosition={setSelectNewItemPosition} />
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/no-misused-promises */
|
/* eslint-disable @typescript-eslint/no-misused-promises */
|
||||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||||
import { toast } from 'react-toastify'
|
import { toast } from 'react-toastify'
|
||||||
@ -82,6 +82,7 @@ function useItemsManager(initialItems: Item[]): {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
result.map((item) => {
|
result.map((item) => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
dispatch({ type: 'ADD', item: { ...item, layer } })
|
dispatch({ type: 'ADD', item: { ...item, layer } })
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
|
|||||||
@ -63,7 +63,7 @@ function useSelectPositionManager(): {
|
|||||||
if ('menuIcon' in selectPosition) {
|
if ('menuIcon' in selectPosition) {
|
||||||
mapClicked &&
|
mapClicked &&
|
||||||
mapClicked.setItemFormPopup({
|
mapClicked.setItemFormPopup({
|
||||||
layer: selectPosition as LayerProps,
|
layer: selectPosition,
|
||||||
position: mapClicked.position,
|
position: mapClicked.position,
|
||||||
})
|
})
|
||||||
setSelectPosition(null)
|
setSelectPosition(null)
|
||||||
@ -98,6 +98,7 @@ function useSelectPositionManager(): {
|
|||||||
success = true
|
success = true
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
toast.error(error.toString())
|
toast.error(error.toString())
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -123,6 +124,7 @@ function useSelectPositionManager(): {
|
|||||||
success = true
|
success = true
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
toast.error(error.toString())
|
toast.error(error.toString())
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -145,6 +147,7 @@ function useSelectPositionManager(): {
|
|||||||
success = true
|
success = true
|
||||||
// eslint-disable-next-line no-catch-all/no-catch-all
|
// eslint-disable-next-line no-catch-all/no-catch-all
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||||
toast.error(error.toString())
|
toast.error(error.toString())
|
||||||
}
|
}
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|||||||
@ -5,12 +5,8 @@
|
|||||||
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
/* eslint-disable @typescript-eslint/prefer-optional-chain */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
import { useCallback, useReducer, createContext, useContext, useState } from 'react'
|
||||||
|
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
import { hashTagRegex } from '#utils/HashTagRegex'
|
import { hashTagRegex } from '#utils/HashTagRegex'
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
@ -96,8 +92,7 @@ function useTagsManager(initialTags: Tag[]): {
|
|||||||
|
|
||||||
const getItemTags = useCallback(
|
const getItemTags = useCallback(
|
||||||
(item: Item) => {
|
(item: Item) => {
|
||||||
const text =
|
const text = item.text
|
||||||
item.layer?.itemTextField && item ? getValue(item, item.layer.itemTextField) : undefined
|
|
||||||
const itemTagStrings = text?.match(hashTagRegex)
|
const itemTagStrings = text?.match(hashTagRegex)
|
||||||
const itemTags: Tag[] = []
|
const itemTags: Tag[] = []
|
||||||
itemTagStrings?.map((tag) => {
|
itemTagStrings?.map((tag) => {
|
||||||
@ -108,17 +103,14 @@ function useTagsManager(initialTags: Tag[]): {
|
|||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
})
|
})
|
||||||
item.layer?.itemOffersField &&
|
// Could be refactored as it occurs in multiple places
|
||||||
getValue(item, item.layer.itemOffersField)?.map((o) => {
|
item.offers?.forEach((o) => {
|
||||||
const offer = tags.find((t) => t.id === o.tags_id)
|
const offer = tags.find((t) => t.id === o.tags_id)
|
||||||
offer && itemTags.push(offer)
|
offer && itemTags.push(offer)
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
item.layer?.itemNeedsField &&
|
item.needs?.forEach((n) => {
|
||||||
getValue(item, item.layer.itemNeedsField)?.map((n) => {
|
|
||||||
const need = tags.find((t) => t.id === n.tags_id)
|
const need = tags.find((t) => t.id === n.tags_id)
|
||||||
need && itemTags.push(need)
|
need && itemTags.push(need)
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return itemTags
|
return itemTags
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useLocation, useNavigate } from 'react-router-dom'
|
import { useLocation, useNavigate } from 'react-router-dom'
|
||||||
@ -14,7 +11,6 @@ import { useLayers } from '#components/Map/hooks/useLayers'
|
|||||||
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
||||||
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
|
import { useAddTag, useGetItemTags, useTags } from '#components/Map/hooks/useTags'
|
||||||
import { MapOverlayPage } from '#components/Templates'
|
import { MapOverlayPage } from '#components/Templates'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
|
import { linkItem, onUpdateItem, unlinkItem } from './itemFunctions'
|
||||||
import { FormHeader } from './Subcomponents/FormHeader'
|
import { FormHeader } from './Subcomponents/FormHeader'
|
||||||
@ -23,11 +19,12 @@ import { OnepagerForm } from './Templates/OnepagerForm'
|
|||||||
import { SimpleForm } from './Templates/SimpleForm'
|
import { SimpleForm } from './Templates/SimpleForm'
|
||||||
import { TabsForm } from './Templates/TabsForm'
|
import { TabsForm } from './Templates/TabsForm'
|
||||||
|
|
||||||
|
import type { FormState } from '#types/FormState'
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
import type { Tag } from '#types/Tag'
|
import type { Tag } from '#types/Tag'
|
||||||
|
|
||||||
export function ProfileForm() {
|
export function ProfileForm() {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState<FormState>({
|
||||||
color: '',
|
color: '',
|
||||||
id: '',
|
id: '',
|
||||||
group_type: 'wuerdekompass',
|
group_type: 'wuerdekompass',
|
||||||
@ -91,11 +88,10 @@ export function ProfileForm() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const newColor =
|
const newColor =
|
||||||
item.layer?.itemColorField && getValue(item, item.layer.itemColorField)
|
item.color ??
|
||||||
? getValue(item, item.layer.itemColorField)
|
(getItemTags(item) && getItemTags(item)[0]?.color
|
||||||
: getItemTags(item) && getItemTags(item)[0]?.color
|
|
||||||
? getItemTags(item)[0].color
|
? getItemTags(item)[0].color
|
||||||
: item.layer?.markerDefaultColor
|
: item.layer?.markerDefaultColor)
|
||||||
|
|
||||||
const offers = (item.offers ?? []).reduce((acc: Tag[], o) => {
|
const offers = (item.offers ?? []).reduce((acc: Tag[], o) => {
|
||||||
const offer = tags.find((t) => t.id === o.tags_id)
|
const offer = tags.find((t) => t.id === o.tags_id)
|
||||||
@ -116,7 +112,7 @@ export function ProfileForm() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
setState({
|
setState({
|
||||||
color: newColor,
|
color: newColor ?? '',
|
||||||
id: item?.id ?? '',
|
id: item?.id ?? '',
|
||||||
group_type: item?.group_type ?? '',
|
group_type: item?.group_type ?? '',
|
||||||
status: item?.status ?? '',
|
status: item?.status ?? '',
|
||||||
@ -127,7 +123,8 @@ export function ProfileForm() {
|
|||||||
telephone: item?.telephone ?? '',
|
telephone: item?.telephone ?? '',
|
||||||
next_appointment: item?.next_appointment ?? '',
|
next_appointment: item?.next_appointment ?? '',
|
||||||
image: item?.image ?? '',
|
image: item?.image ?? '',
|
||||||
marker_icon: item?.marker_icon ?? '',
|
// Do we actually mean marker_icon here?
|
||||||
|
marker_icon: item?.markerIcon ?? '',
|
||||||
offers,
|
offers,
|
||||||
needs,
|
needs,
|
||||||
relations,
|
relations,
|
||||||
@ -140,7 +137,7 @@ export function ProfileForm() {
|
|||||||
const [template, setTemplate] = useState<string>('')
|
const [template, setTemplate] = useState<string>('')
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTemplate(item.layer?.itemType.template || appState.userType)
|
setTemplate(item.layer?.itemType.template ?? appState.userType)
|
||||||
}, [appState.userType, item])
|
}, [appState.userType, item])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -198,7 +195,8 @@ export function ProfileForm() {
|
|||||||
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
|
className={loading ? ' tw-loading tw-btn tw-float-right' : 'tw-btn tw-float-right'}
|
||||||
type='submit'
|
type='submit'
|
||||||
style={{
|
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}`,
|
// We could refactor this, it is used several times at different locations
|
||||||
|
backgroundColor: `${item.color ?? (getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item?.layer?.markerDefaultColor)}`,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/await-thenable */
|
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
/* eslint-disable @typescript-eslint/no-non-null-asserted-optional-chain */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
||||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
import { LatLng } from 'leaflet'
|
import { LatLng } from 'leaflet'
|
||||||
@ -21,7 +21,6 @@ import { useSelectPosition, useSetSelectPosition } from '#components/Map/hooks/u
|
|||||||
import { useTags } from '#components/Map/hooks/useTags'
|
import { useTags } from '#components/Map/hooks/useTags'
|
||||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||||
import { MapOverlayPage } from '#components/Templates'
|
import { MapOverlayPage } from '#components/Templates'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
|
import { handleDelete, linkItem, unlinkItem } from './itemFunctions'
|
||||||
import { FlexView } from './Templates/FlexView'
|
import { FlexView } from './Templates/FlexView'
|
||||||
@ -32,6 +31,7 @@ import { TabsView } from './Templates/TabsView'
|
|||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
import type { ItemsApi } from '#types/ItemsApi'
|
import type { ItemsApi } from '#types/ItemsApi'
|
||||||
import type { Tag } from '#types/Tag'
|
import type { Tag } from '#types/Tag'
|
||||||
|
import type { Marker } from 'leaflet'
|
||||||
|
|
||||||
export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any> }) {
|
export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any> }) {
|
||||||
const [item, setItem] = useState<Item>()
|
const [item, setItem] = useState<Item>()
|
||||||
@ -88,30 +88,25 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
|||||||
setNeeds([])
|
setNeeds([])
|
||||||
setRelations([])
|
setRelations([])
|
||||||
|
|
||||||
item?.layer?.itemOffersField &&
|
item?.offers?.forEach((o) => {
|
||||||
getValue(item, item.layer.itemOffersField)?.map((o) => {
|
|
||||||
const tag = tags.find((t) => t.id === o.tags_id)
|
const tag = tags.find((t) => t.id === o.tags_id)
|
||||||
tag && setOffers((current) => [...current, tag])
|
tag && setOffers((current) => [...current, tag])
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
item?.layer?.itemNeedsField &&
|
item?.needs?.forEach((n) => {
|
||||||
getValue(item, item.layer.itemNeedsField)?.map((n) => {
|
|
||||||
const tag = tags.find((t) => t.id === n.tags_id)
|
const tag = tags.find((t) => t.id === n.tags_id)
|
||||||
tag && setNeeds((current) => [...current, tag])
|
tag && setNeeds((current) => [...current, tag])
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
item?.relations?.map((r) => {
|
item?.relations?.forEach((r) => {
|
||||||
const item = items.find((i) => i.id === r.related_items_id)
|
const item = items.find((i) => i.id === r.related_items_id)
|
||||||
item && setRelations((current) => [...current, item])
|
item && setRelations((current) => [...current, item])
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [item, items])
|
}, [item, items])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const setMap = async (marker, x) => {
|
const setMap = (marker: Marker, x: number) => {
|
||||||
await map.setView(
|
map.setView(
|
||||||
new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4),
|
new LatLng(item?.position?.coordinates[1]!, item?.position?.coordinates[0]! + x / 4),
|
||||||
undefined,
|
undefined,
|
||||||
)
|
)
|
||||||
@ -164,7 +159,7 @@ export function ProfileView({ attestationApi }: { attestationApi?: ItemsApi<any>
|
|||||||
}, [selectPosition])
|
}, [selectPosition])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setTemplate(item?.layer?.itemType.template || appState.userType)
|
setTemplate(item?.layer?.itemType.template ?? appState.userType)
|
||||||
}, [appState.userType, item])
|
}, [appState.userType, item])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -10,7 +10,6 @@ import { useHasUserPermission } from '#components/Map/hooks/usePermissions'
|
|||||||
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
import { useGetItemTags } from '#components/Map/hooks/useTags'
|
||||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||||
import DialogModal from '#components/Templates/DialogModal'
|
import DialogModal from '#components/Templates/DialogModal'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
|
|
||||||
@ -20,7 +19,6 @@ export function ActionButton({
|
|||||||
triggerItemSelected,
|
triggerItemSelected,
|
||||||
existingRelations,
|
existingRelations,
|
||||||
itemType,
|
itemType,
|
||||||
colorField,
|
|
||||||
collection = 'items',
|
collection = 'items',
|
||||||
customStyle,
|
customStyle,
|
||||||
}: {
|
}: {
|
||||||
@ -28,7 +26,6 @@ export function ActionButton({
|
|||||||
triggerItemSelected?: any
|
triggerItemSelected?: any
|
||||||
existingRelations: Item[]
|
existingRelations: Item[]
|
||||||
itemType?: string
|
itemType?: string
|
||||||
colorField?: string
|
|
||||||
collection?: string
|
collection?: string
|
||||||
customStyle?: string
|
customStyle?: string
|
||||||
item: Item
|
item: Item
|
||||||
@ -45,6 +42,12 @@ export function ActionButton({
|
|||||||
.filter((i) => !existingRelations.some((s) => s.id === i.id))
|
.filter((i) => !existingRelations.some((s) => s.id === i.id))
|
||||||
.filter((i) => i.id !== item.id)
|
.filter((i) => i.id !== item.id)
|
||||||
|
|
||||||
|
const backgroundColor =
|
||||||
|
item.color ??
|
||||||
|
(getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color
|
||||||
|
? getItemTags(item)[0].color
|
||||||
|
: item.layer?.markerDefaultColor)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{hasUserPermission(collection, 'update', item) && (
|
{hasUserPermission(collection, 'update', item) && (
|
||||||
@ -58,7 +61,7 @@ export function ActionButton({
|
|||||||
setModalOpen(true)
|
setModalOpen(true)
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item.layer?.markerDefaultColor}`,
|
backgroundColor,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@ -82,7 +85,7 @@ export function ActionButton({
|
|||||||
triggerAddButton()
|
triggerAddButton()
|
||||||
}}
|
}}
|
||||||
style={{
|
style={{
|
||||||
backgroundColor: `${colorField && getValue(item, colorField) ? getValue(item, colorField) : getItemTags(item) && getItemTags(item)[0] && getItemTags(item)[0].color ? getItemTags(item)[0].color : item.layer?.markerDefaultColor}`,
|
backgroundColor,
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import { useState, useCallback, useRef } from 'react'
|
|||||||
import { ReactCrop, centerCrop, makeAspectCrop } from 'react-image-crop'
|
import { ReactCrop, centerCrop, makeAspectCrop } from 'react-image-crop'
|
||||||
|
|
||||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import 'react-image-crop/dist/ReactCrop.css'
|
import 'react-image-crop/dist/ReactCrop.css'
|
||||||
import DialogModal from '#components/Templates/DialogModal'
|
import DialogModal from '#components/Templates/DialogModal'
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
import { useCallback, useEffect, useRef, useState } from 'react'
|
import { useCallback, useEffect, useRef, useState } from 'react'
|
||||||
import { HexColorPicker } from 'react-colorful'
|
import { HexColorPicker } from 'react-colorful'
|
||||||
|
|
||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import './ColorPicker.css'
|
import './ColorPicker.css'
|
||||||
import useClickOutside from '#components/Profile/hooks/useClickOutside'
|
import useClickOutside from '#components/Profile/hooks/useClickOutside'
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|||||||
36
src/Components/Profile/Subcomponents/GalleryView.tsx
Normal file
36
src/Components/Profile/Subcomponents/GalleryView.tsx
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { useState } from 'react'
|
||||||
|
import { RowsPhotoAlbum } from 'react-photo-album'
|
||||||
|
import ReactLightbox from 'yet-another-react-lightbox'
|
||||||
|
import 'yet-another-react-lightbox/styles.css'
|
||||||
|
import 'react-photo-album/rows.css'
|
||||||
|
|
||||||
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
|
|
||||||
|
import type { Item } from '#types/Item'
|
||||||
|
|
||||||
|
export const GalleryView = ({ item }: { item: Item }) => {
|
||||||
|
const [index, setIndex] = useState(-1)
|
||||||
|
const appState = useAppState()
|
||||||
|
const images = item.gallery?.map((i, j) => {
|
||||||
|
return {
|
||||||
|
src: appState.assetsApi.url + `${i.directus_files_id.id}.jpg`,
|
||||||
|
width: i.directus_files_id.width,
|
||||||
|
height: i.directus_files_id.height,
|
||||||
|
index: j,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!images) throw new Error('GalleryView: images is undefined')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='tw-mx-6 tw-mb-6'>
|
||||||
|
<RowsPhotoAlbum
|
||||||
|
photos={images}
|
||||||
|
targetRowHeight={150}
|
||||||
|
onClick={({ index: current }) => setIndex(current)}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<ReactLightbox index={index} slides={images} open={index >= 0} close={() => setIndex(-1)} />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -4,46 +4,29 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
||||||
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
import { useEffect } from 'react'
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
import { useAppState } from '#components/AppShell/hooks/useAppState'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
|
|
||||||
export function LinkedItemsHeaderView({
|
export function LinkedItemsHeaderView({
|
||||||
item,
|
item,
|
||||||
unlinkCallback,
|
unlinkCallback,
|
||||||
itemNameField,
|
|
||||||
itemAvatarField,
|
|
||||||
loading,
|
loading,
|
||||||
unlinkPermission,
|
unlinkPermission,
|
||||||
itemSubnameField,
|
|
||||||
}: {
|
}: {
|
||||||
item: Item
|
item: Item
|
||||||
unlinkCallback?: any
|
unlinkCallback?: any
|
||||||
itemNameField?: string
|
|
||||||
itemAvatarField?: string
|
|
||||||
itemSubnameField?: string
|
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
unlinkPermission: boolean
|
unlinkPermission: boolean
|
||||||
}) {
|
}) {
|
||||||
const appState = useAppState()
|
const appState = useAppState()
|
||||||
|
|
||||||
const avatar =
|
const avatar = appState.assetsApi.url + item.image
|
||||||
itemAvatarField && getValue(item, itemAvatarField)
|
const title = item.name
|
||||||
? appState.assetsApi.url + getValue(item, itemAvatarField)
|
const subtitle = item.subname
|
||||||
: item.layer?.itemAvatarField &&
|
|
||||||
item &&
|
|
||||||
getValue(item, item.layer?.itemAvatarField) &&
|
|
||||||
appState.assetsApi.url + getValue(item, item.layer?.itemAvatarField)
|
|
||||||
const title = itemNameField
|
|
||||||
? getValue(item, itemNameField)
|
|
||||||
: item.layer?.itemNameField && item && getValue(item, item.layer.itemNameField)
|
|
||||||
const subtitle = itemSubnameField
|
|
||||||
? getValue(item, itemSubnameField)
|
|
||||||
: item.layer?.itemSubnameField && item && getValue(item, item.layer.itemSubnameField)
|
|
||||||
|
|
||||||
useEffect(() => {}, [item])
|
useEffect(() => {}, [item])
|
||||||
|
|
||||||
|
|||||||
@ -5,7 +5,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { TextAreaInput } from '#components/Input'
|
import { TextAreaInput } from '#components/Input'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import { MarkdownHint } from './MarkdownHint'
|
import { MarkdownHint } from './MarkdownHint'
|
||||||
|
|
||||||
@ -14,6 +13,7 @@ import type { FormState } from '#types/FormState'
|
|||||||
export const ProfileTextForm = ({
|
export const ProfileTextForm = ({
|
||||||
state,
|
state,
|
||||||
setState,
|
setState,
|
||||||
|
// Is this really used?
|
||||||
dataField,
|
dataField,
|
||||||
heading,
|
heading,
|
||||||
size,
|
size,
|
||||||
@ -49,7 +49,8 @@ export const ProfileTextForm = ({
|
|||||||
</div>
|
</div>
|
||||||
<TextAreaInput
|
<TextAreaInput
|
||||||
placeholder={'...'}
|
placeholder={'...'}
|
||||||
defaultValue={getValue(state, field)}
|
// eslint-disable-next-line security/detect-object-injection
|
||||||
|
defaultValue={state[field]}
|
||||||
updateFormValue={(v) =>
|
updateFormValue={(v) =>
|
||||||
setState((prevState) => ({
|
setState((prevState) => ({
|
||||||
...prevState,
|
...prevState,
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
import { get } from 'radash'
|
||||||
|
|
||||||
import { TextView } from '#components/Map'
|
import { TextView } from '#components/Map'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
|
|
||||||
export const ProfileTextView = ({
|
export const ProfileTextView = ({
|
||||||
item,
|
item,
|
||||||
dataField,
|
dataField = 'text',
|
||||||
heading,
|
heading,
|
||||||
hideWhenEmpty,
|
hideWhenEmpty,
|
||||||
}: {
|
}: {
|
||||||
@ -15,13 +15,19 @@ export const ProfileTextView = ({
|
|||||||
heading: string
|
heading: string
|
||||||
hideWhenEmpty: boolean
|
hideWhenEmpty: boolean
|
||||||
}) => {
|
}) => {
|
||||||
|
const text = get(item, dataField)
|
||||||
|
|
||||||
|
if (typeof text !== 'string') {
|
||||||
|
throw new Error('ProfileTextView: text is not a string')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='tw-my-10 tw-mt-2 tw-px-6'>
|
<div className='tw-my-10 tw-mt-2 tw-px-6'>
|
||||||
{!(getValue(item, dataField) === '' && hideWhenEmpty) && (
|
{!(text === '' && hideWhenEmpty) && (
|
||||||
<h2 className='tw-text-lg tw-font-semibold'>{heading}</h2>
|
<h2 className='tw-text-lg tw-font-semibold'>{heading}</h2>
|
||||||
)}
|
)}
|
||||||
<div className='tw-mt-2 tw-text-sm'>
|
<div className='tw-mt-2 tw-text-sm'>
|
||||||
<TextView rawText={dataField ? getValue(item, dataField) : getValue(item, 'text')} />
|
<TextView itemId={item.id} rawText={text} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,5 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
|
|
||||||
import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoForm'
|
import { ContactInfoForm } from '#components/Profile/Subcomponents/ContactInfoForm'
|
||||||
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'
|
import { GroupSubheaderForm } from '#components/Profile/Subcomponents/GroupSubheaderForm'
|
||||||
|
|||||||
@ -1,19 +1,20 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
|
|
||||||
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
|
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
|
||||||
|
import { GalleryView } from '#components/Profile/Subcomponents/GalleryView'
|
||||||
import { GroupSubHeaderView } from '#components/Profile/Subcomponents/GroupSubHeaderView'
|
import { GroupSubHeaderView } from '#components/Profile/Subcomponents/GroupSubHeaderView'
|
||||||
import { ProfileStartEndView } from '#components/Profile/Subcomponents/ProfileStartEndView'
|
import { ProfileStartEndView } from '#components/Profile/Subcomponents/ProfileStartEndView'
|
||||||
import { ProfileTextView } from '#components/Profile/Subcomponents/ProfileTextView'
|
import { ProfileTextView } from '#components/Profile/Subcomponents/ProfileTextView'
|
||||||
|
|
||||||
import type { Item } from '#types/Item'
|
import type { Item } from '#types/Item'
|
||||||
|
import type { Key } from 'react'
|
||||||
|
|
||||||
const componentMap = {
|
const componentMap = {
|
||||||
groupSubheaders: GroupSubHeaderView,
|
groupSubheaders: GroupSubHeaderView,
|
||||||
texts: ProfileTextView,
|
texts: ProfileTextView,
|
||||||
contactInfos: ContactInfoView,
|
contactInfos: ContactInfoView,
|
||||||
startEnd: ProfileStartEndView,
|
startEnd: ProfileStartEndView,
|
||||||
|
gallery: GalleryView,
|
||||||
// weitere Komponenten hier
|
// weitere Komponenten hier
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,14 +23,17 @@ export const FlexView = ({ item }: { item: Item }) => {
|
|||||||
console.log(item)
|
console.log(item)
|
||||||
return (
|
return (
|
||||||
<div className='tw-h-full tw-overflow-y-auto fade'>
|
<div className='tw-h-full tw-overflow-y-auto fade'>
|
||||||
{item.layer?.itemType.profileTemplate.map((templateItem) => {
|
{item.layer?.itemType.profileTemplate.map(
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(templateItem: { collection: string | number; id: Key | null | undefined; item: any }) => {
|
||||||
const TemplateComponent = componentMap[templateItem.collection]
|
const TemplateComponent = componentMap[templateItem.collection]
|
||||||
return TemplateComponent ? (
|
return TemplateComponent ? (
|
||||||
<TemplateComponent key={templateItem.id} item={item} {...templateItem.item} />
|
<TemplateComponent key={templateItem.id} item={item} {...templateItem.item} />
|
||||||
) : (
|
) : (
|
||||||
<div key={templateItem.id}>Component not found</div>
|
<div key={templateItem.id}>Component not found</div>
|
||||||
)
|
)
|
||||||
})}
|
},
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
import { TextView } from '#components/Map'
|
import { TextView } from '#components/Map'
|
||||||
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
|
import { ContactInfoView } from '#components/Profile/Subcomponents/ContactInfoView'
|
||||||
@ -16,14 +15,14 @@ export const OnepagerView = ({ item }: { item: Item }) => {
|
|||||||
{item.user_created?.first_name && <ContactInfoView heading='Du hast Fragen?' item={item} />}
|
{item.user_created?.first_name && <ContactInfoView heading='Du hast Fragen?' item={item} />}
|
||||||
{/* Description Section */}
|
{/* Description Section */}
|
||||||
<div className='tw-my-10 tw-mt-2 tw-px-6 tw-text-sm '>
|
<div className='tw-my-10 tw-mt-2 tw-px-6 tw-text-sm '>
|
||||||
<TextView rawText={item.text || 'Keine Beschreibung vorhanden'} />
|
<TextView itemId={item.id} rawText={item.text ?? 'Keine Beschreibung vorhanden'} />
|
||||||
</div>
|
</div>
|
||||||
{/* Next Appointment Section */}
|
{/* Next Appointment Section */}
|
||||||
{item.next_appointment && (
|
{item.next_appointment && (
|
||||||
<div className='tw-my-10 tw-px-6'>
|
<div className='tw-my-10 tw-px-6'>
|
||||||
<h2 className='tw-text-lg tw-font-semibold'>Nächste Termine</h2>
|
<h2 className='tw-text-lg tw-font-semibold'>Nächste Termine</h2>
|
||||||
<div className='tw-mt-2 tw-text-sm'>
|
<div className='tw-mt-2 tw-text-sm'>
|
||||||
<TextView rawText={item.next_appointment} />
|
<TextView itemId={item.id} rawText={item.next_appointment} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import type { Item } from '#types/Item'
|
|||||||
export const SimpleView = ({ item }: { item: Item }) => {
|
export const SimpleView = ({ item }: { item: Item }) => {
|
||||||
return (
|
return (
|
||||||
<div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'>
|
<div className='tw-mt-8 tw-h-full tw-overflow-y-auto fade tw-px-6'>
|
||||||
<TextView item={item} />
|
<TextView text={item.text} itemId={item.id} />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -113,6 +113,7 @@ export const TabsForm = ({
|
|||||||
}
|
}
|
||||||
inputStyle='tw-h-24'
|
inputStyle='tw-h-24'
|
||||||
containerStyle='tw-pt-4'
|
containerStyle='tw-pt-4'
|
||||||
|
required={false}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -172,7 +173,7 @@ export const TabsForm = ({
|
|||||||
name='my_tabs_2'
|
name='my_tabs_2'
|
||||||
role='tab'
|
role='tab'
|
||||||
className='tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
className='tw-tab [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
||||||
aria-label='Relations'
|
aria-label='Links'
|
||||||
checked={activeTab === 7 && true}
|
checked={activeTab === 7 && true}
|
||||||
onChange={() => updateActiveTab(7)}
|
onChange={() => updateActiveTab(7)}
|
||||||
/>
|
/>
|
||||||
@ -196,7 +197,7 @@ export const TabsForm = ({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||||
<TextView truncate item={i} />
|
<TextView truncate itemId={item.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -207,7 +208,6 @@ export const TabsForm = ({
|
|||||||
item={item}
|
item={item}
|
||||||
existingRelations={state.relations}
|
existingRelations={state.relations}
|
||||||
triggerItemSelected={(id) => linkItem(id, item, updateItem)}
|
triggerItemSelected={(id) => linkItem(id, item, updateItem)}
|
||||||
colorField={item.layer.itemColorField}
|
|
||||||
></ActionButton>
|
></ActionButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -108,9 +108,9 @@ export const TabsView = ({
|
|||||||
<StartEndView item={item}></StartEndView>
|
<StartEndView item={item}></StartEndView>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<TextView item={item} />
|
<TextView text={item.text} itemId={item.id} />
|
||||||
<div className='tw-h-4'></div>
|
<div className='tw-h-4'></div>
|
||||||
<TextView item={item} itemTextField='contact' />
|
<TextView text={item.contact} itemId={item.id} />
|
||||||
</div>
|
</div>
|
||||||
{item.layer?.itemType.questlog && (
|
{item.layer?.itemType.questlog && (
|
||||||
<>
|
<>
|
||||||
@ -121,7 +121,7 @@ export const TabsView = ({
|
|||||||
className={
|
className={
|
||||||
'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
'tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
||||||
}
|
}
|
||||||
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Credibility'}`}
|
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 2 ? '❤️' : '❤️\u00A0Trust'}`}
|
||||||
checked={activeTab === 2 && true}
|
checked={activeTab === 2 && true}
|
||||||
onChange={() => updateActiveTab(2)}
|
onChange={() => updateActiveTab(2)}
|
||||||
/>
|
/>
|
||||||
@ -161,7 +161,7 @@ export const TabsView = ({
|
|||||||
appState.assetsApi.url +
|
appState.assetsApi.url +
|
||||||
getUserProfile(a.user_created.id)?.image
|
getUserProfile(a.user_created.id)?.image
|
||||||
}
|
}
|
||||||
alt='Avatar Tailwind CSS Component'
|
alt='Avatar'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -243,7 +243,7 @@ export const TabsView = ({
|
|||||||
name='my_tabs_2'
|
name='my_tabs_2'
|
||||||
role='tab'
|
role='tab'
|
||||||
className='tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
className='tw-tab tw-font-bold !tw-ps-2 !tw-pe-2 [--tab-border-color:var(--fallback-bc,oklch(var(--bc)/0.2))]'
|
||||||
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 7 ? '🔗' : '🔗\u00A0Relations'}`}
|
aria-label={`${item.layer.itemType.icon_as_labels && activeTab !== 7 ? '🔗' : '🔗\u00A0Links'}`}
|
||||||
checked={activeTab === 7 && true}
|
checked={activeTab === 7 && true}
|
||||||
onChange={() => updateActiveTab(7)}
|
onChange={() => updateActiveTab(7)}
|
||||||
/>
|
/>
|
||||||
@ -267,7 +267,7 @@ export const TabsView = ({
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
/>
|
/>
|
||||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||||
<TextView truncate item={i} />
|
<TextView truncate text={i.text} itemId={item.id} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
@ -277,7 +277,6 @@ export const TabsView = ({
|
|||||||
item={item}
|
item={item}
|
||||||
existingRelations={relations}
|
existingRelations={relations}
|
||||||
triggerItemSelected={linkItem}
|
triggerItemSelected={linkItem}
|
||||||
colorField={item.layer.itemColorField}
|
|
||||||
></ActionButton>
|
></ActionButton>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -46,7 +46,6 @@ const DialogModal = ({
|
|||||||
<dialog
|
<dialog
|
||||||
className={`${className ?? ''} tw-card tw-shadow-xl tw-absolute tw-right-0 tw-top-0 tw-bottom-0 tw-left-0 tw-m-auto tw-transition-opacity tw-duration-300 tw-p-4 tw-max-w-xl tw-bg-base-100`}
|
className={`${className ?? ''} tw-card tw-shadow-xl tw-absolute tw-right-0 tw-top-0 tw-bottom-0 tw-left-0 tw-m-auto tw-transition-opacity tw-duration-300 tw-p-4 tw-max-w-xl tw-bg-base-100`}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
// eslint-disable-next-line react/no-unknown-property
|
|
||||||
onCancel={onClose}
|
onCancel={onClose}
|
||||||
onClick={(e) =>
|
onClick={(e) =>
|
||||||
ref.current && !isClickInsideRectangle(e, ref.current) && closeOnClickOutside && onClose()
|
ref.current && !isClickInsideRectangle(e, ref.current) && closeOnClickOutside && onClose()
|
||||||
|
|||||||
@ -2,14 +2,12 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
||||||
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
||||||
/* eslint-disable @typescript-eslint/restrict-plus-operands */
|
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
|
|
||||||
import { StartEndView, TextView } from '#components/Map'
|
import { StartEndView, TextView } from '#components/Map'
|
||||||
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
import useWindowDimensions from '#components/Map/hooks/useWindowDimension'
|
||||||
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
import { HeaderView } from '#components/Map/Subcomponents/ItemPopupComponents/HeaderView'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import { DateUserInfo } from './DateUserInfo'
|
import { DateUserInfo } from './DateUserInfo'
|
||||||
|
|
||||||
@ -19,13 +17,11 @@ export const ItemCard = ({
|
|||||||
i,
|
i,
|
||||||
loading,
|
loading,
|
||||||
url,
|
url,
|
||||||
parameterField,
|
|
||||||
deleteCallback,
|
deleteCallback,
|
||||||
}: {
|
}: {
|
||||||
i: Item
|
i: Item
|
||||||
loading: boolean
|
loading: boolean
|
||||||
url: string
|
url: string
|
||||||
parameterField: string
|
|
||||||
deleteCallback: any
|
deleteCallback: any
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
@ -35,27 +31,23 @@ export const ItemCard = ({
|
|||||||
<div
|
<div
|
||||||
className='tw-cursor-pointer tw-card tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-bg-base-100 tw-text-base-content tw-p-4 tw-mb-4 tw-h-fit'
|
className='tw-cursor-pointer tw-card tw-border-[1px] tw-border-base-300 tw-card-body tw-shadow-xl tw-bg-base-100 tw-text-base-content tw-p-4 tw-mb-4 tw-h-fit'
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
|
// We could have an onClick callback instead
|
||||||
const params = new URLSearchParams(window.location.search)
|
const params = new URLSearchParams(window.location.search)
|
||||||
if (windowDimensions.width < 786 && i.position)
|
if (windowDimensions.width < 786 && i.position)
|
||||||
navigate('/' + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
|
navigate('/' + i.id + `${params ? `?${params}` : ''}`)
|
||||||
else navigate(url + getValue(i, parameterField) + `${params ? `?${params}` : ''}`)
|
else navigate(url + i.id + `${params ? `?${params}` : ''}`)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<HeaderView
|
<HeaderView
|
||||||
loading={loading}
|
loading={loading}
|
||||||
item={i}
|
item={i}
|
||||||
api={i.layer?.api}
|
api={i.layer?.api}
|
||||||
itemAvatarField={i.layer?.itemAvatarField}
|
|
||||||
itemNameField={i.layer?.itemNameField}
|
|
||||||
itemSubnameField={i.layer?.itemSubnameField}
|
|
||||||
editCallback={() => navigate('/edit-item/' + i.id)}
|
editCallback={() => navigate('/edit-item/' + i.id)}
|
||||||
deleteCallback={() => deleteCallback(i)}
|
deleteCallback={() => deleteCallback(i)}
|
||||||
></HeaderView>
|
></HeaderView>
|
||||||
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
<div className='tw-overflow-y-auto tw-overflow-x-hidden tw-max-h-64 fade'>
|
||||||
{i.layer?.itemType.show_start_end && <StartEndView item={i}></StartEndView>}
|
{i.layer?.itemType.show_start_end && <StartEndView item={i}></StartEndView>}
|
||||||
{i.layer?.itemType.show_text && (
|
{i.layer?.itemType.show_text && <TextView truncate text={i.text} itemId={i.id} />}
|
||||||
<TextView truncate item={i} itemTextField={i.layer.itemTextField} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
<DateUserInfo item={i}></DateUserInfo>
|
<DateUserInfo item={i}></DateUserInfo>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import { useNavigate } from 'react-router-dom'
|
|||||||
|
|
||||||
import { useItems } from '#components/Map/hooks/useItems'
|
import { useItems } from '#components/Map/hooks/useItems'
|
||||||
import { useTags } from '#components/Map/hooks/useTags'
|
import { useTags } from '#components/Map/hooks/useTags'
|
||||||
import { getValue } from '#utils/GetValue'
|
|
||||||
|
|
||||||
import { MapOverlayPage } from './MapOverlayPage'
|
import { MapOverlayPage } from './MapOverlayPage'
|
||||||
import { TagView } from './TagView'
|
import { TagView } from './TagView'
|
||||||
@ -42,21 +41,16 @@ export const MarketView = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOffers([])
|
setOffers([])
|
||||||
setNeeds([])
|
setNeeds([])
|
||||||
items.map((i) => {
|
for (const item of items) {
|
||||||
i.layer?.itemOffersField &&
|
item.offers?.forEach((o) => {
|
||||||
getValue(i, i.layer.itemOffersField)?.map((o) => {
|
|
||||||
const tag = tags.find((t) => t.id === o.tags_id)
|
const tag = tags.find((t) => t.id === o.tags_id)
|
||||||
tag && setOffers((current) => [...current, tag])
|
tag && setOffers((current) => [...current, tag])
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
i.layer?.itemNeedsField &&
|
item.needs?.forEach((n) => {
|
||||||
getValue(i, i.layer.itemNeedsField)?.map((n) => {
|
|
||||||
const tag = tags.find((t) => t.id === n.tags_id)
|
const tag = tags.find((t) => t.id === n.tags_id)
|
||||||
tag && setNeeds((current) => [...current, tag])
|
tag && setNeeds((current) => [...current, tag])
|
||||||
return null
|
|
||||||
})
|
|
||||||
return null
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.log(offers)
|
console.log(offers)
|
||||||
|
|
||||||
|
|||||||
@ -30,12 +30,10 @@ import type { Item } from '#types/Item'
|
|||||||
export const OverlayItemsIndexPage = ({
|
export const OverlayItemsIndexPage = ({
|
||||||
url,
|
url,
|
||||||
layerName,
|
layerName,
|
||||||
parameterField,
|
|
||||||
plusButton = true,
|
plusButton = true,
|
||||||
}: {
|
}: {
|
||||||
layerName: string
|
layerName: string
|
||||||
url: string
|
url: string
|
||||||
parameterField: string
|
|
||||||
plusButton?: boolean
|
plusButton?: boolean
|
||||||
}) => {
|
}) => {
|
||||||
const [loading, setLoading] = useState<boolean>(false)
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
@ -165,7 +163,6 @@ export const OverlayItemsIndexPage = ({
|
|||||||
i={i}
|
i={i}
|
||||||
loading={loading}
|
loading={loading}
|
||||||
url={url}
|
url={url}
|
||||||
parameterField={parameterField}
|
|
||||||
deleteCallback={() => deleteItem(i)}
|
deleteCallback={() => deleteItem(i)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
4
src/Utils/ContainsUUID.ts
Normal file
4
src/Utils/ContainsUUID.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export function containsUUID(str: string): boolean {
|
||||||
|
const uuidRegex = /[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}/i
|
||||||
|
return uuidRegex.test(str)
|
||||||
|
}
|
||||||
@ -1,14 +0,0 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
||||||
export function getValue(obj, path) {
|
|
||||||
if (!obj || typeof path !== 'string') return undefined
|
|
||||||
|
|
||||||
const pathArray = path.split('.') // Use a different variable for the split path
|
|
||||||
for (let i = 0, len = pathArray.length; i < len; i++) {
|
|
||||||
if (!obj) return undefined // Check if obj is falsy at each step
|
|
||||||
// eslint-disable-next-line security/detect-object-injection
|
|
||||||
obj = obj[pathArray[i]] // Dive one level deeper
|
|
||||||
}
|
|
||||||
return obj // Return the final value
|
|
||||||
}
|
|
||||||
@ -61,6 +61,16 @@ const addIcon = (icon: string) => {
|
|||||||
return '<svg class="flower-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M210.35,129.36c-.81-.47-1.7-.92-2.62-1.36.92-.44,1.81-.89,2.62-1.36a40,40,0,1,0-40-69.28c-.81.47-1.65,1-2.48,1.59.08-1,.13-2,.13-3a40,40,0,0,0-80,0c0,.94,0,1.94.13,3-.83-.57-1.67-1.12-2.48-1.59a40,40,0,1,0-40,69.28c.81.47,1.7.92,2.62,1.36-.92.44-1.81.89-2.62,1.36a40,40,0,1,0,40,69.28c.81-.47,1.65-1,2.48-1.59-.08,1-.13,2-.13,2.95a40,40,0,0,0,80,0c0-.94-.05-1.94-.13-2.95.83.57,1.67,1.12,2.48,1.59A39.79,39.79,0,0,0,190.29,204a40.43,40.43,0,0,0,10.42-1.38,40,40,0,0,0,9.64-73.28ZM104,128a24,24,0,1,1,24,24A24,24,0,0,1,104,128Zm74.35-56.79a24,24,0,1,1,24,41.57c-6.27,3.63-18.61,6.13-35.16,7.19A40,40,0,0,0,154.53,98.1C163.73,84.28,172.08,74.84,178.35,71.21ZM128,32a24,24,0,0,1,24,24c0,7.24-4,19.19-11.36,34.06a39.81,39.81,0,0,0-25.28,0C108,75.19,104,63.24,104,56A24,24,0,0,1,128,32ZM44.86,80a24,24,0,0,1,32.79-8.79c6.27,3.63,14.62,13.07,23.82,26.89A40,40,0,0,0,88.81,120c-16.55-1.06-28.89-3.56-35.16-7.18A24,24,0,0,1,44.86,80ZM77.65,184.79a24,24,0,1,1-24-41.57c6.27-3.63,18.61-6.13,35.16-7.19a40,40,0,0,0,12.66,21.87C92.27,171.72,83.92,181.16,77.65,184.79ZM128,224a24,24,0,0,1-24-24c0-7.24,4-19.19,11.36-34.06a39.81,39.81,0,0,0,25.28,0C148,180.81,152,192.76,152,200A24,24,0,0,1,128,224Zm83.14-48a24,24,0,0,1-32.79,8.79c-6.27-3.63-14.62-13.07-23.82-26.89A40,40,0,0,0,167.19,136c16.55,1.06,28.89,3.56,35.16,7.18A24,24,0,0,1,211.14,176Z"></path></svg>'
|
return '<svg class="flower-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M210.35,129.36c-.81-.47-1.7-.92-2.62-1.36.92-.44,1.81-.89,2.62-1.36a40,40,0,1,0-40-69.28c-.81.47-1.65,1-2.48,1.59.08-1,.13-2,.13-3a40,40,0,0,0-80,0c0,.94,0,1.94.13,3-.83-.57-1.67-1.12-2.48-1.59a40,40,0,1,0-40,69.28c.81.47,1.7.92,2.62,1.36-.92.44-1.81.89-2.62,1.36a40,40,0,1,0,40,69.28c.81-.47,1.65-1,2.48-1.59-.08,1-.13,2-.13,2.95a40,40,0,0,0,80,0c0-.94-.05-1.94-.13-2.95.83.57,1.67,1.12,2.48,1.59A39.79,39.79,0,0,0,190.29,204a40.43,40.43,0,0,0,10.42-1.38,40,40,0,0,0,9.64-73.28ZM104,128a24,24,0,1,1,24,24A24,24,0,0,1,104,128Zm74.35-56.79a24,24,0,1,1,24,41.57c-6.27,3.63-18.61,6.13-35.16,7.19A40,40,0,0,0,154.53,98.1C163.73,84.28,172.08,74.84,178.35,71.21ZM128,32a24,24,0,0,1,24,24c0,7.24-4,19.19-11.36,34.06a39.81,39.81,0,0,0-25.28,0C108,75.19,104,63.24,104,56A24,24,0,0,1,128,32ZM44.86,80a24,24,0,0,1,32.79-8.79c6.27,3.63,14.62,13.07,23.82,26.89A40,40,0,0,0,88.81,120c-16.55-1.06-28.89-3.56-35.16-7.18A24,24,0,0,1,44.86,80ZM77.65,184.79a24,24,0,1,1-24-41.57c6.27-3.63,18.61-6.13,35.16-7.19a40,40,0,0,0,12.66,21.87C92.27,171.72,83.92,181.16,77.65,184.79ZM128,224a24,24,0,0,1-24-24c0-7.24,4-19.19,11.36-34.06a39.81,39.81,0,0,0,25.28,0C148,180.81,152,192.76,152,200A24,24,0,0,1,128,224Zm83.14-48a24,24,0,0,1-32.79,8.79c-6.27-3.63-14.62-13.07-23.82-26.89A40,40,0,0,0,167.19,136c16.55,1.06,28.89,3.56,35.16,7.18A24,24,0,0,1,211.14,176Z"></path></svg>'
|
||||||
case 'network':
|
case 'network':
|
||||||
return '<svg class="network-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M212,200a36,36,0,1,1-69.85-12.25l-53-34.05a36,36,0,1,1,0-51.4l53-34a36.09,36.09,0,1,1,8.67,13.45l-53,34.05a36,36,0,0,1,0,24.5l53,34.05A36,36,0,0,1,212,200Z"></path></svg>'
|
return '<svg class="network-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M212,200a36,36,0,1,1-69.85-12.25l-53-34.05a36,36,0,1,1,0-51.4l53-34a36.09,36.09,0,1,1,8.67,13.45l-53,34.05a36,36,0,0,1,0,24.5l53,34.05A36,36,0,0,1,212,200Z"></path></svg>'
|
||||||
|
case 'crosshair':
|
||||||
|
return '<svg class="network-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 512 512" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M256 0c17.7 0 32 14.3 32 32l0 10.4c93.7 13.9 167.7 88 181.6 181.6l10.4 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-10.4 0c-13.9 93.7-88 167.7-181.6 181.6l0 10.4c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-10.4C130.3 455.7 56.3 381.7 42.4 288L32 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l10.4 0C56.3 130.3 130.3 56.3 224 42.4L224 32c0-17.7 14.3-32 32-32zM107.4 288c12.5 58.3 58.4 104.1 116.6 116.6l0-20.6c0-17.7 14.3-32 32-32s32 14.3 32 32l0 20.6c58.3-12.5 104.1-58.4 116.6-116.6L384 288c-17.7 0-32-14.3-32-32s14.3-32 32-32l20.6 0C392.1 165.7 346.3 119.9 288 107.4l0 20.6c0 17.7-14.3 32-32 32s-32-14.3-32-32l0-20.6C165.7 119.9 119.9 165.7 107.4 224l20.6 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-20.6 0zM256 224a32 32 0 1 1 0 64 32 32 0 1 1 0-64z"></path></svg>'
|
||||||
|
case 'shop':
|
||||||
|
return '<svg class="shop-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 640 512" height="1.25em" width="1.25em" xmlns="http://www.w3.org/2000/svg"><path d="M36.8 192l566.3 0c20.3 0 36.8-16.5 36.8-36.8c0-7.3-2.2-14.4-6.2-20.4L558.2 21.4C549.3 8 534.4 0 518.3 0L121.7 0c-16 0-31 8-39.9 21.4L6.2 134.7c-4 6.1-6.2 13.2-6.2 20.4C0 175.5 16.5 192 36.8 192zM64 224l0 160 0 80c0 26.5 21.5 48 48 48l224 0c26.5 0 48-21.5 48-48l0-80 0-160-64 0 0 160-192 0 0-160-64 0zm448 0l0 256c0 17.7 14.3 32 32 32s32-14.3 32-32l0-256-64 0z"></path></svg>'
|
||||||
|
case 'plant':
|
||||||
|
return '<svg class="plant-icon" stroke="currentColor" stroke-width="0" fill="#fff" viewBox="0 0 256 256" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M205.41,159.07a60.9,60.9,0,0,1-31.83,8.86,71.71,71.71,0,0,1-27.36-5.66A55.55,55.55,0,0,0,136,194.51V224a8,8,0,0,1-8.53,8,8.18,8.18,0,0,1-7.47-8.25V211.31L81.38,172.69A52.5,52.5,0,0,1,63.44,176a45.82,45.82,0,0,1-23.92-6.67C17.73,156.09,6,125.62,8.27,87.79a8,8,0,0,1,7.52-7.52c37.83-2.23,68.3,9.46,81.5,31.25A46,46,0,0,1,103.74,140a4,4,0,0,1-6.89,2.43l-19.2-20.1a8,8,0,0,0-11.31,11.31l53.88,55.25c.06-.78.13-1.56.21-2.33a68.56,68.56,0,0,1,18.64-39.46l50.59-53.46a8,8,0,0,0-11.31-11.32l-49,51.82a4,4,0,0,1-6.78-1.74c-4.74-17.48-2.65-34.88,6.4-49.82,17.86-29.48,59.42-45.26,111.18-42.22a8,8,0,0,1,7.52,7.52C250.67,99.65,234.89,141.21,205.41,159.07Z"></path></svg>'
|
||||||
|
case 'circle-dot':
|
||||||
|
return '<svg class="circle-dot-icon" stroke="#fff" fill="transparent" stroke-width="2.5" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" height="1.55em" width="1.55em" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"></circle><circle cx="12" cy="12" r="1"></circle></svg>'
|
||||||
|
case 'cannabis':
|
||||||
|
return '<svg class="network-icon" stroke="currentColor" fill="#fff" stroke-width="0" viewBox="0 0 512 512" height="1.5em" width="1.5em" xmlns="http://www.w3.org/2000/svg"><path d="M256 0c5.3 0 10.3 2.7 13.3 7.1c15.8 23.5 36.7 63.7 49.2 109c7.2 26.4 11.8 55.2 10.4 84c11.5-8.8 23.7-16.7 35.8-23.6c41-23.3 84.4-36.9 112.2-42.5c5.2-1 10.7 .6 14.4 4.4s5.4 9.2 4.4 14.5c-5.6 27.7-19.3 70.9-42.7 111.7c-9.1 15.9-19.9 31.7-32.4 46.3c27.8 6.6 52.4 17.3 67.2 25.5c5.1 2.8 8.2 8.2 8.2 14s-3.2 11.2-8.2 14c-15.2 8.4-40.9 19.5-69.8 26.1c-20.2 4.6-42.9 7.2-65.2 4.6l8.3 33.1c1.5 6.1-.6 12.4-5.5 16.4s-11.6 4.6-17.2 1.9L280 417.2l0 70.8c0 13.3-10.7 24-24 24s-24-10.7-24-24l0-70.8-58.5 29.1c-5.6 2.8-12.3 2.1-17.2-1.9s-7-10.3-5.5-16.4l8.3-33.1c-22.2 2.6-45 0-65.2-4.6c-28.9-6.6-54.6-17.6-69.8-26.1c-5.1-2.8-8.2-8.2-8.2-14s3.2-11.2 8.2-14c14.8-8.2 39.4-18.8 67.2-25.5C78.9 296.3 68.1 280.5 59 264.6c-23.4-40.8-37.1-84-42.7-111.7c-1.1-5.2 .6-10.7 4.4-14.5s9.2-5.4 14.4-4.4c27.9 5.5 71.2 19.2 112.2 42.5c12.1 6.9 24.3 14.7 35.8 23.6c-1.4-28.7 3.1-57.6 10.4-84c12.5-45.3 33.4-85.5 49.2-109c3-4.4 8-7.1 13.3-7.1z"></path></svg>'
|
||||||
default:
|
default:
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// eslint-disable-next-line import/no-unassigned-import
|
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@ -19,7 +18,6 @@ export {
|
|||||||
export { AppShell, Content, SideBar, Sitemap } from './Components/AppShell'
|
export { AppShell, Content, SideBar, Sitemap } from './Components/AppShell'
|
||||||
export {
|
export {
|
||||||
AuthProvider,
|
AuthProvider,
|
||||||
useAuth,
|
|
||||||
LoginPage,
|
LoginPage,
|
||||||
SignupPage,
|
SignupPage,
|
||||||
RequestPasswordPage,
|
RequestPasswordPage,
|
||||||
@ -39,6 +37,8 @@ export {
|
|||||||
} from './Components/Templates'
|
} from './Components/Templates'
|
||||||
export { TextInput, TextAreaInput, SelectBox } from './Components/Input'
|
export { TextInput, TextAreaInput, SelectBox } from './Components/Input'
|
||||||
|
|
||||||
|
export * from './types'
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
my_modal_3: {
|
my_modal_3: {
|
||||||
|
|||||||
@ -17,4 +17,6 @@ export interface FormState {
|
|||||||
offers: Tag[]
|
offers: Tag[]
|
||||||
needs: Tag[]
|
needs: Tag[]
|
||||||
relations: Item[]
|
relations: Item[]
|
||||||
|
start: string
|
||||||
|
end: string
|
||||||
}
|
}
|
||||||
34
types/Item.d.ts → src/types/Item.d.ts
vendored
34
types/Item.d.ts → src/types/Item.d.ts
vendored
@ -1,14 +1,26 @@
|
|||||||
import type { ItemsApi } from './ItemsApi'
|
import type { ItemsApi } from './ItemsApi'
|
||||||
|
import type { ItemType } from './ItemType'
|
||||||
import type { LayerProps } from './LayerProps'
|
import type { LayerProps } from './LayerProps'
|
||||||
import type { Relation } from './Relation'
|
import type { Relation } from './Relation'
|
||||||
import type { UserItem } from './UserItem'
|
import type { UserItem } from './UserItem'
|
||||||
import type { Point } from 'geojson'
|
import type { Point } from 'geojson'
|
||||||
|
|
||||||
|
type TagIds = { tags_id: string }[]
|
||||||
|
|
||||||
|
interface GalleryItem {
|
||||||
|
directus_files_id: {
|
||||||
|
id: number
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface Item {
|
export interface Item {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
text: string
|
text?: string
|
||||||
position?: Point
|
data?: string
|
||||||
|
position?: Point | null
|
||||||
date_created?: string
|
date_created?: string
|
||||||
date_updated?: string | null
|
date_updated?: string | null
|
||||||
start?: string
|
start?: string
|
||||||
@ -24,8 +36,22 @@ export interface Item {
|
|||||||
slug?: string
|
slug?: string
|
||||||
user_created?: UserItem
|
user_created?: UserItem
|
||||||
image?: string
|
image?: string
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
group_type?: string
|
||||||
[key: string]: any
|
offers?: TagIds
|
||||||
|
needs?: TagIds
|
||||||
|
status?: string
|
||||||
|
color?: string
|
||||||
|
markerIcon?: string
|
||||||
|
avatar?: string
|
||||||
|
new?: boolean
|
||||||
|
contact?: string
|
||||||
|
telephone?: string
|
||||||
|
next_appointment?: string
|
||||||
|
type?: ItemType
|
||||||
|
gallery?: GalleryItem[]
|
||||||
|
|
||||||
|
// {
|
||||||
|
// coordinates: [number, number]
|
||||||
/* constructor(
|
/* constructor(
|
||||||
id: string,
|
id: string,
|
||||||
name: string,
|
name: string,
|
||||||
15
src/types/ItemType.d.ts
vendored
Normal file
15
src/types/ItemType.d.ts
vendored
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import type { Key } from 'react'
|
||||||
|
|
||||||
|
export interface ItemType {
|
||||||
|
name: string
|
||||||
|
show_start_end: boolean
|
||||||
|
show_text: boolean
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
profileTemplate: { collection: string | number; id: Key | null | undefined; item: any }[]
|
||||||
|
offers_and_needs: boolean
|
||||||
|
icon_as_labels: unknown
|
||||||
|
relations: boolean
|
||||||
|
template: string
|
||||||
|
show_start_end_input: boolean
|
||||||
|
questlog: boolean
|
||||||
|
}
|
||||||
@ -2,7 +2,7 @@ export interface ItemsApi<T> {
|
|||||||
getItems(): Promise<T[]>
|
getItems(): Promise<T[]>
|
||||||
getItem?(id: string): Promise<T>
|
getItem?(id: string): Promise<T>
|
||||||
createItem?(item: T): Promise<T>
|
createItem?(item: T): Promise<T>
|
||||||
updateItem?(item: T): Promise<T>
|
updateItem?(item: Partial<T>): Promise<T>
|
||||||
deleteItem?(id: string): Promise<boolean>
|
deleteItem?(id: string): Promise<boolean>
|
||||||
collectionName?: string
|
collectionName?: string
|
||||||
}
|
}
|
||||||
@ -18,17 +18,6 @@ export interface LayerProps {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
api?: ItemsApi<any>
|
api?: ItemsApi<any>
|
||||||
itemType: ItemType
|
itemType: ItemType
|
||||||
itemNameField?: string
|
|
||||||
itemSubnameField?: string
|
|
||||||
itemTextField?: string
|
|
||||||
itemAvatarField?: string
|
|
||||||
itemColorField?: string
|
|
||||||
itemOwnerField?: string
|
|
||||||
itemTagsField?: string
|
|
||||||
itemLatitudeField?: string
|
|
||||||
itemLongitudeField?: string
|
|
||||||
itemOffersField?: string
|
|
||||||
itemNeedsField?: string
|
|
||||||
onlyOnePerOwner?: boolean
|
onlyOnePerOwner?: boolean
|
||||||
customEditLink?: string
|
customEditLink?: string
|
||||||
customEditParameter?: string
|
customEditParameter?: string
|
||||||
0
types/Tag.d.ts → src/types/Tag.d.ts
vendored
0
types/Tag.d.ts → src/types/Tag.d.ts
vendored
@ -13,4 +13,5 @@ export interface UtopiaMapProps {
|
|||||||
showLayerControl?: boolean
|
showLayerControl?: boolean
|
||||||
showGratitudeControl?: boolean
|
showGratitudeControl?: boolean
|
||||||
infoText?: string
|
infoText?: string
|
||||||
|
donationWidget?: boolean
|
||||||
}
|
}
|
||||||
9
src/types/index.ts
Normal file
9
src/types/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
export type { ItemsApi } from './ItemsApi'
|
||||||
|
export type { Tag } from './Tag'
|
||||||
|
export type { Item } from './Item'
|
||||||
|
export type { Permission } from './Permission'
|
||||||
|
export type { LayerProps } from './LayerProps'
|
||||||
|
export type { UserApi } from './UserApi'
|
||||||
|
export type { UserItem } from './UserItem'
|
||||||
|
export type { UtopiaMapProps } from './UtopiaMapProps'
|
||||||
|
export type { AssetsApi } from './AssetsApi'
|
||||||
@ -1,14 +1,15 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "dist",
|
"outDir": "dist",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "dist/types", // 🔹 Muss innerhalb von dist/ liegen
|
||||||
|
"emitDeclarationOnly": true, // Nur `.d.ts` generieren, kein JavaScript
|
||||||
"module": "esnext",
|
"module": "esnext",
|
||||||
"target": "es5",
|
"target": "ESNext",
|
||||||
"lib": ["es6", "dom","es2015", "es2016", "es2017", "es2020"],
|
"lib": ["es6", "dom","es2015", "es2016", "es2017", "es2020"],
|
||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"declaration": true,
|
|
||||||
"declarationDir": "./types",
|
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
@ -16,18 +17,20 @@
|
|||||||
"strictNullChecks": true,
|
"strictNullChecks": true,
|
||||||
"noUnusedLocals": false,
|
"noUnusedLocals": false,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"#components/*": ["./src/Components/*"],
|
"#components/*": ["./src/Components/*"],
|
||||||
"#utils/*": ["./src/Utils/*"],
|
"#utils/*": ["./src/Utils/*"],
|
||||||
"#src/*": ["./src/*"],
|
"#src/*": ["./src/*"],
|
||||||
"#types/*": ["./types/*"],
|
"#types/*": ["./src/types/*"],
|
||||||
"#root/*": ["./*"]
|
"#root/*": ["./*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src"],
|
"include": ["src", "vite.config.ts", "setupTest.ts", "cypress.config.ts", "cypress/support/commands.ts", "cypress/support/component.ts"],
|
||||||
"exclude": ["node_modules", "dist", "example", "rollup.config.mjss"],
|
"exclude": ["node_modules", "dist", "example", "rollup.config.mjss"],
|
||||||
"typeRoots": [
|
"typeRoots": [
|
||||||
"./types",
|
"./types",
|
||||||
"./node_modules/@types/"
|
"./node_modules/@types/"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
5
types/ItemType.d.ts
vendored
5
types/ItemType.d.ts
vendored
@ -1,5 +0,0 @@
|
|||||||
export interface ItemType {
|
|
||||||
name: string
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
||||||
[key: string]: any
|
|
||||||
}
|
|
||||||
23
vite.config.ts
Normal file
23
vite.config.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import react from '@vitejs/plugin-react'
|
||||||
|
import { defineConfig } from 'vite'
|
||||||
|
import { configDefaults } from 'vitest/config'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react()],
|
||||||
|
test: {
|
||||||
|
globals: true,
|
||||||
|
environment: 'happy-dom',
|
||||||
|
setupFiles: ['setupTest.ts'],
|
||||||
|
coverage: {
|
||||||
|
all: true,
|
||||||
|
include: ['src/**/*.{js,jsx,ts,tsx}'],
|
||||||
|
exclude: [...configDefaults.exclude],
|
||||||
|
thresholds: {
|
||||||
|
lines: 0,
|
||||||
|
functions: 66,
|
||||||
|
branches: 66,
|
||||||
|
statements: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user