Merge pull request #245 from utopia-os/mono-repo

chore(other): mono repo
This commit is contained in:
Ulf Gebhardt 2025-06-13 09:14:43 +02:00 committed by GitHub
commit 18d159a504
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
355 changed files with 28414 additions and 96 deletions

View File

@ -1,4 +1,4 @@
name: deploy:docs
name: deploy:docs:lib
on:
push:
@ -14,36 +14,37 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Install Dependencies & Build Library
run: |
npm install
npm run build
working-directory: ./
working-directory: lib/
- name: Build static files
id: build
run: npm run docs:generate
working-directory: ./
working-directory: lib/
- name: Generate coverage
id: coverage
run: npm run test:unit
working-directory: lib/
- name: Create coverage badges
id: coverage-badge
uses: jaywcjlove/coverage-badges-cli@main
with:
style: flat
source: coverage/coverage-summary.json
output: docs/test-coverage.svg
source: lib/coverage/coverage-summary.json
output: lib/docs/test-coverage.svg
jsonPath: total.lines.pct
- name: Upload static files as artifact
id: deployment
uses: actions/upload-pages-artifact@v3.0.1
with:
path: docs/
path: lib/docs/
# Deploy job
deploy:

View File

@ -1,10 +1,10 @@
name: build
name: build:lib
on: push
jobs:
files-changed:
name: Detect File Changes - build
name: Detect File Changes - build -lib
runs-on: ubuntu-latest
outputs:
build: ${{ steps.filter.outputs.build }}
@ -16,7 +16,7 @@ jobs:
filters: |
build:
- '.github/workflows/**/*'
- '**/*'
- 'lib/**/*'
build:
if: needs.files-changed.outputs.build == 'true'
@ -27,12 +27,12 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Install Dependencies & Build Library
run: |
npm install
npm run build
working-directory: ./
working-directory: lib/
build-examples:
if: needs.files-changed.outputs.build == 'true'
@ -47,7 +47,7 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Link Utopia-UI in Example App
run: |
npm install
@ -55,3 +55,4 @@ jobs:
cd ${{ matrix.app }}
npm install
npm run build
working-directory: lib/

View File

@ -1,10 +1,10 @@
name: test:docs
name: test:docs:lib
on: push
jobs:
files-changed:
name: Detect File Changes - docs
name: Detect File Changes - docs - lib
runs-on: ubuntu-latest
outputs:
docs: ${{ steps.filter.outputs.docs }}
@ -16,7 +16,7 @@ jobs:
filters: |
docs:
- '.github/workflows/**/*'
- '**/*.md'
- 'lib/**/*.md'
- 'LICENSE'
# build:
@ -35,7 +35,7 @@ jobs:
docs:
if: needs.files-changed.outputs.docs == 'true'
name: Docs
name: Docs - lib
needs: files-changed
runs-on: ubuntu-latest
env:
@ -44,10 +44,10 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Docs
run: |
npm install
npm run docs:generate
./scripts/docs-coverage.sh
working-directory: ./
working-directory: lib/

View File

@ -0,0 +1,33 @@
name: test:lint:frontend
on: push
jobs:
files-changed:
name: Detect File Changes - lint - frontend
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/**/*'
- 'frontend/**/*'
lint:
if: needs.files-changed.outputs.lint == 'true'
name: Lint - 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: Lint
run: npm install && npm run test:lint:eslint
working-directory: frontend/

View File

@ -1,4 +1,4 @@
name: test::examples
name: test::examples:lib
on: push
@ -16,7 +16,7 @@ jobs:
filters: |
lint:
- '.github/workflows/**/*'
- '**/*'
- 'lib/**/*'
# build:
# if: needs.files-changed.outputs.frontend == 'true'
@ -41,10 +41,10 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Lint
run: npm install && npm run lint
working-directory: ./examples/1-basic-map
working-directory: lib/examples/1-basic-map
lint-example-2-static-layers:
if: needs.files-changed.outputs.lint == 'true'
@ -55,10 +55,10 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Lint
run: npm install && npm run lint
working-directory: ./examples/2-static-layers
working-directory: lib/examples/2-static-layers
lint-example-3-tags:
if: needs.files-changed.outputs.lint == 'true'
@ -69,10 +69,10 @@ jobs:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.1.7
- uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.0.3
with:
node-version-file: './.tool-versions'
node-version-file: '.tool-versions'
- name: Lint
run: npm install && npm run lint
working-directory: ./examples/3-tags
working-directory: lib/examples/3-tags
# unit:
# if: needs.files-changed.outputs.frontend == 'true'

33
.github/workflows/test.lint.lib.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: test:lint:lib
on: push
jobs:
files-changed:
name: Detect File Changes - lint - lib
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/**/*'
- 'lib/**/*'
lint:
if: needs.files-changed.outputs.lint == 'true'
name: Lint - lib
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 test:lint:eslint
working-directory: lib/

View File

@ -29,11 +29,13 @@ jobs:
# Configure which scopes are allowed (newline delimited).
# Append a scope for each service here
scopes: |
backend
frontend
lib
docu
docker
release
workflow
source
other
# Configure that a scope must always be provided.
requireScope: true
@ -74,4 +76,4 @@ jobs:
# special "[WIP]" prefix to indicate this state. This will avoid the
# validation of the PR title and the pull request checks remain pending.
# Note that a second check will be reported if this is enabled.
wip: true
wip: true

View File

@ -1,61 +0,0 @@
name: test:lint
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:
if: needs.files-changed.outputs.lint == 'true'
name: Lint
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 test:lint:eslint
working-directory: ./
# 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

View File

@ -1,10 +1,10 @@
name: test:unit
name: test:unit:lib
on: push
jobs:
files-changed:
name: Detect File Changes - unit
name: Detect File Changes - unit - lib
runs-on: ubuntu-latest
outputs:
unit: ${{ steps.filter.outputs.unit }}
@ -16,18 +16,18 @@ jobs:
filters: |
unit:
- '.github/workflows/**/*'
- '**/*'
- 'lib/**/*'
unit:
if: needs.files-changed.outputs.unit == 'true'
name: Unit
name: Unit - lib
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'
node-version-file: '.tool-versions'
- name: Unit
run: npm install && npm run test:unit
working-directory: ./
working-directory: lib/

12
backend/Dockerfile Normal file
View File

@ -0,0 +1,12 @@
FROM node:20-alpine as third-party-ext
RUN apk add python3 g++ make
WORKDIR /extensions
ADD extensions .
RUN npm install
# Move all extensions the starts with directus-extension-, using find, to the /extensions/directus folder
RUN mkdir -p ./directus
RUN cd node_modules && find . -maxdepth 1 -type d -name "directus-extension-*" -exec mv {} ../directus \;
FROM directus/directus:11.4.1
# Copy third party extensions
COPY --from=third-party-ext /extensions/directus ./extensions

16
backend/README.md Normal file
View File

@ -0,0 +1,16 @@
In order to pull data from your locally running backend (see [docker-compose](../docker-compose.yml)) to your local harddrive, you can run the following command
```
npx directus-sync pull \
--directus-url http://localhost:8055 \
--directus-email admin@it4c.dev \
--directus-password admin123
```
To push local changes or to seed directus use the following command
```
npx directus-sync push \
--directus-url http://localhost:8055 \
--directus-email admin@it4c.dev \
--directus-password admin123
```

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,34 @@
[
{
"name": "Administrator",
"icon": "verified",
"description": "$t:admin_description",
"ip_access": null,
"enforce_tfa": false,
"admin_access": true,
"app_access": true,
"roles": [
{
"role": "_sync_default_admin_role",
"sort": null
}
],
"_syncId": "_sync_default_admin_policy"
},
{
"name": "$t:public_label",
"icon": "public",
"description": "$t:public_description",
"ip_access": null,
"enforce_tfa": false,
"admin_access": false,
"app_access": false,
"roles": [
{
"role": null,
"sort": 1
}
],
"_syncId": "_sync_default_public_policy"
}
]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,9 @@
[
{
"name": "Administrator",
"icon": "verified",
"description": "$t:admin_description",
"parent": null,
"_syncId": "_sync_default_admin_role"
}
]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1 @@
[]

View File

@ -0,0 +1,7 @@
{
"collection": "directus_sync_id_map",
"meta": null,
"schema": {
"name": "directus_sync_id_map"
}
}

View File

@ -0,0 +1,24 @@
{
"collection": "directus_sync_id_map",
"field": "created_at",
"type": "dateTime",
"meta": null,
"schema": {
"name": "created_at",
"table": "directus_sync_id_map",
"data_type": "datetime",
"default_value": "CURRENT_TIMESTAMP",
"max_length": null,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": true,
"is_unique": false,
"is_indexed": true,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
}

View File

@ -0,0 +1,24 @@
{
"collection": "directus_sync_id_map",
"field": "id",
"type": "integer",
"meta": null,
"schema": {
"name": "id",
"table": "directus_sync_id_map",
"data_type": "integer",
"default_value": null,
"max_length": null,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": false,
"is_indexed": false,
"is_primary_key": true,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": true,
"foreign_key_table": null,
"foreign_key_column": null
}
}

View File

@ -0,0 +1,24 @@
{
"collection": "directus_sync_id_map",
"field": "local_id",
"type": "string",
"meta": null,
"schema": {
"name": "local_id",
"table": "directus_sync_id_map",
"data_type": "varchar",
"default_value": null,
"max_length": 255,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": false,
"is_indexed": false,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
}

View File

@ -0,0 +1,24 @@
{
"collection": "directus_sync_id_map",
"field": "sync_id",
"type": "string",
"meta": null,
"schema": {
"name": "sync_id",
"table": "directus_sync_id_map",
"data_type": "varchar",
"default_value": null,
"max_length": 255,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": false,
"is_indexed": false,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
}

View File

@ -0,0 +1,24 @@
{
"collection": "directus_sync_id_map",
"field": "table",
"type": "string",
"meta": null,
"schema": {
"name": "table",
"table": "directus_sync_id_map",
"data_type": "varchar",
"default_value": null,
"max_length": 255,
"numeric_precision": null,
"numeric_scale": null,
"is_nullable": false,
"is_unique": false,
"is_indexed": false,
"is_primary_key": false,
"is_generated": false,
"generation_expression": null,
"has_auto_increment": false,
"foreign_key_table": null,
"foreign_key_column": null
}
}

View File

@ -0,0 +1,5 @@
{
"version": 1,
"directus": "11.4.1",
"vendor": "sqlite"
}

View File

@ -0,0 +1,973 @@
type Query {
"""There's no data to query."""
_empty: Void
}
type Mutation
type Subscription {
directus_folders_mutated(event: EventEnum): directus_folders_mutated
directus_files_mutated(event: EventEnum): directus_files_mutated
directus_operations_mutated(event: EventEnum): directus_operations_mutated
directus_notifications_mutated(event: EventEnum): directus_notifications_mutated
directus_translations_mutated(event: EventEnum): directus_translations_mutated
directus_shares_mutated(event: EventEnum): directus_shares_mutated
directus_versions_mutated(event: EventEnum): directus_versions_mutated
directus_revisions_mutated(event: EventEnum): directus_revisions_mutated
directus_users_mutated(event: EventEnum): directus_users_mutated
directus_webhooks_mutated(event: EventEnum): directus_webhooks_mutated
directus_settings_mutated(event: EventEnum): directus_settings_mutated
directus_policies_mutated(event: EventEnum): directus_policies_mutated
directus_permissions_mutated(event: EventEnum): directus_permissions_mutated
directus_access_mutated(event: EventEnum): directus_access_mutated
directus_dashboards_mutated(event: EventEnum): directus_dashboards_mutated
directus_flows_mutated(event: EventEnum): directus_flows_mutated
directus_panels_mutated(event: EventEnum): directus_panels_mutated
directus_presets_mutated(event: EventEnum): directus_presets_mutated
directus_roles_mutated(event: EventEnum): directus_roles_mutated
directus_comments_mutated(event: EventEnum): directus_comments_mutated
directus_activity_mutated(event: EventEnum): directus_activity_mutated
}
"""The `Boolean` scalar type represents `true` or `false`."""
scalar Boolean
"""ISO8601 Date values"""
scalar Date
"""BigInt value"""
scalar GraphQLBigInt
"""A Float or a String"""
scalar GraphQLStringOrFloat
"""Hashed string values"""
scalar Hash
"""
The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `"4"`) or integer (such as `4`) input value will be accepted as an ID.
"""
scalar ID
"""
The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
"""
scalar Int
"""
The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).
"""
scalar JSON
"""
The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
"""
scalar String
"""Represents NULL values"""
scalar Void
enum EventEnum {
create
update
delete
}
type count_functions {
count: Int
}
type datetime_functions {
year: Int
month: Int
week: Int
day: Int
weekday: Int
hour: Int
minute: Int
second: Int
}
type directus_access {
id: ID!
role(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_roles
user(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
policy(filter: directus_policies_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_policies
sort: Int
}
type directus_access_mutated {
key: ID!
event: EventEnum
data: directus_access
}
type directus_activity {
id: ID!
action: String!
user(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
timestamp: Date
timestamp_func: datetime_functions
ip: String
user_agent: String
collection: String!
item: String!
origin: String
revisions(filter: directus_revisions_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_revisions]
revisions_func: count_functions
}
type directus_activity_mutated {
key: ID!
event: EventEnum
data: directus_activity
}
type directus_comments {
id: ID!
collection: String!
item: String!
comment: String!
date_created: Date
date_created_func: datetime_functions
date_updated: Date
date_updated_func: datetime_functions
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
user_updated(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
}
type directus_comments_mutated {
key: ID!
event: EventEnum
data: directus_comments
}
type directus_dashboards {
id: ID!
name: String!
icon: String
note: String
date_created: Date
date_created_func: datetime_functions
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
color: String
panels(filter: directus_panels_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_panels]
panels_func: count_functions
}
type directus_dashboards_mutated {
key: ID!
event: EventEnum
data: directus_dashboards
}
type directus_files {
id: ID!
storage: String!
filename_disk: String
filename_download: String!
title: String
type: String
folder(filter: directus_folders_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_folders
uploaded_by(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
created_on: Date
created_on_func: datetime_functions
modified_by(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
modified_on: Date
modified_on_func: datetime_functions
charset: String
filesize: GraphQLBigInt
width: Int
height: Int
duration: Int
embed: String
description: String
location: String
tags: JSON
tags_func: count_functions
metadata: JSON
metadata_func: count_functions
focal_point_x: Int
focal_point_y: Int
tus_id: String
tus_data: JSON
tus_data_func: count_functions
uploaded_on: Date
uploaded_on_func: datetime_functions
}
type directus_files_mutated {
key: ID!
event: EventEnum
data: directus_files
}
type directus_flows {
id: ID!
name: String!
icon: String
color: String
description: String
status: String
trigger: String
accountability: String
options: JSON
options_func: count_functions
operation(filter: directus_operations_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_operations
date_created: Date
date_created_func: datetime_functions
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
operations(filter: directus_operations_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_operations]
operations_func: count_functions
}
type directus_flows_mutated {
key: ID!
event: EventEnum
data: directus_flows
}
type directus_folders {
id: ID!
name: String!
parent(filter: directus_folders_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_folders
}
type directus_folders_mutated {
key: ID!
event: EventEnum
data: directus_folders
}
type directus_notifications {
id: ID!
timestamp: Date
timestamp_func: datetime_functions
status: String
recipient(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
sender(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
subject: String!
message: String
collection: String
item: String
}
type directus_notifications_mutated {
key: ID!
event: EventEnum
data: directus_notifications
}
type directus_operations {
id: ID!
name: String
key: String!
type: String!
position_x: Int!
position_y: Int!
options: JSON
options_func: count_functions
resolve(filter: directus_operations_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_operations
reject(filter: directus_operations_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_operations
flow(filter: directus_flows_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_flows
date_created: Date
date_created_func: datetime_functions
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
}
type directus_operations_mutated {
key: ID!
event: EventEnum
data: directus_operations
}
type directus_panels {
id: ID!
dashboard(filter: directus_dashboards_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_dashboards
name: String
icon: String
color: String
show_header: Boolean!
note: String
type: String!
position_x: Int!
position_y: Int!
width: Int!
height: Int!
options: JSON
options_func: count_functions
date_created: Date
date_created_func: datetime_functions
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
}
type directus_panels_mutated {
key: ID!
event: EventEnum
data: directus_panels
}
type directus_permissions {
id: ID
collection: String!
action: String!
permissions: JSON
permissions_func: count_functions
validation: JSON
validation_func: count_functions
presets: JSON
presets_func: count_functions
fields: [String]
policy(filter: directus_policies_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_policies
}
type directus_permissions_mutated {
key: ID!
event: EventEnum
data: directus_permissions
}
type directus_policies {
id: ID!
name: String!
icon: String
description: String
ip_access: [String]
"""$t:field_options.directus_policies.enforce_tfa"""
enforce_tfa: Boolean!
admin_access: Boolean!
app_access: Boolean!
permissions(filter: directus_permissions_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_permissions]
permissions_func: count_functions
users(filter: directus_access_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_access]
users_func: count_functions
roles(filter: directus_access_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_access]
roles_func: count_functions
}
type directus_policies_mutated {
key: ID!
event: EventEnum
data: directus_policies
}
type directus_presets {
id: ID!
bookmark: String
user(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
role(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_roles
collection: String
search: String
layout: String
layout_query: JSON
layout_query_func: count_functions
layout_options: JSON
layout_options_func: count_functions
refresh_interval: Int
filter: JSON
filter_func: count_functions
icon: String
color: String
}
type directus_presets_mutated {
key: ID!
event: EventEnum
data: directus_presets
}
type directus_revisions {
id: ID!
activity(filter: directus_activity_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_activity
collection: String!
item: String!
data: JSON
data_func: count_functions
delta: JSON
delta_func: count_functions
parent(filter: directus_revisions_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_revisions
version(filter: directus_versions_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_versions
}
type directus_revisions_mutated {
key: ID!
event: EventEnum
data: directus_revisions
}
type directus_roles {
id: ID!
name: String!
icon: String
description: String
parent(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_roles
children(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_roles]
children_func: count_functions
policies(filter: directus_access_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_access]
policies_func: count_functions
users(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_users]
users_func: count_functions
}
type directus_roles_mutated {
key: ID!
event: EventEnum
data: directus_roles
}
type directus_settings {
id: ID!
project_name: String
project_url: String
"""$t:field_options.directus_settings.project_color_note"""
project_color: String
project_logo(filter: directus_files_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_files
public_foreground(filter: directus_files_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_files
public_background(filter: directus_files_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_files
public_note: String
auth_login_attempts: Int
auth_password_policy: String
storage_asset_transform: String
storage_asset_presets: JSON
storage_asset_presets_func: count_functions
custom_css: String
storage_default_folder(filter: directus_folders_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_folders
basemaps: JSON
basemaps_func: count_functions
mapbox_key: String
module_bar: JSON
module_bar_func: count_functions
project_descriptor: String
default_language: String
custom_aspect_ratios: JSON
custom_aspect_ratios_func: count_functions
public_favicon(filter: directus_files_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_files
default_appearance: String
default_theme_light: String
theme_light_overrides: JSON
theme_light_overrides_func: count_functions
default_theme_dark: String
theme_dark_overrides: JSON
theme_dark_overrides_func: count_functions
report_error_url: String
report_bug_url: String
report_feature_url: String
"""$t:fields.directus_settings.public_registration_note"""
public_registration: Boolean!
"""$t:fields.directus_settings.public_registration_verify_email_note"""
public_registration_verify_email: Boolean
public_registration_role(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_roles
"""$t:fields.directus_settings.public_registration_email_filter_note"""
public_registration_email_filter: JSON
public_registration_email_filter_func: count_functions
}
type directus_settings_mutated {
key: ID!
event: EventEnum
data: directus_settings
}
type directus_shares {
id: ID!
name: String
collection: String!
item: String!
role(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_roles
"""$t:shared_leave_blank_for_passwordless_access"""
password: Hash
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
date_created: Date
date_created_func: datetime_functions
"""$t:shared_leave_blank_for_unlimited"""
date_start: Date
date_start_func: datetime_functions
"""$t:shared_leave_blank_for_unlimited"""
date_end: Date
date_end_func: datetime_functions
times_used: Int
"""$t:shared_leave_blank_for_unlimited"""
max_uses: Int
}
type directus_shares_mutated {
key: ID!
event: EventEnum
data: directus_shares
}
type directus_translations {
id: ID!
language: String!
key: String!
value: String!
}
type directus_translations_mutated {
key: ID!
event: EventEnum
data: directus_translations
}
type directus_users {
id: ID!
first_name: String
last_name: String
email: String
password: Hash
location: String
title: String
description: String
tags: JSON
tags_func: count_functions
avatar(filter: directus_files_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_files
language: String
tfa_secret: Hash
status: String
role(filter: directus_roles_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_roles
token: Hash
last_access: Date
last_access_func: datetime_functions
last_page: String
provider: String
external_identifier: String
auth_data: JSON
auth_data_func: count_functions
email_notifications: Boolean
appearance: String
theme_dark: String
theme_light: String
theme_light_overrides: JSON
theme_light_overrides_func: count_functions
theme_dark_overrides: JSON
theme_dark_overrides_func: count_functions
policies(filter: directus_access_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): [directus_access]
policies_func: count_functions
}
type directus_users_mutated {
key: ID!
event: EventEnum
data: directus_users
}
type directus_versions {
id: ID!
key: String!
name: String
collection: String!
item: String!
hash: String
date_created: Date
date_created_func: datetime_functions
date_updated: Date
date_updated_func: datetime_functions
user_created(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
user_updated(filter: directus_users_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_users
delta: JSON
delta_func: count_functions
}
type directus_versions_mutated {
key: ID!
event: EventEnum
data: directus_versions
}
type directus_webhooks {
id: ID!
name: String!
method: String
url: String!
status: String
data: Boolean
actions: [String]!
collections: [String]!
headers: JSON
headers_func: count_functions
was_active_before_deprecation: Boolean!
migrated_flow(filter: directus_flows_filter, sort: [String], limit: Int, offset: Int, page: Int, search: String): directus_flows
}
type directus_webhooks_mutated {
key: ID!
event: EventEnum
data: directus_webhooks
}
input big_int_filter_operators {
_eq: GraphQLBigInt
_neq: GraphQLBigInt
_in: [GraphQLBigInt]
_nin: [GraphQLBigInt]
_gt: GraphQLBigInt
_gte: GraphQLBigInt
_lt: GraphQLBigInt
_lte: GraphQLBigInt
_null: Boolean
_nnull: Boolean
_between: [GraphQLBigInt]
_nbetween: [GraphQLBigInt]
}
input boolean_filter_operators {
_eq: Boolean
_neq: Boolean
_null: Boolean
_nnull: Boolean
}
input count_function_filter_operators {
count: number_filter_operators
}
input date_filter_operators {
_eq: String
_neq: String
_gt: String
_gte: String
_lt: String
_lte: String
_null: Boolean
_nnull: Boolean
_in: [String]
_nin: [String]
_between: [GraphQLStringOrFloat]
_nbetween: [GraphQLStringOrFloat]
}
input datetime_function_filter_operators {
year: number_filter_operators
month: number_filter_operators
week: number_filter_operators
day: number_filter_operators
weekday: number_filter_operators
hour: number_filter_operators
minute: number_filter_operators
second: number_filter_operators
}
input directus_access_filter {
id: string_filter_operators
role: directus_roles_filter
user: directus_users_filter
policy: directus_policies_filter
sort: number_filter_operators
_and: [directus_access_filter]
_or: [directus_access_filter]
}
input directus_activity_filter {
id: number_filter_operators
action: string_filter_operators
user: directus_users_filter
timestamp: date_filter_operators
timestamp_func: datetime_function_filter_operators
ip: string_filter_operators
user_agent: string_filter_operators
collection: string_filter_operators
item: string_filter_operators
origin: string_filter_operators
revisions: directus_revisions_filter
revisions_func: count_function_filter_operators
_and: [directus_activity_filter]
_or: [directus_activity_filter]
}
input directus_dashboards_filter {
id: string_filter_operators
name: string_filter_operators
icon: string_filter_operators
note: string_filter_operators
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_created: directus_users_filter
color: string_filter_operators
panels: directus_panels_filter
panels_func: count_function_filter_operators
_and: [directus_dashboards_filter]
_or: [directus_dashboards_filter]
}
input directus_files_filter {
id: string_filter_operators
storage: string_filter_operators
filename_disk: string_filter_operators
filename_download: string_filter_operators
title: string_filter_operators
type: string_filter_operators
folder: directus_folders_filter
uploaded_by: directus_users_filter
created_on: date_filter_operators
created_on_func: datetime_function_filter_operators
modified_by: directus_users_filter
modified_on: date_filter_operators
modified_on_func: datetime_function_filter_operators
charset: string_filter_operators
filesize: big_int_filter_operators
width: number_filter_operators
height: number_filter_operators
duration: number_filter_operators
embed: string_filter_operators
description: string_filter_operators
location: string_filter_operators
tags: string_filter_operators
tags_func: count_function_filter_operators
metadata: string_filter_operators
metadata_func: count_function_filter_operators
focal_point_x: number_filter_operators
focal_point_y: number_filter_operators
tus_id: string_filter_operators
tus_data: string_filter_operators
tus_data_func: count_function_filter_operators
uploaded_on: date_filter_operators
uploaded_on_func: datetime_function_filter_operators
_and: [directus_files_filter]
_or: [directus_files_filter]
}
input directus_flows_filter {
id: string_filter_operators
name: string_filter_operators
icon: string_filter_operators
color: string_filter_operators
description: string_filter_operators
status: string_filter_operators
trigger: string_filter_operators
accountability: string_filter_operators
options: string_filter_operators
options_func: count_function_filter_operators
operation: directus_operations_filter
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_created: directus_users_filter
operations: directus_operations_filter
operations_func: count_function_filter_operators
_and: [directus_flows_filter]
_or: [directus_flows_filter]
}
input directus_folders_filter {
id: string_filter_operators
name: string_filter_operators
parent: directus_folders_filter
_and: [directus_folders_filter]
_or: [directus_folders_filter]
}
input directus_operations_filter {
id: string_filter_operators
name: string_filter_operators
key: string_filter_operators
type: string_filter_operators
position_x: number_filter_operators
position_y: number_filter_operators
options: string_filter_operators
options_func: count_function_filter_operators
resolve: directus_operations_filter
reject: directus_operations_filter
flow: directus_flows_filter
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_created: directus_users_filter
_and: [directus_operations_filter]
_or: [directus_operations_filter]
}
input directus_panels_filter {
id: string_filter_operators
dashboard: directus_dashboards_filter
name: string_filter_operators
icon: string_filter_operators
color: string_filter_operators
show_header: boolean_filter_operators
note: string_filter_operators
type: string_filter_operators
position_x: number_filter_operators
position_y: number_filter_operators
width: number_filter_operators
height: number_filter_operators
options: string_filter_operators
options_func: count_function_filter_operators
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
user_created: directus_users_filter
_and: [directus_panels_filter]
_or: [directus_panels_filter]
}
input directus_permissions_filter {
id: number_filter_operators
collection: string_filter_operators
action: string_filter_operators
permissions: string_filter_operators
permissions_func: count_function_filter_operators
validation: string_filter_operators
validation_func: count_function_filter_operators
presets: string_filter_operators
presets_func: count_function_filter_operators
fields: string_filter_operators
policy: directus_policies_filter
_and: [directus_permissions_filter]
_or: [directus_permissions_filter]
}
input directus_policies_filter {
id: string_filter_operators
name: string_filter_operators
icon: string_filter_operators
description: string_filter_operators
ip_access: string_filter_operators
enforce_tfa: boolean_filter_operators
admin_access: boolean_filter_operators
app_access: boolean_filter_operators
permissions: directus_permissions_filter
permissions_func: count_function_filter_operators
users: directus_access_filter
users_func: count_function_filter_operators
roles: directus_access_filter
roles_func: count_function_filter_operators
_and: [directus_policies_filter]
_or: [directus_policies_filter]
}
input directus_revisions_filter {
id: number_filter_operators
activity: directus_activity_filter
collection: string_filter_operators
item: string_filter_operators
data: string_filter_operators
data_func: count_function_filter_operators
delta: string_filter_operators
delta_func: count_function_filter_operators
parent: directus_revisions_filter
version: directus_versions_filter
_and: [directus_revisions_filter]
_or: [directus_revisions_filter]
}
input directus_roles_filter {
id: string_filter_operators
name: string_filter_operators
icon: string_filter_operators
description: string_filter_operators
parent: directus_roles_filter
children: directus_roles_filter
children_func: count_function_filter_operators
policies: directus_access_filter
policies_func: count_function_filter_operators
users: directus_users_filter
users_func: count_function_filter_operators
_and: [directus_roles_filter]
_or: [directus_roles_filter]
}
input directus_users_filter {
id: string_filter_operators
first_name: string_filter_operators
last_name: string_filter_operators
email: string_filter_operators
password: hash_filter_operators
location: string_filter_operators
title: string_filter_operators
description: string_filter_operators
tags: string_filter_operators
tags_func: count_function_filter_operators
avatar: directus_files_filter
language: string_filter_operators
tfa_secret: hash_filter_operators
status: string_filter_operators
role: directus_roles_filter
token: hash_filter_operators
last_access: date_filter_operators
last_access_func: datetime_function_filter_operators
last_page: string_filter_operators
provider: string_filter_operators
external_identifier: string_filter_operators
auth_data: string_filter_operators
auth_data_func: count_function_filter_operators
email_notifications: boolean_filter_operators
appearance: string_filter_operators
theme_dark: string_filter_operators
theme_light: string_filter_operators
theme_light_overrides: string_filter_operators
theme_light_overrides_func: count_function_filter_operators
theme_dark_overrides: string_filter_operators
theme_dark_overrides_func: count_function_filter_operators
policies: directus_access_filter
policies_func: count_function_filter_operators
_and: [directus_users_filter]
_or: [directus_users_filter]
}
input directus_versions_filter {
id: string_filter_operators
key: string_filter_operators
name: string_filter_operators
collection: string_filter_operators
item: string_filter_operators
hash: string_filter_operators
date_created: date_filter_operators
date_created_func: datetime_function_filter_operators
date_updated: date_filter_operators
date_updated_func: datetime_function_filter_operators
user_created: directus_users_filter
user_updated: directus_users_filter
delta: string_filter_operators
delta_func: count_function_filter_operators
_and: [directus_versions_filter]
_or: [directus_versions_filter]
}
input hash_filter_operators {
_null: Boolean
_nnull: Boolean
_empty: Boolean
_nempty: Boolean
}
input number_filter_operators {
_eq: GraphQLStringOrFloat
_neq: GraphQLStringOrFloat
_in: [GraphQLStringOrFloat]
_nin: [GraphQLStringOrFloat]
_gt: GraphQLStringOrFloat
_gte: GraphQLStringOrFloat
_lt: GraphQLStringOrFloat
_lte: GraphQLStringOrFloat
_null: Boolean
_nnull: Boolean
_between: [GraphQLStringOrFloat]
_nbetween: [GraphQLStringOrFloat]
}
input string_filter_operators {
_eq: String
_neq: String
_contains: String
_icontains: String
_ncontains: String
_starts_with: String
_nstarts_with: String
_istarts_with: String
_nistarts_with: String
_ends_with: String
_nends_with: String
_iends_with: String
_niends_with: String
_in: [String]
_nin: [String]
_null: Boolean
_nnull: Boolean
_empty: Boolean
_nempty: Boolean
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,6 @@
{
"name": "directus-extensions",
"dependencies": {
"directus-extension-sync": "^3.0.2"
}
}

1
frontend/.env Normal file
View File

@ -0,0 +1 @@
VITE_OPEN_COLLECTIVE_API_KEY=your_key

3
frontend/.eslintignore Normal file
View File

@ -0,0 +1,3 @@
node_modules/
dist/
data/

223
frontend/.eslintrc.cjs Normal file
View File

@ -0,0 +1,223 @@
// eslint-disable-next-line import/no-commonjs
module.exports = {
env: {
browser: true,
es2021: true,
},
extends: [
'standard',
'eslint:recommended',
'plugin:@eslint-community/eslint-comments/recommended',
'plugin:@typescript-eslint/recommended',
'plugin:import/recommended',
'plugin:import/typescript',
// 'plugin:promise/recommended',
'plugin:security/recommended-legacy',
'plugin:react/recommended',
],
parserOptions: {
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: [
'@typescript-eslint',
'import',
'promise',
'security',
'no-catch-all',
'react',
'react-hooks',
'react-refresh',
],
// TODO also parse this
ignorePatterns: ['vite.config.ts'],
settings: {
'import/resolver': {
typescript: true,
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
},
},
react: {
version: '18.2.0',
},
},
rules: {
'react-hooks/rules-of-hooks': 'error', // Checks rules of Hooks
'react-hooks/exhaustive-deps': 'warn', // Checks effect dependencies
'react/react-in-jsx-scope': 'off', // Disable requirement for React import
'no-catch-all/no-catch-all': 'error',
'no-console': 'error',
'no-debugger': 'error',
camelcase: 'error',
indent: ['error', 2],
'linebreak-style': ['error', 'unix'],
semi: ['error', 'never'],
// Optional eslint-comments rule
'@eslint-community/eslint-comments/no-unused-disable': 'error',
'@eslint-community/eslint-comments/disable-enable-pair': ['error', { allowWholeFile: true }],
// import
'import/export': 'error',
'import/no-deprecated': 'error',
'import/no-empty-named-blocks': 'error',
'import/no-extraneous-dependencies': 'error',
'import/no-mutable-exports': 'error',
'import/no-unused-modules': 'error',
'import/no-named-as-default': 'error',
'import/no-named-as-default-member': 'error',
'import/no-amd': 'error',
'import/no-commonjs': 'error',
'import/no-import-module-exports': 'error',
'import/no-nodejs-modules': 'off',
'import/unambiguous': 'off', // not compatible with scriptless vue files
'import/default': 'error',
'import/named': 'error',
'import/namespace': 'error',
'import/no-absolute-path': 'error',
'import/no-cycle': 'error',
'import/no-dynamic-require': 'error',
'import/no-internal-modules': 'off',
'import/no-relative-packages': 'error',
'import/no-relative-parent-imports': [
'error',
{
ignore: ['#[src,types,root,components,utils,assets]/*'],
},
],
'import/no-self-import': 'error',
'import/no-unresolved': [
'error',
{
ignore: ['react'],
},
],
'import/no-useless-path-segments': 'error',
'import/no-webpack-loader-syntax': 'error',
'import/consistent-type-specifier-style': 'error',
'import/exports-last': 'off',
'import/extensions': [
'error',
'never',
{
json: 'always',
},
],
'import/first': 'error',
'import/group-exports': 'off',
'import/newline-after-import': 'error',
'import/no-anonymous-default-export': 'off', // todo - consider to enable again
'import/no-default-export': 'off', // incompatible with vite & vike
'import/no-duplicates': 'error',
'import/no-named-default': 'error',
'import/no-namespace': 'error',
'import/no-unassigned-import': [
'error',
{
allow: ['**/*.css'],
},
],
'import/order': [
'error',
{
groups: ['builtin', 'external', 'internal', 'parent', 'sibling', 'index', 'object', 'type'],
'newlines-between': 'always',
alphabetize: {
order: 'asc', // sort in ascending order. Options: ["ignore", "asc", "desc"]
caseInsensitive: true, // ignore case. Options: [true, false]
},
distinctGroup: true,
},
],
'import/prefer-default-export': 'off',
// promise
'promise/catch-or-return': 'error',
'promise/no-return-wrap': 'error',
'promise/param-names': 'error',
'promise/always-return': 'error',
'promise/no-native': 'off',
'promise/no-nesting': 'warn',
'promise/no-promise-in-callback': 'warn',
'promise/no-callback-in-promise': 'warn',
'promise/avoid-new': 'warn',
'promise/no-new-statics': 'error',
'promise/no-return-in-finally': 'warn',
'promise/valid-params': 'warn',
'promise/prefer-await-to-callbacks': 'error',
'promise/no-multiple-resolved': 'error',
},
overrides: [
{
files: ['*.ts', '*.tsx'],
parser: '@typescript-eslint/parser',
parserOptions: {
tsconfigRootDir: __dirname,
project: ['./tsconfig.json', '**/tsconfig.json'],
ecmaVersion: 'latest',
parser: '@typescript-eslint/parser',
sourceType: 'module',
},
plugins: ['@typescript-eslint'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:@typescript-eslint/strict',
],
rules: {
'@typescript-eslint/consistent-type-imports': 'error',
// allow explicitly defined dangling promises
'@typescript-eslint/no-floating-promises': ['error', { ignoreVoid: true }],
'no-void': ['error', { allowAsStatement: true }],
},
},
{
files: ['!*.json'],
plugins: ['prettier'],
extends: ['plugin:prettier/recommended'],
rules: {
'prettier/prettier': 'error',
},
},
{
files: ['*.json'],
plugins: ['json'],
extends: ['plugin:json/recommended-with-comments'],
},
// {
// files: ['*.{test,spec}.[tj]s'],
// plugins: ['vitest'],
// extends: ['plugin:vitest/all'],
// rules: {
// 'vitest/prefer-lowercase-title': 'off',
// 'vitest/no-hooks': 'off',
// 'vitest/consistent-test-filename': 'off',
// 'vitest/prefer-expect-assertions': [
// 'off',
// {
// onlyFunctionsWithExpectInLoop: true,
// onlyFunctionsWithExpectInCallback: true,
// },
// ],
// 'vitest/prefer-strict-equal': 'off',
// 'vitest/prefer-to-be-falsy': 'off',
// 'vitest/prefer-to-be-truthy': 'off',
// 'vitest/require-hook': [
// 'error',
// {
// allowedFunctionCalls: [
// 'mockClient.setRequestHandler',
// 'setActivePinia',
// 'provideApolloClient',
// ],
// },
// ],
// },
// },
{
files: ['*.yaml', '*.yml'],
parser: 'yaml-eslint-parser',
plugins: ['yml'],
extends: ['plugin:yml/prettier'],
},
],
}

2
frontend/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
node_modules/
dist/

47
frontend/README.md Normal file
View File

@ -0,0 +1,47 @@
# 🌍 Utopia Map [![](https://img.shields.io/opencollective/backers/utopia-project)](https://opencollective.com/utopia-project#section-contributors) [![Join us on Telegram](https://img.shields.io/badge/Join-Telegram-blue?logo=telegram)](https://t.me/UtopiaMap)
The Utopia Map is a flexible collaborative app for decentralized coordination and real-life networking that can be adapted to the specific requirements of different networks. Its central element is the interactive geographical map, where users can add and manage **Items** in predefined **Layers**.
Utopia Map is made for networks and initiatives that aim to connect people in real life. By providing a custom instance of Utopia Map, each network can grow and coordinate its ecosystem effectively while encouraging real-world interactions and collaborations.
**Utopia Map** is based on **[Utopia UI](https://github.com/utopia-os/utopia-ui)**.
## Key Features
- **Interactive Map**: The core feature is an intuitive geographical map where users can add, edit, and manage items like members, activities, and resources. Each map instance has its own identity, users, and unique configuration.
- **Customizable Layers**: Items are organized into predefined Layers, each with specific icons, colors, texts, and Map Markers. This ensures clarity and relevance for different networks.
- **Dynamic Map Markers**: Geographic position of item are indicated on the map by adaptive and customizable Map Markers
- **Popups**: Clicking a Map Marker reveals a Popup — a compact preview of the Item with its most relevant information. Define custom Popups for each of your Layers.
- **Profiles**: Each Item has a dedicated Profile that showcases all its associated data, making it easier to explore and manage. Define custom profiles for each of your Layers.
## Deployment Options
- **SaaS**: A hosted solution with regular updates and support for easy onboarding and maintenance.
- **Self-Hosted**: Deploy on your own infrastructure to retain full control and customization.
## Getting Started
Clone the repository and get started with the following commands:
```bash
npm install
npm run dev
```
## Get your Map! 🌱 🌍
Start mapping and growing your community ecosystem together with your custom map.
[Join us on Telegram](https://t.me/UtopiaMap)
## Support Utopia Map 💚
We are building Utopia Map as an free and opensource tool. To keep this project sustainable and accessible, we need financial support as well as Developrs, UX Designer, Community Managers and Content Creators.
[Join us on Telegram](https://t.me/UtopiaMap) and support us on [OpenCollective](https://opencollective.com/utopia-project)
<a href="https://opencollective.com/utopia-project">
<img width="250" src="https://opencollective.com/utopia-project/donate/button@2x.png?color=blue" style="margin-bottom:20px;" />
</a>

View File

@ -0,0 +1,70 @@
services:
frontend:
image: cupcakearmy/static
restart: unless-stopped
ports:
- 8080:80
volumes:
- ./dist:/srv:ro
database:
image: postgis/postgis:13-master
# Required when running on platform other than amd64, like Apple M1/M2:
# platform: linux/amd64
volumes:
- ./data/database:/var/lib/postgresql/data
environment:
POSTGRES_USER: 'directus'
POSTGRES_PASSWORD: 'directus'
POSTGRES_DB: 'directus'
healthcheck:
test: ['CMD', 'pg_isready', '--host=localhost', '--username=directus']
interval: 10s
timeout: 5s
retries: 5
start_interval: 5s
start_period: 30s
cache:
image: redis:6
healthcheck:
test: ['CMD-SHELL', "[ $$(redis-cli ping) = 'PONG' ]"]
interval: 10s
timeout: 5s
retries: 5
start_interval: 5s
start_period: 30s
backend:
container_name: backend
build:
context: ./backend
depends_on:
database:
condition: service_healthy
cache:
condition: service_healthy
ports:
- 8055:8055
environment:
PUBLIC_URL: 'http://localhost'
SECRET: 'SECRET'
CORS_ENABLED: 'true'
CORS_ORIGIN: 'http://localhost:8080'
DB_CLIENT: 'pg'
DB_HOST: 'database'
DB_PORT: '5432'
DB_DATABASE: 'directus'
DB_USER: 'directus'
DB_PASSWORD: 'directus'
CACHE_ENABLED: 'true'
CACHE_AUTO_PURGE: 'true'
CACHE_STORE: 'redis'
REDIS: 'redis://cache:6379'
ADMIN_EMAIL: 'admin@it4c.dev'
ADMIN_PASSWORD: 'admin123'

22
frontend/index.html Normal file
View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, interactive-widget=resizes-visual" />
<meta name="description" content="collaborative and interactive Maps for Utopians">
<link rel="icon" type="image/png" href="/3markers-globe.svg" />
<link rel="stylesheet" type="text/css" href="/style.css" />
<title>Utopia Map</title>
</head>
<body>
<div id="root">
<div class="outer fade-in">
<img height="100" class="pulse-loader opacity" src="/3markers-globe.svg"/>
<br>
<span class="loader"></span>
</div>
</div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1,26 @@
-----BEGIN CERTIFICATE-----
MIIEXTCCAsWgAwIBAgIQcCbPt92wSwGqEayIluO/fTANBgkqhkiG9w0BAQsFADB7
MR4wHAYDVQQKExVta2NlcnQgZGV2ZWxvcG1lbnQgQ0ExKDAmBgNVBAsMH2ZyaXR6
QGZyaXR6LVRoaW5rUGFkLVQxNHMtR2VuLTMxLzAtBgNVBAMMJm1rY2VydCBmcml0
ekBmcml0ei1UaGlua1BhZC1UMTRzLUdlbi0zMB4XDTI1MDUzMDE3NTUwMFoXDTI3
MDgzMDE3NTUwMFowUzEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRp
ZmljYXRlMSgwJgYDVQQLDB9mcml0ekBmcml0ei1UaGlua1BhZC1UMTRzLUdlbi0z
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApl8YZzbT2AHZCtsVR7Er
8TNDl6EbpKsppOstbtcF7m3eRNL6C/NrgGusDYEpqimUNFkzietD/WFHjzZFiOwL
UUNlTlFud+xAhjFEpI7VMdckY0vWYNRpX5A3qZM5Mj0GnP8HJOeRvp855XpaIlR9
Nlnx8PQy7Na40MAf6dXUEnEyXHDSEasNaGpsi/csEDvWc4APzVyi22IE5yPoGJqr
gr04zgy6Vk5sorCFIxdJ1AjyNsO8TK3cP5c/AWbeG9rzD6Ue8kTNIx/C6IPVWTjz
UltRwoFYNmqNAfyMC92jsDOyNzIceA83flfij073Bux1nry6ECsCgClS4G3N+6/f
ewIDAQABo4GEMIGBMA4GA1UdDwEB/wQEAwIFoDATBgNVHSUEDDAKBggrBgEFBQcD
ATAfBgNVHSMEGDAWgBRK5epC4SLzleTP68biJNt6oll9xzA5BgNVHREEMjAwggls
b2NhbGhvc3SCCzE5Mi4xNjgueC54hwR/AAABhxAAAAAAAAAAAAAAAAAAAAABMA0G
CSqGSIb3DQEBCwUAA4IBgQCLTcK+MOuA99iF4aOE3iUwsuJmmcr/CLjMi3S+MqmP
LKRakqefruV2x4cpgW52Y31nH7UYUTus8FPj/FJAFF8+A1Cw/mLvamAGfcHRpeX0
CiqmOlDmfqN8x40qeckOn+ymHtjG6szWJ1Erryb2v8KkRmE/dJYCtuUKIvid+yK1
RObg8UBNf98CpzBWnNgGjIKcJOjtde9sl2d0970SJ3udPJ1Vg2iqx/7sSrFFnBi8
s7S086GJDEE7vj+3BGs+Xdvscibf9J5TUf0LzziDj7v/1xbQSdsqyhjp2oFXv44A
JZLehvqnyrQ2LxGeECitLugwWg/VabZC8OGMo15VUO4Vexx/l4SjrqVVcBP4rfs0
rjLUvxNcpyQEX6sFSBIrlA1NhQc9fH9SK1ownjiSoYKyDn/aQ1M57oQJXIm8+kVN
VUrPrqDOopsCj4uOuRhnN7Pr6R4r2JX/N9SlaYvCACIYVdlsO/ENdOamtoLXSZKF
v+MkgeYzDeqx7kJDhrLwhKY=
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQCmXxhnNtPYAdkK
2xVHsSvxM0OXoRukqymk6y1u1wXubd5E0voL82uAa6wNgSmqKZQ0WTOJ60P9YUeP
NkWI7AtRQ2VOUW537ECGMUSkjtUx1yRjS9Zg1GlfkDepkzkyPQac/wck55G+nznl
eloiVH02WfHw9DLs1rjQwB/p1dQScTJccNIRqw1oamyL9ywQO9ZzgA/NXKLbYgTn
I+gYmquCvTjODLpWTmyisIUjF0nUCPI2w7xMrdw/lz8BZt4b2vMPpR7yRM0jH8Lo
g9VZOPNSW1HCgVg2ao0B/IwL3aOwM7I3Mhx4Dzd+V+KPTvcG7HWevLoQKwKAKVLg
bc37r997AgMBAAECggEBAJk7+TQHkbLgcNHI256+lmR//P+JifZvF9ooKh5uua3T
T+dAEsIzH46WnU+K6Z8q0LAugIPFR0f283v6Vq04C2x7kLFD22VlGkD+2+RdEgvn
xkIWtYtV2QAFyt7LFOEqyA6N7jyVdO2HpkGwIZiBDRqkWWy+3rC5TFwouLwdEEky
sPExVAYYkuDwYpb82ENz6fxR4rvfI5mMkoyI12q4UxByLd7lC6rHgCP8jpz3lpMB
dx2+eWpRH4cA/SSxn2SnpSyLIWnPaS4FmL7t6Yfd9xnp9Qm6vtiTeR+i6mOP1D9u
yjNhI/CoUBIFlxOnweGwlycR9kEiF0khzV7LKq5N55ECgYEA1eQeDs/hd7QAcmKN
lyH0ZdXXFoUpE81yZHECoQ/EZaWh+qzvRQoMusUMRZHKXewgCjuj3CygatIFX+gS
tkjz04K24p/ilGgGXUAnsc2K0N0lDhd5fTngP49AOvbpwbH1pmy1YIeWML0AvdZO
qAir+mo8WYArOBJNVsfnjYChyZcCgYEAxyAKM4D6G2k7MzlRoZ5rNifk8lYP0TZc
fISRt5g4ckGL/qLHJl0Y5EcFXdElEwO9VTNYkuRNSffttzVtR4MSpVsSOcGW+fcd
nAarlK58EZzg54LCITANEJNVFrj6zhaxKLKLfvhvuhM6hR36Th8xz+JHeSf2zEGP
lXWLxHt1rb0CgYEAshUE5aY0/foTaSke5Bc0Kdl6BFkIE2G5fWEOJFVBWrQmAdUc
PeoZISPQjNfOmpZvMrXnPvi081s7eFd7xONGhvLNHDCscI2PxvIUWphcnHuTT4FB
+H5IdALsImvTwh5N/auC9ATPinLCfExDHuphbdQnvQjWcw/h7n/wDUNs8K8CgYEA
kM4o/PlfaI2nIoIdbgXRe4mZge26BO+eZGLXwQaujdBC6UMgxjJPhrprGaWda219
wzbaw7/IwPDOazwsIyQbbCqFX4D7tP3TB4MGRdJ5oSlPmKxLVm0mIxaxexu8MCDq
Zmko7oXmYg/Xr7R0MVmYFNA+H8pDPJi0vQDFSnZpxpUCgYB2/fXHBjlBrvq41Tve
O46t0vZVcePsmLPNNE9wugT6QRuIlRGbi3PVE0PIjPG+gpfZPDGedLztMnsrm4cj
RMuCOer300/MAw/cBLEhmaX38Y/UK4oQ3y+XlvniEHcFSBhDoBFrvMqxLvYdU06X
ktmcj6c87RE7QEWnSmbpj3Oo3A==
-----END PRIVATE KEY-----

11533
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

55
frontend/package.json Normal file
View File

@ -0,0 +1,55 @@
{
"name": "utopia-map",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --host",
"build": "tsc && vite build",
"test:lint:eslint": "eslint --ext .ts,.tsx,.js,.jsx,.cjs,.mjs,.json,.yml,.yaml --max-warnings 0 .",
"preview": "vite preview"
},
"dependencies": {
"@directus/sdk": "^17.0.2",
"@heroicons/react": "^2.1.1",
"@tailwindcss/vite": "^4.0.15",
"@types/geojson": "^7946.0.10",
"axios": "^1.6.5",
"date-fns": "^3.3.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-rnd": "^10.4.1",
"react-router-dom": "^6.23.0",
"utopia-ui": "^3.0.96"
},
"devDependencies": {
"@eslint-community/eslint-plugin-eslint-comments": "^4.4.1",
"@types/node": "^22.15.28",
"@types/react": "^18.2.79",
"@types/react-dom": "^18.2.25",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.0.0",
"daisyui": "^5.0.6",
"eslint": "^8.24.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard": "^17.1.0",
"eslint-import-resolver-typescript": "^3.6.3",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-n": "^16.6.2",
"eslint-plugin-no-catch-all": "^1.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-react": "^7.31.8",
"eslint-plugin-react-hooks": "^4.6.0",
"eslint-plugin-react-refresh": "^0.4.18",
"eslint-plugin-security": "^3.0.1",
"eslint-plugin-yml": "^1.14.0",
"postcss": "^8.4.30",
"tailwindcss": "^4.0.15",
"typescript": "^5.0.2",
"vite": "^6.2.0",
"vite-plugin-pwa": "^0.21.1"
}
}

View File

@ -0,0 +1,264 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="18mm"
height="16mm"
viewBox="0 0 17.999999 16.000001"
version="1.1"
id="svg119"
sodipodi:docname="3markers-globe.svg"
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs113" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="10.24"
inkscape:cx="25.927734"
inkscape:cy="34.61914"
inkscape:document-units="mm"
inkscape:current-layer="layer6"
showgrid="false"
fit-margin-right="-0.1"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-bottom="0"
inkscape:window-width="1920"
inkscape:window-height="1013"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:pagecheckerboard="0" />
<metadata
id="metadata116">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:groupmode="layer"
id="layer4"
inkscape:label="Ebene 4"
style="display:inline"
transform="translate(0,-4.809948)">
<path
inkscape:connector-curvature="0"
style="display:inline;fill:#aad400;stroke-width:0.26458332;fill-opacity:1"
d="m 8.5782875,9.4721385 c -2.9104166,0 -5.2916666,2.3812505 -5.2916666,5.2916675 0,2.910417 2.38125,5.291667 5.2916666,5.291667 2.9104165,0 5.2916665,-2.38125 5.2916665,-5.291667 0,-2.910417 -2.38125,-5.2916675 -5.2916665,-5.2916675 z"
id="path2" />
<path
inkscape:connector-curvature="0"
style="display:inline;fill:#31b0fd;stroke-width:0.26458332;fill-opacity:1"
d="m 14.134538,14.763806 c 0,3.095625 -2.513542,5.55625 -5.5562505,5.55625 -3.0427082,0 -5.5562499,-2.460625 -5.5562499,-5.55625 0,-3.095625 2.460625,-5.5562505 5.5562499,-5.5562505 3.0956255,0 5.5562505,2.4606255 5.5562505,5.5562505 z m -5.6091675,2.566458 c 0,-0.105833 -0.05292,-0.15875 -0.15875,-0.211666 -0.3439579,-0.105834 -0.6614579,-0.105834 -0.9524996,-0.396875 -0.052917,-0.105834 -0.052917,-0.211667 -0.1058333,-0.343959 -0.1058333,-0.105833 -0.396875,-0.15875 -0.555625,-0.211666 -0.2116667,0 -0.4497917,0 -0.714375,0 -0.1058333,0 -0.2910417,0 -0.396875,0 -0.15875,-0.05292 -0.2910417,-0.291042 -0.396875,-0.449792 0,-0.05292 0,-0.15875 -0.1058333,-0.15875 -0.1058334,-0.05292 -0.2116667,0.05292 -0.3439584,0 -0.052917,-0.05292 -0.052917,-0.105833 -0.052917,-0.15875 0,-0.15875 0.1058333,-0.343958 0.2116666,-0.449792 0.15875,-0.105833 0.3439584,0.05292 0.5027084,0.05292 0.052917,0 0.052917,0 0.1058333,0.05292 0.15875,0.05292 0.2116667,0.264583 0.2116667,0.449791 0,0.05292 0,0.105834 0,0.105834 0,0.05292 0.052917,0.05292 0.1058333,0.05292 0.052917,-0.291041 0.052917,-0.555625 0.1058333,-0.846666 0,-0.343959 0.3439584,-0.661459 0.6085417,-0.767292 0.1058333,-0.05292 0.15875,0.05292 0.2910417,0 0.3439583,-0.105833 1.1641666,-0.449792 1.0054166,-0.899583 -0.1058333,-0.396875 -0.4497916,-0.767292 -0.8995833,-0.714375 -0.1058333,0.05292 -0.15875,0.105833 -0.2645833,0.15875 -0.15875,0.105833 -0.5027084,0.449791 -0.6614584,0.449791 -0.2910416,-0.05292 -0.2910416,-0.449791 -0.2116666,-0.608541 0.052917,-0.211667 0.555625,-0.9525 0.8995833,-0.820209 0.052917,0.05292 0.15875,0.15875 0.2116667,0.211667 0.1058333,0.05292 0.2910416,0.05292 0.4497916,0.05292 0.052917,0 0.1058334,0 0.15875,-0.05292 0.052917,-0.05292 0.052917,-0.05292 0.052917,-0.105833 0,-0.15875 -0.15875,-0.343959 -0.2645833,-0.449792 -0.1058334,-0.105833 -0.2910417,-0.211667 -0.4497917,-0.291042 -0.555625,-0.15875 -1.4552083,0.05292 -1.8785417,0.449792 -0.4233333,0.396875 -0.7672916,1.058333 -1.0054166,1.613958 -0.1058334,0.343959 -0.2116667,0.767292 -0.2645834,1.164167 -0.052917,0.264583 -0.1058333,0.502708 0.052917,0.767292 0.15875,0.343958 0.5027083,0.661458 0.8466667,0.899583 0.2116666,0.15875 0.6614583,0.15875 0.8995833,0.449792 0.15875,0.211666 0.1058333,0.502708 0.1058333,0.767291 0,0.343959 0.2116667,0.608542 0.3439584,0.899584 0.052917,0.15875 0.1058333,0.396875 0.15875,0.555625 0,0.05292 0.052917,0.396875 0.052917,0.449791 0.3439584,0.15875 0.6085417,0.343959 1.0054167,0.449792 0.052917,0 0.2645833,-0.343958 0.2645833,-0.396875 0.15875,-0.15875 0.2910417,-0.396875 0.4497917,-0.502708 0.1058333,-0.05292 0.2116663,-0.105834 0.3439583,-0.211667 0.105833,-0.105833 0.15875,-0.343958 0.211667,-0.502708 0.02646,-0.132292 0.07937,-0.343959 0.02646,-0.502709 z m 0.105834,-5.132916 c 0.05292,0 0.105833,-0.05292 0.211666,-0.105834 0.15875,-0.105833 0.343959,-0.291041 0.502709,-0.396875 0.15875,-0.105833 0.343958,-0.291041 0.449791,-0.396875 0.15875,-0.105833 0.2910415,-0.343958 0.3439585,-0.502708 0.05292,-0.105833 0.211666,-0.343958 0.15875,-0.502708 -0.05292,-0.105834 -0.3439585,-0.15875 -0.4497915,-0.211667 -0.449792,-0.1058335 -0.820208,-0.1587505 -1.27,-0.1587505 -0.15875,0 -0.396875,0.05292 -0.449792,0.2116675 -0.05292,0.291041 0.15875,0.211666 0.396875,0.291041 0,0 0.05292,0.449792 0.05292,0.502709 0.05292,0.264583 -0.105833,0.449791 -0.105833,0.714375 0,0.15875 0,0.449791 0.105833,0.555625 z m 4.6566665,3.889375 c 0.05292,-0.105834 0.05292,-0.291042 0.105833,-0.396875 0.05292,-0.264584 0.05292,-0.555625 0.05292,-0.820209 0,-0.555625 -0.05292,-1.11125 -0.211666,-1.613958 -0.105834,-0.15875 -0.15875,-0.343958 -0.211667,-0.502708 -0.105833,-0.291042 -0.264583,-0.555625 -0.502708,-0.767292 -0.211667,-0.291042 -0.502709,-1.058333 -1.005417,-0.820208 -0.15875,0.05292 -0.264583,0.264583 -0.396875,0.396875 -0.105833,0.15875 -0.211667,0.343958 -0.343959,0.502708 -0.05292,0.05292 -0.105833,0.15875 -0.05292,0.211667 0,0.05292 0.05292,0.05292 0.105834,0.05292 0.105833,0.05292 0.15875,0.05292 0.264583,0.105834 0.05292,0 0.105834,0.05292 0.05292,0.105833 0,0 0,0.05292 -0.05292,0.05292 -0.264583,0.291041 -0.555625,0.502708 -0.820208,0.767291 -0.05292,0.05292 -0.105834,0.15875 -0.105834,0.211667 0,0.05292 0.05292,0.05292 0.05292,0.105833 0,0.05292 -0.05292,0.05292 -0.105833,0.105834 -0.105832,0.05292 -0.2116665,0.105833 -0.2910415,0.15875 -0.05292,0.105833 0,0.291041 -0.05292,0.396875 -0.05292,0.291041 -0.211666,0.502708 -0.343958,0.767291 -0.105833,0.15875 -0.15875,0.343959 -0.264583,0.502709 0,0.211666 -0.05292,0.396875 0.05292,0.555625 0.264583,0.396875 0.767291,0.15875 1.1641655,0.343958 0.105834,0.05292 0.211667,0.05292 0.291042,0.15875 0.15875,0.15875 0.15875,0.449792 0.211667,0.608542 0.05292,0.211666 0.105833,0.449791 0.211667,0.661458 0.05292,0.264583 0.15875,0.555625 0.211666,0.767292 0.502709,-0.396875 0.9525,-0.820209 1.27,-1.375834 0.396875,-0.343958 0.555625,-0.79375 0.714375,-1.243541 z"
id="path4" />
</g>
<g
inkscape:groupmode="layer"
id="layer3"
inkscape:label="Ebene 2"
style="display:inline"
transform="translate(4.6675853,46.091266)">
<g
id="g89"
transform="rotate(-34.618495,35.488753,-41.431433)">
<g
id="g356"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.1012137,-0.03262355,0.01210909,0.10427296,3.1730944,-59.40221)"
stroke-miterlimit="2"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-opacity:1">
<image
id="image354"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAQCAYAAACcN8ZaAAAACXBIWXMAAA9hAAAPYQGoP6dpAAABoUlEQVRIic2Vy3LCMAxFD2lS6Gtg0f//x0JoEwKmC98LqklCZ7qpZjSynYdOrmQH/pEtJtarYlxNrM89V1oaiSnMR2Gq4HVwry0m4OZgnPAInBUPikdfd5LyhTXwKF8CK+ChgIrPTSl8DjBJAINiBXQBMtUzIC/Aq+JKa02AmAIwRCzDSQl7uQFqRdCktErJ34A1sBFQU8DYUhi7BGeyAkfFgdteORfP3sBYmaUANsA7WSErU4L4pafgA7msveYprLtM7plLE48pY6gl8CyQNfCkBLaY3Ar0Gp/I/fAFfAJ7je2dYBLXMt30jEHsDblk7ptGShjAfeAvPyh5B2wDyF5rPSO7aE6ZSAvXrdzofpfGQBFgB7SaO1qNsjRwp2cM4i+OsrqffK3VtZ08wrg8VsIl+XHIlVaPXHT9nWgrVQYBHZS4lRugJ5ej1b1dUOHSF3rHKNCYMj6cdlxP3J7cwGhsGAO7DLM9MTKehfHNhnFJPsi7K4WvngOYTDhnU1v7yPWoTuT6+1fgph3COPGLnrhnUz/KCFuHseGiAnHn/cm+AU9X31BIhxprAAAAAElFTkSuQmCC"
preserveAspectRatio="none"
height="16"
width="35"
y="-6.5234251"
x="3.2418795"
transform="rotate(32.792589)" />
</g>
<g
id="g522"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.13120595,0,0,0.13120595,2.0117562,-64.499292)"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#c4037d;fill-opacity:1;stroke:none">
<path
id="path520"
d="M 28.205,3.217 H 6.777 c -2.367,0 -4.286,1.87 -4.286,4.179 v 19.847 c 0,2.308 1.919,4.179 4.286,4.179 h 5.357 l 5.337,13.58 5.377,-13.58 h 5.357 c 2.366,0 4.285,-1.87 4.285,-4.179 V 7.396 c 0,-2.308 -1.919,-4.179 -4.285,-4.179"
inkscape:connector-curvature="0"
style="vector-effect:none;fill-rule:nonzero" />
</g>
<g
id="g550"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.13915785,0,0,0.13915785,1.8725982,-64.608673)"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#ffffff;fill-opacity:1;stroke:none">
<path
id="path548"
d="m 11.875,22.4375 c 0,0.3646 0.1276,0.6745 0.3828,0.9297 0.2552,0.2552 0.5651,0.3828 0.9297,0.3828 h 9.625 c 0.3646,0 0.6745,-0.1276 0.9297,-0.3828 0.2552,-0.2552 0.3828,-0.5651 0.3828,-0.9297 V 15 h -12.25 v 7.4375 m 8.75,-5.3594 c 0,-0.0937 0.0313,-0.1718 0.0938,-0.2343 0.0625,-0.0625 0.1406,-0.0938 0.2343,-0.0938 h 1.0938 c 0.0937,0 0.1719,0.0313 0.2344,0.0938 0.0625,0.0625 0.0937,0.1406 0.0937,0.2343 v 1.0938 c 0,0.0937 -0.0312,0.1719 -0.0937,0.2344 C 22.2188,18.4688 22.1406,18.5 22.0469,18.5 h -1.0938 c -0.0937,0 -0.1718,-0.0312 -0.2343,-0.0937 -0.0625,-0.0625 -0.0938,-0.1407 -0.0938,-0.2344 v -1.0938 m 0,3.5 c 0,-0.0937 0.0313,-0.1718 0.0938,-0.2343 0.0625,-0.0625 0.1406,-0.0938 0.2343,-0.0938 h 1.0938 c 0.0937,0 0.1719,0.0313 0.2344,0.0938 0.0625,0.0625 0.0937,0.1406 0.0937,0.2343 v 1.0938 c 0,0.0937 -0.0312,0.1719 -0.0937,0.2344 C 22.2188,21.9688 22.1406,22 22.0469,22 H 20.9531 C 20.8594,22 20.7813,21.9688 20.7188,21.9063 20.6563,21.8438 20.625,21.7656 20.625,21.6719 v -1.0938 m -3.5,-3.5 c 0,-0.0937 0.0313,-0.1718 0.0938,-0.2343 0.0625,-0.0625 0.1406,-0.0938 0.2343,-0.0938 h 1.0938 c 0.0937,0 0.1719,0.0313 0.2344,0.0938 0.0625,0.0625 0.0937,0.1406 0.0937,0.2343 v 1.0938 c 0,0.0937 -0.0312,0.1719 -0.0937,0.2344 C 18.7188,18.4688 18.6406,18.5 18.5469,18.5 h -1.0938 c -0.0937,0 -0.1718,-0.0312 -0.2343,-0.0937 -0.0625,-0.0625 -0.0938,-0.1407 -0.0938,-0.2344 v -1.0938 m 0,3.5 c 0,-0.0937 0.0313,-0.1718 0.0938,-0.2343 0.0625,-0.0625 0.1406,-0.0938 0.2343,-0.0938 h 1.0938 c 0.0937,0 0.1719,0.0313 0.2344,0.0938 0.0625,0.0625 0.0937,0.1406 0.0937,0.2343 v 1.0938 c 0,0.0937 -0.0312,0.1719 -0.0937,0.2344 C 18.7188,21.9688 18.6406,22 18.5469,22 H 17.4531 C 17.3594,22 17.2813,21.9688 17.2188,21.9063 17.1563,21.8438 17.125,21.7656 17.125,21.6719 v -1.0938 m -3.5,-3.5 c 0,-0.0937 0.0313,-0.1718 0.0938,-0.2343 0.0625,-0.0625 0.1406,-0.0938 0.2343,-0.0938 h 1.0938 c 0.0937,0 0.1719,0.0313 0.2344,0.0938 0.0625,0.0625 0.0937,0.1406 0.0937,0.2343 v 1.0938 c 0,0.0937 -0.0312,0.1719 -0.0937,0.2344 C 15.2188,18.4688 15.1406,18.5 15.0469,18.5 h -1.0938 c -0.0937,0 -0.1718,-0.0312 -0.2343,-0.0937 -0.0625,-0.0625 -0.0938,-0.1407 -0.0938,-0.2344 v -1.0938 m 0,3.5 c 0,-0.0937 0.0313,-0.1718 0.0938,-0.2343 0.0625,-0.0625 0.1406,-0.0938 0.2343,-0.0938 h 1.0938 c 0.0937,0 0.1719,0.0313 0.2344,0.0938 0.0625,0.0625 0.0937,0.1406 0.0937,0.2343 v 1.0938 c 0,0.0937 -0.0312,0.1719 -0.0937,0.2344 C 15.2188,21.9688 15.1406,22 15.0469,22 H 13.9531 C 13.8594,22 13.7813,21.9688 13.7188,21.9063 13.6563,21.8438 13.625,21.7656 13.625,21.6719 V 20.5781 M 22.8125,11.5 H 21.5 V 10.1875 C 21.5,10.0625 21.4583,9.95833 21.375,9.875 21.2917,9.79167 21.1875,9.75 21.0625,9.75 h -0.875 c -0.125,0 -0.2292,0.04167 -0.3125,0.125 -0.0833,0.08333 -0.125,0.1875 -0.125,0.3125 V 11.5 h -3.5 V 10.1875 C 16.25,10.0625 16.2083,9.95833 16.125,9.875 16.0417,9.79167 15.9375,9.75 15.8125,9.75 h -0.875 c -0.125,0 -0.2292,0.04167 -0.3125,0.125 C 14.5417,9.95833 14.5,10.0625 14.5,10.1875 V 11.5 h -1.3125 c -0.3646,0 -0.6745,0.1276 -0.9297,0.3828 -0.2552,0.2552 -0.3828,0.5651 -0.3828,0.9297 v 1.3125 h 12.25 v -1.3125 c 0,-0.3646 -0.1276,-0.6745 -0.3828,-0.9297 C 23.487,11.6276 23.1771,11.5 22.8125,11.5"
inkscape:connector-curvature="0"
style="vector-effect:none;fill-rule:nonzero" />
</g>
<g
id="g356-3"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.1012137,-0.03262355,0.01210909,0.10427296,8.1741435,-58.851786)"
stroke-miterlimit="2"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-opacity:1">
<image
id="image354-6"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAQCAYAAACcN8ZaAAAACXBIWXMAAA9hAAAPYQGoP6dpAAABoUlEQVRIic2Vy3LCMAxFD2lS6Gtg0f//x0JoEwKmC98LqklCZ7qpZjSynYdOrmQH/pEtJtarYlxNrM89V1oaiSnMR2Gq4HVwry0m4OZgnPAInBUPikdfd5LyhTXwKF8CK+ChgIrPTSl8DjBJAINiBXQBMtUzIC/Aq+JKa02AmAIwRCzDSQl7uQFqRdCktErJ34A1sBFQU8DYUhi7BGeyAkfFgdteORfP3sBYmaUANsA7WSErU4L4pafgA7msveYprLtM7plLE48pY6gl8CyQNfCkBLaY3Ar0Gp/I/fAFfAJ7je2dYBLXMt30jEHsDblk7ptGShjAfeAvPyh5B2wDyF5rPSO7aE6ZSAvXrdzofpfGQBFgB7SaO1qNsjRwp2cM4i+OsrqffK3VtZ08wrg8VsIl+XHIlVaPXHT9nWgrVQYBHZS4lRugJ5ej1b1dUOHSF3rHKNCYMj6cdlxP3J7cwGhsGAO7DLM9MTKehfHNhnFJPsi7K4WvngOYTDhnU1v7yPWoTuT6+1fgph3COPGLnrhnUz/KCFuHseGiAnHn/cm+AU9X31BIhxprAAAAAElFTkSuQmCC"
preserveAspectRatio="none"
height="13.833991"
width="32.70261"
y="-4.4695668"
x="2.8086059"
transform="matrix(0.82962222,0.55832515,-0.5232806,0.85216044,0,0)" />
</g>
<g
id="g356-3-7"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.1012137,-0.03262355,0.01210909,0.10427296,10.119996,-55.735617)"
stroke-miterlimit="2"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#000000;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:2;stroke-opacity:1">
<image
id="image354-6-5"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACMAAAAQCAYAAACcN8ZaAAAACXBIWXMAAA9hAAAPYQGoP6dpAAABoUlEQVRIic2Vy3LCMAxFD2lS6Gtg0f//x0JoEwKmC98LqklCZ7qpZjSynYdOrmQH/pEtJtarYlxNrM89V1oaiSnMR2Gq4HVwry0m4OZgnPAInBUPikdfd5LyhTXwKF8CK+ChgIrPTSl8DjBJAINiBXQBMtUzIC/Aq+JKa02AmAIwRCzDSQl7uQFqRdCktErJ34A1sBFQU8DYUhi7BGeyAkfFgdteORfP3sBYmaUANsA7WSErU4L4pafgA7msveYprLtM7plLE48pY6gl8CyQNfCkBLaY3Ar0Gp/I/fAFfAJ7je2dYBLXMt30jEHsDblk7ptGShjAfeAvPyh5B2wDyF5rPSO7aE6ZSAvXrdzofpfGQBFgB7SaO1qNsjRwp2cM4i+OsrqffK3VtZ08wrg8VsIl+XHIlVaPXHT9nWgrVQYBHZS4lRugJ5ej1b1dUOHSF3rHKNCYMj6cdlxP3J7cwGhsGAO7DLM9MTKehfHNhnFJPsi7K4WvngOYTDhnU1v7yPWoTuT6+1fgph3COPGLnrhnUz/KCFuHseGiAnHn/cm+AU9X31BIhxprAAAAAElFTkSuQmCC"
preserveAspectRatio="none"
height="16"
width="35"
y="-2.0808461"
x="2.1253965"
transform="rotate(32.792589)" />
</g>
</g>
</g>
<g
inkscape:label="Ebene 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-58.269337,-115.53478)"
style="display:inline">
<g
id="g109"
transform="rotate(-1.1556361,1155.3307,302.61847)">
<g
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;fill:#e87520;fill-opacity:1;stroke:none"
transform="matrix(0.13120595,0,0,0.13120595,69.079628,93.634241)"
font-size="10"
font-weight="400"
font-style="normal"
id="g6701">
<path
style="vector-effect:none;fill-rule:nonzero"
inkscape:connector-curvature="0"
d="M 28.205,3.217 H 6.777 c -2.367,0 -4.286,1.87 -4.286,4.179 v 19.847 c 0,2.308 1.919,4.179 4.286,4.179 h 5.357 l 5.337,13.58 5.377,-13.58 h 5.357 c 2.366,0 4.285,-1.87 4.285,-4.179 V 7.396 c 0,-2.308 -1.919,-4.179 -4.285,-4.179"
id="path6699" />
</g>
<g
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;fill:#231f20;fill-opacity:0.2;stroke:none"
transform="matrix(0.14057447,0,0,-0.14057447,38.479791,112.89298)"
font-size="10"
font-weight="400"
font-style="normal"
id="g6709">
<path
style="vector-effect:none;fill-rule:nonzero"
inkscape:connector-curvature="0"
d="m 244,134 h -20 c -2.209,0 -4,-1.746 -4,-3.9 v -18.525 c 0,-2.154 1.791,-3.9 4,-3.9 h 5 L 233.982,95 239,107.675 h 5 c 2.209,0 4,1.746 4,3.9 V 130.1 c 0,2.154 -1.791,3.9 -4,3.9 m 0,-1 c 1.654,0 3,-1.301 3,-2.9 v -18.525 c 0,-1.599 -1.346,-2.9 -3,-2.9 h -5.68 l -0.25,-0.632 -4.084,-10.318 -4.055,10.316 -0.249,0.634 H 224 c -1.654,0 -3,1.301 -3,2.9 V 130.1 c 0,1.599 1.346,2.9 3,2.9 h 20"
id="path6707" />
</g>
<g
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;fill:#ffffff;fill-opacity:1;stroke:none"
transform="matrix(0.13915785,0,0,0.13915785,68.702265,93.556608)"
font-size="10"
font-weight="400"
font-style="normal"
id="g6729">
<path
style="vector-effect:none;fill-rule:nonzero"
inkscape:connector-curvature="0"
d="m 18.364194,16.757346 c 0.6354,0 1.224,-0.1562 1.7656,-0.4687 0.5417,-0.3125 0.9662,-0.737 1.2735,-1.2735 0.3073,-0.5364 0.4609,-1.1224 0.4609,-1.7578 0,-0.6354 -0.1536,-1.224 -0.4609,-1.7656 -0.3073,-0.5417 -0.7318,-0.9662 -1.2735,-1.2735 -0.5416,-0.3072494 -1.1302,-0.4608994 -1.7656,-0.4608994 -0.6354,0 -1.2214,0.15365 -1.7578,0.4608994 -0.5365,0.3073 -0.9609,0.7318 -1.2734,1.2735 -0.3125,0.5416 -0.4688,1.1302 -0.4688,1.7656 0,0.6354 0.1563,1.2214 0.4688,1.7578 0.3125,0.5365 0.7369,0.961 1.2734,1.2735 0.5364,0.3125 1.1224,0.4687 1.7578,0.4687 m 2.4688,0.875 h -0.4688 c -0.6354,0.2917 -1.2995,0.4375 -1.9922,0.4375 -0.6927,0 -1.362,-0.1458 -2.0078,-0.4375 h -0.4531 c -0.6563,0 -1.2683,0.1641 -1.836,0.4922 -0.5677,0.3281 -1.0156,0.7734 -1.3437,1.3359 -0.3281,0.5625 -0.4922,1.1719 -0.4922,1.8282 v 1.1562 c 0,0.3646 0.1276,0.6745 0.3828,0.9297 0.2552,0.2552 0.5651,0.3828 0.9297,0.3828 h 9.625 c 0.3646,0 0.6745,-0.1276 0.9297,-0.3828 0.2552,-0.2552 0.3828,-0.5651 0.3828,-0.9297 v -1.1562 c 0,-0.6563 -0.1641,-1.2657 -0.4922,-1.8282 -0.3281,-0.5625 -0.7734,-1.0078 -1.3359,-1.3359 -0.5625,-0.3281 -1.1719,-0.4922 -1.8281,-0.4922"
id="path6727" />
</g>
</g>
</g>
<g
inkscape:groupmode="layer"
id="layer6"
inkscape:label="Ebene 3"
style="display:inline"
transform="translate(4.6675853,46.091266)">
<g
id="g99"
transform="rotate(58.747582,-19.440046,-29.612246)">
<g
id="g678"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.12723008,0,0,0.12723008,-15.000979,-62.923636)"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#008e5b;fill-opacity:1;stroke:none">
<path
id="path676"
d="m 17.5,2.746 c -8.284,0 -15,6.853 -15,15.307 0,0.963 0.098,1.902 0.265,2.816 0.36664,2.0268 1.13579,3.9595 2.262,5.684 l 0.134,0.193 12.295,17.785 12.439,-17.863 0.056,-0.08 c 1.2093,-1.8462 2.0083,-3.9305 2.343,-6.112 0.123,-0.791 0.206,-1.597 0.206,-2.423 0,-8.454 -6.716,-15.307 -15,-15.307"
inkscape:connector-curvature="0"
style="vector-effect:none;fill-rule:nonzero" />
</g>
<g
id="g684"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.12723008,0,0,0.12723008,-15.000979,-62.923636)"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#2a71b0;fill-opacity:1;stroke:none">
<path
id="path682"
d="m 17.488,2.748 c -8.284,0 -15,6.853 -15,15.307 0,0.963 0.098,1.902 0.265,2.816 0.36664,2.0268 1.13579,3.9595 2.262,5.684 l 0.134,0.193 12.295,17.785 12.44,-17.863 0.055,-0.08 c 1.2093,-1.8462 2.0083,-3.9305 2.343,-6.112 0.124,-0.791 0.206,-1.597 0.206,-2.423 0,-8.454 -6.716,-15.307 -15,-15.307 m 0,1.071 c 7.68,0 13.929,6.386 13.929,14.236 0,0.685 -0.064,1.423 -0.193,2.258 -0.325,2.075 -1.059,3.99 -2.164,5.667 L 29.005,26.058 17.448,42.653 6.032,26.14 5.912,25.966 C 4.86177,24.3621 4.14583,22.5629 3.807,20.676 3.64649,19.8118 3.56383,18.935 3.56,18.056 3.56,10.205 9.809,3.819 17.488,3.819"
inkscape:connector-curvature="0"
style="vector-effect:none;fill-rule:nonzero;fill:#008000" />
</g>
<g
id="g702"
font-style="normal"
font-weight="400"
font-size="10"
transform="matrix(0.13915785,0,0,0.13915785,-15.279297,-63.122493)"
style="font-style:normal;font-weight:400;font-size:10px;font-family:Ubuntu;display:inline;fill:#ffffff;fill-opacity:1;stroke:none">
<path
id="path700"
d="m 18,9.96875 c -1.2187,0 -2.349,0.30465 -3.3906,0.91405 -1.0417,0.6094 -1.8672,1.4349 -2.4766,2.4766 -0.6094,1.0416 -0.914,2.1719 -0.914,3.3906 0,1.2188 0.3046,2.349 0.914,3.3906 0.6094,1.0417 1.4349,1.8672 2.4766,2.4766 1.0416,0.6094 2.1719,0.9141 3.3906,0.9141 1.2188,0 2.349,-0.3047 3.3906,-0.9141 1.0417,-0.6094 1.8672,-1.4349 2.4766,-2.4766 0.6094,-1.0416 0.9141,-2.1718 0.9141,-3.3906 0,-1.2187 -0.3047,-2.349 -0.9141,-3.3906 C 23.2578,12.3177 22.4323,11.4922 21.3906,10.8828 20.349,10.2734 19.2188,9.96875 18,9.96875"
inkscape:connector-curvature="0"
style="vector-effect:none;fill-rule:nonzero" />
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 22 KiB

BIN
frontend/public/bg1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 KiB

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" fill="currentColor" viewBox="0 0 20 20" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
</defs>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Dribbble-Light-Preview" transform="translate(-140.000000, -7559.000000)" fill="currentColor">
<g id="icons" transform="translate(56.000000, 160.000000)">
<path d="M94,7399 C99.523,7399 104,7403.59 104,7409.253 C104,7413.782 101.138,7417.624 97.167,7418.981 C96.66,7419.082 96.48,7418.762 96.48,7418.489 C96.48,7418.151 96.492,7417.047 96.492,7415.675 C96.492,7414.719 96.172,7414.095 95.813,7413.777 C98.04,7413.523 100.38,7412.656 100.38,7408.718 C100.38,7407.598 99.992,7406.684 99.35,7405.966 C99.454,7405.707 99.797,7404.664 99.252,7403.252 C99.252,7403.252 98.414,7402.977 96.505,7404.303 C95.706,7404.076 94.85,7403.962 94,7403.958 C93.15,7403.962 92.295,7404.076 91.497,7404.303 C89.586,7402.977 88.746,7403.252 88.746,7403.252 C88.203,7404.664 88.546,7405.707 88.649,7405.966 C88.01,7406.684 87.619,7407.598 87.619,7408.718 C87.619,7412.646 89.954,7413.526 92.175,7413.785 C91.889,7414.041 91.63,7414.493 91.54,7415.156 C90.97,7415.418 89.522,7415.871 88.63,7414.304 C88.63,7414.304 88.101,7413.319 87.097,7413.247 C87.097,7413.247 86.122,7413.234 87.029,7413.87 C87.029,7413.87 87.684,7414.185 88.139,7415.37 C88.139,7415.37 88.726,7417.2 91.508,7416.58 C91.513,7417.437 91.522,7418.245 91.522,7418.489 C91.522,7418.76 91.338,7419.077 90.839,7418.982 C86.865,7417.627 84,7413.783 84,7409.253 C84,7403.59 88.478,7399 94,7399" id="github-[#142]">
</path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-opencollective" viewBox="0 0 16 16">
<path fill-opacity=".4" d="M12.995 8.195c0 .937-.312 1.912-.78 2.693l1.99 1.99c.976-1.327 1.6-2.966 1.6-4.683 0-1.795-.624-3.434-1.561-4.76l-2.068 2.028c.468.781.78 1.679.78 2.732h.04Z"/>
<path d="M8 13.151a4.995 4.995 0 1 1 0-9.99c1.015 0 1.951.273 2.732.82l1.95-2.03a7.805 7.805 0 1 0 .04 12.449l-1.951-2.03a5.072 5.072 0 0 1-2.732.781H8Z"/>
</svg>

After

Width:  |  Height:  |  Size: 487 B

111
frontend/public/style.css Normal file
View File

@ -0,0 +1,111 @@
body {
height: 100vh;
overflow: hidden;
}
#root {
width: 100%;
height: 100%;
}
.pulse-loader {
animation: pulse 2.5s infinite ease-in-out;
}
@keyframes pulse {
30% {
transform: scale(1);
}
50% {
transform: scale(1.1);
}
70% {
transform: scale(1);
}
}
.loader {
width: 10px;
height: 10px;
border-radius: 50%;
display: block;
margin: 15px auto;
position: relative;
color: #FFF;
left: -120px;
box-sizing: border-box;
animation: shadowRolling 2s linear infinite;
}
@keyframes shadowRolling {
0% {
box-shadow: 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
}
12% {
box-shadow: 100px 0 lightgray, 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
}
25% {
box-shadow: 110px 0 lightgray, 100px 0 lightgray, 0px 0 rgba(255, 255, 255, 0), 0px 0 rgba(255, 255, 255, 0);
}
36% {
box-shadow: 120px 0 lightgray, 110px 0 lightgray, 100px 0 lightgray, 0px 0 rgba(255, 255, 255, 0);
}
50% {
box-shadow: 130px 0 lightgray, 120px 0 lightgray, 110px 0 lightgray, 100px 0 lightgray;
}
62% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 130px 0 lightgray, 120px 0 lightgray, 110px 0 lightgray;
}
75% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 130px 0 lightgray, 120px 0 lightgray;
}
87% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 130px 0 lightgray;
}
100% {
box-shadow: 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0), 200px 0 rgba(255, 255, 255, 0);
}
}
.opacity {
opacity: 0.8;
}
.outer {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center; /* Zentriert die Items horizontal */
justify-content: center; /* Zentriert die Items vertikal */}
.inner {
height: 96px;
color: lightgray;
display: grid;
place-items: center;
}
@keyframes fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fade-in {
animation: fade-in 1.5s ease-in;
}

41
frontend/src/App.css Normal file
View File

@ -0,0 +1,41 @@
html, body, #root {
margin: 0;
padding: 0;
height: 100%;
overflow: hidden;
}
.App {
width: 100vw;
height: 100dvh;
overflow: hidden;
}
#app-content {
height: 100%;
}
.fadeable-div {
transition: opacity 1000ms ease;
opacity: 1;
}
.div-hidden {
opacity: 0;
}
.movable-div {
transition: transform 600ms ease;
transform: translateX(0);
}
.move-out-right {
transform: translateX(100vw);
/* Verschiebt das Div um die Breite des Viewports */
}
.move-out-left {
transform: translateX(-100vw);
/* Verschiebt das Div um die Breite des Viewports */
}

266
frontend/src/App.tsx Normal file
View File

@ -0,0 +1,266 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable import/order */
/* eslint-disable eqeqeq */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-floating-promises */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/prefer-optional-chain */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-unsafe-call */
import type { Tag } from 'utopia-ui'
import {
AppShell,
SideBar,
Content,
AuthProvider,
Modal,
LoginPage,
SignupPage,
Quests,
RequestPasswordPage,
SetNewPasswordPage,
OverlayItemsIndexPage,
Permissions,
Tags,
SelectUser,
AttestationForm,
MarketView,
SVG,
LoadingMapOverlay,
} from 'utopia-ui'
import { Route, Routes } from 'react-router-dom'
import './App.css'
import { lazy, Suspense, useEffect, useState } from 'react'
import { assetsApi } from './api/assetsApi'
import { itemsApi } from './api/itemsApi'
import { layersApi } from './api/layersApi'
import { mapApi } from './api/mapApi'
import { permissionsApi } from './api/permissionsApi'
import { userApi } from './api/userApi'
import { ModalContent } from './ModalContent'
import { Landingpage } from './pages/Landingpage'
import MapContainer from './pages/MapContainer'
import { getBottomRoutes, routes } from './routes/sidebar'
function App() {
const [permissionsApiInstance, setPermissionsApiInstance] = useState<permissionsApi>()
const [tagsApi, setTagsApi] = useState<itemsApi<Tag>>()
const [mapApiInstance, setMapApiInstance] = useState<mapApi>()
const [layersApiInstance, setLayersApiInstance] = useState<layersApi>()
const [attestationApi, setAttestationApi] = useState<itemsApi<any>>()
const [map, setMap] = useState<any>()
const [layers, setLayers] = useState<any>()
const [layerPageRoutes, setLayerPageRoutes] = useState<any>()
const [loading, setLoading] = useState<boolean>(true)
const [embedded, setEmbedded] = useState<boolean>(true)
useEffect(() => {
const params = new URLSearchParams(location.search)
const embedded = params.get('embedded')
embedded !== 'true' && setEmbedded(false)
}, [location])
useEffect(() => {
setPermissionsApiInstance(new permissionsApi())
setMapApiInstance(new mapApi(window.location.origin))
setAttestationApi(new itemsApi<any>('attestations'))
}, [])
useEffect(() => {
mapApiInstance && getMap()
}, [mapApiInstance])
const getMap = async () => {
const map = await mapApiInstance?.getItems()
map && setMap(map)
map && map != 'null' && setLayersApiInstance(new layersApi(map.id))
map && map != 'null' && map.own_tag_space
? setTagsApi(new itemsApi<Tag>('tags', undefined, map.id))
: setTagsApi(new itemsApi<Tag>('tags'))
}
useEffect(() => {
layersApiInstance && getLayers()
}, [layersApiInstance])
const getLayers = async () => {
const layers = await layersApiInstance?.getItems()
layers && setLayers(layers)
setLayerPageRoutes(
layers
?.filter((l: any) => l.listed)
.map((l: any) => ({
path: '/' + l.name, // url
icon: (
<SVG
src={'https://api.utopia-lab.org/assets/' + l.indexIcon}
className='w-6 h-6'
preProcessor={(code: string) =>
code.replace(/stroke=".*?"/g, 'stroke="currentColor"')
}
/>
),
name: l.name, // name that appear in Sidebar
})),
)
}
useEffect(() => {
if (map && map.name) {
document.title = map?.name && map.name
let link: HTMLLinkElement = document.querySelector("link[rel~='icon']")!
if (!link) {
link = document.createElement('link')
link.rel = 'icon'
document.getElementsByTagName('head')[0].appendChild(link)
}
link.href = map?.logo && 'https://api.utopia-lab.org/assets/' + map.logo // Specify the path to your favicon
}
setLoading(false)
}, [map])
const currentUrl = window.location.href
const bottomRoutes = getBottomRoutes(currentUrl)
const ProfileForm = lazy(() =>
import('utopia-ui/Profile').then((mod) => ({
default: mod.ProfileForm,
})),
)
const ProfileView = lazy(() =>
import('utopia-ui/Profile').then((mod) => ({
default: mod.ProfileView,
})),
)
const UserSettings = lazy(() =>
import('utopia-ui/Profile').then((mod) => ({
default: mod.UserSettings,
})),
)
if (map && layers)
return (
<div className='App overflow-x-hidden'>
<AuthProvider userApi={new userApi()}>
<AppShell
assetsApi={new assetsApi('https://api.utopia-lab.org/assets/')}
appName={map.name}
embedded={embedded}
openCollectiveApiKey={import.meta.env.VITE_OPEN_COLLECTIVE_API_KEY}
>
<Permissions
api={permissionsApiInstance}
adminRole='8ed0b24e-3320-48cd-8444-bc152304e580'
></Permissions>
{tagsApi && <Tags api={tagsApi}></Tags>}
<Modal>
<ModalContent map={map} />
</Modal>
<SideBar routes={[...routes, ...layerPageRoutes]} bottomRoutes={bottomRoutes} />
<Content>
<Quests />
<Routes>
<Route path='/*' element={<MapContainer map={map} layers={layers} />}>
<Route path='login' element={<LoginPage />} />
<Route path='signup' element={<SignupPage />} />
<Route
path='reset-password'
element={<RequestPasswordPage resetUrl={map.url + '/set-new-password/'} />}
/>
<Route path='set-new-password' element={<SetNewPasswordPage />} />
<Route
path='item/*'
element={
<Suspense fallback={<LoadingMapOverlay />}>
<ProfileView attestationApi={attestationApi} />
</Suspense>
}
/>
<Route
path='edit-item/*'
element={
<Suspense fallback={<LoadingMapOverlay />}>
<ProfileForm />
</Suspense>
}
/>
<Route
path='user-settings'
element={
<Suspense fallback={<LoadingMapOverlay />}>
<UserSettings />
</Suspense>
}
/>
<Route path='landingpage' element={<Landingpage />} />
<Route path='market' element={<MarketView />} />
<Route path='select-user' element={<SelectUser />} />
{/* <Route
path='onboarding'
element={
<MapOverlayPage
backdrop
className='max-w-[calc(100vw-32px)] md:max-w-md h-[calc(100vh-96px)] md:h-fit'
>
<Onboarding />
</MapOverlayPage>
}
/> */}
<Route
path='attestation-form'
element={<AttestationForm api={attestationApi} />}
/>
{layers.map((l: any) => (
<Route
key={l.id}
path={l.name}
element={
<OverlayItemsIndexPage
plusButton={l.index_plus_button}
layerName={l.name}
url={'/item/'}
parameterField={'id'}
/>
}
/>
))}
</Route>
</Routes>
</Content>
</AppShell>
</AuthProvider>
</div>
)
else if (map == 'null' && !loading)
return (
<div className='flex items-center justify-center h-screen'>
<div>
<p className='text-xl font-semibold'>This map does not exist</p>
</div>
</div>
)
else
return (
<div className='outer'>
<img className='pulse-loader opacity h-[96px]' src='/3markers-globe.svg' />
<br />
<span className='loader'></span>
</div>
)
}
export default App

View File

@ -0,0 +1,184 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/restrict-plus-operands */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import { useEffect, useState } from 'react'
import { TextView } from 'utopia-ui'
interface ChapterProps {
clickAction1?: () => void
clickAction2?: () => void
map?: any
}
export function Welcome1({ clickAction1, map }: ChapterProps) {
return (
<>
{map.custom_text ? (
<>
<TextView rawText={map.custom_text}></TextView>
</>
) : (
<>
<h3 className='font-bold text-lg'>Welcome to {map?.name || 'Utopia Map'}</h3>
<img
className='float-right w-32 m-2'
src={'https://api.utopia-lab.org/assets/' + map.logo}
></img>
<p className='py-3'>
It is a tool for collaborative mapping to connect local initiatives, people and events.
</p>
<p className='py-1'>
Join us and grow the network by adding projects and events to the map.
</p>
<p className='py-1'>Create your personal profile and place it on the map.</p>
<div className='grid'>
<label className='btn btn-primary place-self-end mt-4' onClick={() => clickAction1!()}>
Close
</label>
</div>
</>
)}
</>
)
}
export function Welcome2({ clickAction1 }: ChapterProps) {
return (
<>
<h3 className='font-bold text-lg'> Dencentralized Networking</h3>
<img className='float-right w-32 mx-4 my-2' src='/markers-circle.svg'></img>
<p className='py-3'>
Find like-minded people, projects and events. In your neighbourhood and wherever you are!
</p>
<p className='py-3'>Onboard new people, places and events</p>
<div className='grid'>
<button className='btn place-self-end mt-4' onClick={() => clickAction1!()}>
next
</button>
</div>
</>
)
}
export function Welcome3({ clickAction1 }: ChapterProps) {
return (
<>
<h3 className='font-bold text-lg'>Mapping the Change</h3>
<p className='py-3'>More and more people are waking up to what's really happening. </p>
<p className='py-1'>
They are in the process of understanding the potential that is within themselves and within
the whole mankind.
</p>
<img className='float-left w-32 mx-4' src='/3markers-globe.svg'></img>
<p className='py-1'>
Starting to reconnect with our Mother Earth and beginning to question things that long times
have been taken for granted.
</p>
<div className='grid'>
<label className='btn place-self-end mt-4' onClick={() => clickAction1!()}>
next
</label>
</div>
</>
)
}
export function Welcome4({ clickAction1 }: ChapterProps) {
return (
<>
<h3 className='font-bold text-lg'> Dezentralized Networks </h3>
<p className='py-3'>
Find like-minded people, places and events. In your neighbourhood and wherever you are!
</p>
<img className='float-right w-32 mx-4 my-2' src='/network.svg'></img>
<p className='py-1'>
Hypnotised, they sit in front of screens in concrete blocks, flooded and disillusioned by
irrelevant information.
</p>
<p className='py-1'>
From an early age, they are trained to do alienated work and consume unhealthy and
meaningless products.
</p>
<div className='grid'>
<button className='btn place-self-end mt-4' onClick={() => clickAction1!()}>
next
</button>
</div>
</>
)
}
const close = () => {
const myModal = document.getElementById('my_modal_3') as HTMLDialogElement
myModal.close()
}
export const ModalContent = ({ map }: { map: any }) => {
useEffect(() => {
const myModal = document.getElementById('my_modal_3') as HTMLDialogElement
if (map.info_open) {
myModal.showModal()
}
}, [map.info_open])
const [chapter, setChapter] = useState<number>(1)
// const setQuestsOpen = useSetQuestOpen()
const ActiveChapter = () => {
switch (chapter) {
case 1:
return (
<Welcome1
map={map}
clickAction1={() => {
close()
setTimeout(() => {
// setQuestsOpen(true);
setChapter(1)
}, 1000)
}}
/>
)
case 2:
return (
<Welcome2
clickAction1={() => {
setChapter(3)
}}
/>
)
case 3:
return (
<Welcome3
clickAction1={() => {
setChapter(4)
}}
/>
)
case 4:
return (
<Welcome4
clickAction1={() => {
close()
setTimeout(() => {
// setQuestsOpen(true);
setChapter(1)
}, 1000)
}}
/>
)
default:
return <></>
}
}
return <ActiveChapter />
}

View File

@ -0,0 +1,28 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-console */
import { uploadFiles } from '@directus/sdk'
import { directusClient } from './directus'
import type { AssetsApi } from 'utopia-ui'
export class assetsApi implements AssetsApi {
url: string
constructor(url: string) {
this.url = url
}
async upload(file: Blob, title: string) {
const formData = new FormData()
formData.append('title', title)
formData.append('file', file)
try {
return await directusClient.request(uploadFiles(formData))
} catch (error: any) {
console.log(error)
throw error
}
}
}

View File

@ -0,0 +1,93 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/require-await */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createDirectus, rest, authentication } from '@directus/sdk'
import type { AuthenticationData, AuthenticationStorage } from '@directus/sdk'
import type { Point } from 'geojson'
export interface Place {
id: string
name: string
text: string
position?: Point
}
export interface Project {
id: string
name: string
text: string
position?: Point
picture: string
subname: string
[key: string]: any
}
export interface Tag {
id: string
color: string
}
export interface Event {
id: string
name: string
text: string
position?: Point
start: Date
end: Date
}
export interface Update {
id: string
text: string
position?: Point
user_created: string
date_created: string
}
interface CustomUserFields {
position: Point
}
export interface MyCollections {
places: Place[]
events: Event[]
updates: Update[]
tags: Tag[]
projects: Project[]
directus_users: CustomUserFields[]
}
export const authLocalStorage = (mainKey = 'directus_storage') =>
({
// implementation of get, here return json parsed data from localStorage at mainKey (or null if not found)
get: async () => {
const data = window.localStorage.getItem(mainKey)
if (data) {
return JSON.parse(data)
}
return null
},
// implementation of set, here set the value at mainKey in localStorage, or remove it if value is null
set: async (value: AuthenticationData | null) => {
if (!value) {
return window.localStorage.removeItem(mainKey)
}
return window.localStorage.setItem(mainKey, JSON.stringify(value))
},
}) as AuthenticationStorage
export async function getRefreshToken() {
const auth = await authLocalStorage().get()
return auth!.refresh_token
}
export const directusClient = createDirectus<MyCollections>('https://api.utopia-lab.org/')
.with(rest())
.with(
authentication('json', {
// add this if you want to use authentication, json is important, it's type of your authentication usage, here JWT
storage: authLocalStorage(), // here set the storage previously created
}),
)

View File

@ -0,0 +1,125 @@
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
import { createItem, deleteItem, readItem, readItems, updateItem } from '@directus/sdk'
import { directusClient } from './directus'
import type { MyCollections } from './directus'
import type { ItemsApi } from 'utopia-ui'
export class itemsApi<T> implements ItemsApi<T> {
collectionName: string
filter: any
layerId: string | undefined
mapId: string | undefined
customParameter: any
constructor(
collectionName: string,
layerId?: string | undefined,
mapId?: string | undefined,
filter?: any,
customParameter?: any,
) {
this.collectionName = collectionName
if (filter) this.filter = filter
else this.filter = {}
this.layerId = layerId
if (layerId) {
this.filter = { ...filter, ...{ layer: { id: { _eq: layerId } } } }
}
this.mapId = mapId
if (mapId) {
this.filter = { ...filter, ...{ map: { id: { _eq: mapId } } } }
}
if (customParameter) this.customParameter = customParameter
}
async getItems(): Promise<T[]> {
try {
const result = await directusClient.request<T[]>(
readItems(this.collectionName as never, {
fields: [
'*',
'to.*',
'relations.*',
'user_created.*',
'markerIcon.*',
{ offers: ['*'], needs: ['*'], gallery: ['*.*'] } as any,
],
filter: this.filter,
limit: -1,
}),
)
return result
} catch (error: any) {
console.error(error)
if (error.errors?.[0]?.message) {
throw new Error(error.errors[0].message)
} else {
throw error
}
}
}
async getItem(id: string): Promise<T> {
try {
const result = await directusClient.request(readItem(this.collectionName as never, id))
return result as T
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
async createItem(item: T & { id?: string }): Promise<T> {
try {
const result = await directusClient.request(
createItem(this.collectionName as keyof MyCollections, {
...item,
...(this.customParameter && this.customParameter),
...(this.layerId && { layer: this.layerId }),
...(this.layerId && { layer: this.layerId }),
...(this.mapId && { map: this.mapId }),
}),
)
return result as T
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
async updateItem(item: T & { id?: string }): Promise<T> {
try {
const result = await directusClient.request(
updateItem(this.collectionName as keyof MyCollections, item.id!, item),
)
return result as T
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async deleteItem(id: string): Promise<boolean> {
try {
const result = await directusClient.request(
deleteItem(this.collectionName as keyof MyCollections, id),
)
return result as unknown as boolean
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
}

View File

@ -0,0 +1,35 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { readItems } from '@directus/sdk'
import { directusClient } from './directus'
export class layersApi {
mapId: string
constructor(mapId: string) {
this.mapId = mapId
}
async getItems() {
try {
const layers = await directusClient.request(
readItems('layers' as any, {
fields: [
'*',
{ itemType: ['*.*', { profileTemplate: ['*', 'item.*.*.*.*'] }] },
{ markerIcon: ['*'] } as any,
],
filter: { maps: { maps_id: { id: { _eq: this.mapId } } } },
limit: 500,
}),
)
return layers
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
}

View File

@ -0,0 +1,33 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { readItems } from '@directus/sdk'
import { directusClient } from './directus'
export class mapApi {
url: string
constructor(url: string) {
this.url = url
}
async getItems() {
try {
const map = await directusClient.request(
readItems('maps' as any, {
fields: ['*', { user_type: ['name'] }],
filter: { url: { _eq: this.url } } as any,
limit: 500,
}),
)
if (map[0]) return map[0]
else return 'null'
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
}

View File

@ -0,0 +1,27 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-useless-constructor */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
import { readPermissions } from '@directus/sdk'
import { directusClient } from './directus'
import type { ItemsApi, Permission } from 'utopia-ui'
export class permissionsApi implements ItemsApi<Permission> {
constructor() {}
async getItems(): Promise<Permission[]> {
try {
const result = await directusClient.request(
readPermissions({ fields: ['*', { policy: ['name', 'roles'] } as any] }),
)
return result as unknown as Permission[]
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
}

View File

@ -0,0 +1,18 @@
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
import { readUser } from '@directus/sdk'
import { directusClient } from './directus'
export class readUserApi {
async getItem(id: string) {
try {
return await directusClient.request(readUser(id))
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
}

View File

@ -0,0 +1,29 @@
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-unsafe-return */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable @typescript-eslint/no-explicit-any */
import axios from 'axios'
import type { ItemsApi } from 'utopia-ui'
export class refiBcnApi implements ItemsApi<any> {
collectionName: string
constructor(collectionName: string) {
this.collectionName = collectionName
}
async getItems() {
try {
return (
await axios.get(
'https://antontranelis.github.io/ReFi-Barcelona-Prototype/projects/index.json',
)
).data.data
} catch (error: any) {
console.log(error)
if (error.errors[0]?.message) throw error.errors[0].message
else throw error
}
}
}

118
frontend/src/api/userApi.ts Normal file
View File

@ -0,0 +1,118 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable camelcase */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable no-console */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-return */
import { createUser, passwordRequest, passwordReset, readMe, updateMe } from '@directus/sdk'
import { directusClient } from './directus'
import type { UserApi, UserItem } from 'utopia-ui'
interface DirectusError {
errors: {
message: string
[key: string]: any
}[]
}
export class userApi implements UserApi {
async register(email: string, password: string, userName: string): Promise<any> {
try {
return await directusClient.request(createUser({ email, password, first_name: userName }))
} catch (error: unknown) {
console.error('Registration error:', error)
if (
typeof error === 'object' &&
error !== null &&
'errors' in error &&
Array.isArray((error as any).errors)
) {
const directusError = error as DirectusError
const errorMessage = directusError.errors[0]?.message
if (errorMessage.includes('has to be unique')) {
throw new Error('This e-mail address is already registered.')
}
throw new Error(errorMessage || 'Unknown error during registration.')
}
throw error
}
}
async login(email: string, password: string): Promise<any> {
try {
return await directusClient.login(email, password, { mode: 'json' })
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async logout(): Promise<any> {
try {
return await directusClient.logout()
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async getUser(): Promise<any> {
try {
const user = await directusClient.request(readMe({ fields: ['*', { role: ['*'] } as any] }))
return user
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async getToken(): Promise<any> {
try {
const token = await directusClient.getToken()
return token
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async updateUser(user: UserItem): Promise<any> {
const { id, ...userRest } = user
try {
const res = await directusClient.request(updateMe(userRest, { fields: ['*'] }))
return res as any
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async requestPasswordReset(email: string, reset_url?: string): Promise<any> {
try {
return await directusClient.request(passwordRequest(email, reset_url))
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
async passwordReset(reset_token: string, new_password: string): Promise<any> {
try {
return await directusClient.request(passwordReset(reset_token, new_password))
} catch (error: any) {
console.log(error)
if (error.errors[0].message) throw error.errors[0].message
else throw error
}
}
}

8
frontend/src/index.css Normal file
View File

@ -0,0 +1,8 @@
@import 'tailwindcss';
@plugin "daisyui" {
themes: light --default, dark --prefersdark, valentine, retro, aqua, cyberpunk, caramellatte, abyss, silk;
}
@theme {
--animate-fade: fadeOut 1s ease-in-out;
}

15
frontend/src/main.tsx Normal file
View File

@ -0,0 +1,15 @@
/* eslint-disable import/default */
/* eslint-disable import/extensions */
/* eslint-disable import/no-named-as-default-member */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

View File

@ -0,0 +1,98 @@
/* eslint-disable import/default */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline'
import {
add,
eachDayOfInterval,
endOfMonth,
endOfWeek,
format,
getDay,
isSameMonth,
isToday,
parse,
startOfToday,
startOfWeek,
} from 'date-fns'
import React, { useState } from 'react'
import { MapOverlayPage } from 'utopia-ui'
export const Calendar = () => {
const today = startOfToday()
const days = ['sun', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat']
const colStartClasses = [
'',
'col-start-2',
'col-start-3',
'col-start-4',
'col-start-5',
'col-start-6',
'col-start-7',
]
const [currMonth, setCurrMonth] = useState(() => format(today, 'MMM-yyyy'))
const firstDayOfMonth = parse(currMonth, 'MMM-yyyy', new Date())
const daysInMonth = eachDayOfInterval({
start: startOfWeek(firstDayOfMonth),
end: endOfWeek(endOfMonth(firstDayOfMonth)),
})
const getPrevMonth = (event: React.MouseEvent<SVGSVGElement>) => {
event.preventDefault()
const firstDayOfPrevMonth = add(firstDayOfMonth, { months: -1 })
setCurrMonth(format(firstDayOfPrevMonth, 'MMM-yyyy'))
}
const getNextMonth = (event: React.MouseEvent<SVGSVGElement>) => {
event.preventDefault()
const firstDayOfNextMonth = add(firstDayOfMonth, { months: 1 })
setCurrMonth(format(firstDayOfNextMonth, 'MMM-yyyy'))
}
return (
<MapOverlayPage
backdrop
className='tw-max-h-[calc(100dvh-96px)] tw-h-fit md:tw-w-[calc(50%-32px)] tw-w-[calc(100%-32px)] max-w-lg'
>
<div className='flex items-center justify-between'>
<p className='font-semibold text-xl'>{format(firstDayOfMonth, 'MMMM yyyy')}</p>
<div className='flex items-center justify-evenly gap-6 sm:gap-12'>
<ChevronLeftIcon className='w-6 h-6 cursor-pointer' onClick={getPrevMonth} />
<ChevronRightIcon className='w-6 h-6 cursor-pointer' onClick={getNextMonth} />
</div>
</div>
<hr className='my-6' />
<div className='grid grid-cols-7 gap-6 sm:gap-12 place-items-center'>
{days.map((day, idx) => {
return (
<div key={idx} className='font-semibold'>
{capitalizeFirstLetter(day)}
</div>
)
})}
</div>
<div className='grid grid-cols-7 gap-4 sm:gap-12 mt-8 place-items-center'>
{daysInMonth.map((day, idx) => {
return (
<div key={idx} className={colStartClasses[getDay(day)]}>
<p
className={`cursor-pointer flex items-center justify-center font-semibold h-8 w-8 rounded-full hover:text-white ${
isSameMonth(day, today) ? 'text-current' : 'text-gray-500'
} ${!isToday(day) && 'hover:bg-primary-content'} ${
isToday(day) && 'bg-primary text-white!'
}`}
>
{format(day, 'd')}
</p>
</div>
)
})}
</div>
</MapOverlayPage>
)
}
const capitalizeFirstLetter = (string: string) => {
return string
}

View File

@ -0,0 +1,188 @@
/* eslint-disable react/no-unescaped-entities */
import { CardPage } from 'utopia-ui'
export default function Concept() {
return (
<CardPage title='Concept'>
Utopia is a cooperative Real Life Manifestation Game. While playing, we connect with
ourselves, each other and our dreams to manifest them together.<br></br>
<br></br>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Real Life Manifestation Games </div>
<div className='collapse-content'>
<ul className='list-disc list-inside pl-4'>
<li>
Like a role-playing game, you can create your own profile, but here you can map, share
and train real skills.
</li>
<li>
Further, real resources are made visible and available, managed and used, similar to a
strategy game.
</li>
<li>
Project management tasks can be mapped as quests, levels, missions and problems become
challenges.
</li>
<li>The storytelling is based on the real conditions and challenges on our planet.</li>
<li>
The goal of the game is to create win-win-win situations. Win for you, win for us, win
for the world.
</li>
</ul>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Elements</div>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> The App</h3>
The app provides an interactive geographical map as a playing field. It also allows
you to create and view player profiles. The marketplace shows offers and needs.{' '}
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Print Material</h3>
To complement offline play, there are flyers, stickers, signs and workbooks that
invite players to play. Players receive or print ID cards with QR codes that are used
for networking (more on this later).
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Gatherings</h3>
Coming together at workshops, festivals and local meetings to connect, build
structures and to engage new players.{' '}
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Permanent Structures</h3>
When we play, we create tangible structures like places and infrastructure. And also
intangibles like networks of relationships, stories, information ...
</div>
</div>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Goals </div>
<div className='collapse-content'>
<ol className='list-decimal list-inside pl-4'>
<li>To build a decentralised network</li>
<li>Free development of our collective and individual potential</li>
<li>Start co-creation and build collective structures</li>
</ol>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Gameplay </div>
<div className='collapse-content'>
Through playful elements and gamification, the player is motivated and guided through
quests and levels.
<h3 className='text-base my-3 font-medium'> Player Profiles</h3>
The player examines himself and his abilities as well as deeper desires and visions to
define his character or player profile. The focus is on the following questions:
<ul className='list-disc list-inside pl-4 pt-4'>
<li>How and in what kind of world do I want to live?</li>
<li>Who am I and what are my special abilities or my special task in this life?</li>
<li>
What do I have to give? What can and do I want to share with others and the world?
</li>
<li>
What do I still need to come fully into my power? How can others support me in this?
</li>
</ul>
<h3 className='text-base my-3 font-medium'> Resources</h3>
The player explores and defines his/her offers and needs, shares his/her resources and
uses those of the network. E.g. tools, machines, electrical appliances, vehicles, food and
drink, places to sleep, rides, books, access to the internet, individual skills and help
in everyday life
<h3 className='text-base my-3 font-medium'> Realising Projects</h3>
The player joins projects and starts his own. The game offers support in project
management or crowdfunding. In this way, structures, events, permanent places,
infrastructure and everything we need to meet our human needs in harmony with Mother Earth
can be created.
<h3 className='text-base my-3 font-medium'> Making Change visible</h3>
The player is motivated to map and document the newly emerging world by ...
<ul className='list-disc list-inside pl-4 pt-4'>
<li>adding places, events etc. to the map</li>
<li>documenting projects with text, images, audio and video</li>
<li>telling stories of change</li>
</ul>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Web of Trust </div>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
While we connect with other people in real life and build our personal network, we are
simultaneously exchanging cryptographic keys and building a "Web of Trust".{' '}
<div className='basis-full'>
<div className='divider divider-vertical'></div>
</div>
<h3 className='text-base basis-full my-3 font-medium'> Decentralised IDs</h3>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<img className='float-right h-28 px-4 pb-4' src='/public-private-key.svg'></img>
<p>
When we create our profile, a key pair consisting of a private key and a public key
is generated at the same time.
</p>
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<p className='basis-full pb-4'>
We share the public key with our friends and they can use it to encrypt data for us.
We keep the private key secret. It is needed to decrypt data that has been encrypted
for us on our device.
</p>
</div>
<div className='basis-full'>
<div className='divider divider-vertical'></div>
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Key Exchange</h3>
<img className='float-left h-32 px-4' src='/qr-scan.svg'></img>
When we meet people in real life, we can exchange our public keys by scanning each
other's QR codes or on paper.{' '}
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Private data sharing</h3>
<img className='float-right h-32 px-4' src='/web-of-trust.svg'></img>
Within our network, we can then share our profiles, offers, needs, projects, locations
and events end-to-end encrypted.
</div>
</div>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<h2 className='collapse-title text-xl font-medium'>Principles </h2>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Everything is just a game</h3>
Everything happens voluntarily, out of ourselves in the flow. Everyone is invited to
join in at any time.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> No Money</h3>
Since the fun stops with money, we don't pay each other money when we play.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Decentralised Structures</h3>
All structures we create are decentralised and free of hierarchies. The same rules
apply to everyone.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Real Life</h3>
Real change happens in real life. We only use the internet where it directly helps to
organise real encounters.{' '}
</div>
</div>
</div>
</div>
</CardPage>
)
}

View File

@ -0,0 +1,203 @@
import { CardPage } from 'utopia-ui'
export default function Concept() {
return (
<CardPage title='Concept'>
<b>Utopia Game</b> is a cooperative <b>Real Life Manifestation Game</b>. While playing, we
connect with ourselves, each other and our dreams to manifest them together.<br></br>
<br></br>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Real-Life-Manifestations-Spiel ? </div>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
<div className='basis-full pr-4 pb-4'>
Ähnlich wie bei einem Rollenspiel kann man sich sein eigenes Profil erstellen, mit dem
Unterschied, dass hier echte Fähigkeiten abgebildet, geteilt und trainiert werden
können.
</div>
<div className='basis-full pr-4 pb-4'>
Die Geschichte und das Storytelling orientiert sich an den realen Zuständen und
Herausforderungen auf unserer Erde.
</div>
<div className='basis-full pr-4 pb-4'>
Des weiteren werden reale Ressourcen sichtbar und verfügbar gemacht, verwaltet und
eingesetzt, ähnlich wie bei einem Strategie-Spiel.
</div>
<div className='basis-full pr-4 pb-4'>
Die Aufgaben des Projektmanagements können als Quests bzw. Spielaufträge abgebildet
werden. Kleine und große Aufgaben werden in Abenteuer und Herausforderungen verwandelt
und Probleme in Rätsel.
</div>
</div>
<div className='basis-full pr-4 pb-4'>
Ziel des Spiels ist es, WinWinWin Situationen zu erzeugen. Win für Dich, Win für Uns,
Win für die Welt.
</div>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Elemente des Spiels </div>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> App</h3>
Die App bietet eine interaktive geografische Karte als Spielfeld. Außerdem ermöglicht
sie das Erstellen und Ansehen von Spieler-Profilen. Der Marktplatz zeigt Angebote und
Bedürfnisse
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Print Material</h3>
Als Ergänzung und zum Offline-Spielen gibt es Flyer, Aufkleber, Schilder und
Workbooks, welche zum Spielen einladen. Spieler erhalten Ausweise und Visitenkarten
mit QR-Codes, die zu Vernetzung genutzt werden (später mehr)
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Temporäre Events</h3>
Wir kommen zusammen bei Workshops, auf Festivals und bei lokalen Treffen um uns zu
connecten, Strukturen zu bilden und neue Spieler zu gewinnen.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Dauerhafte Strukturen</h3>
Beim Spielen erschaffen wir dauerhafte materielle Strukturen wie Orte und
Infrastruktur. Und Immaterielles wie Netzwerke aus Beziehungen, Geschichten,
Informationen ...
</div>
</div>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Ziel des Spiels </div>
<div className='collapse-content'>
<ol className='list-decimal list-inside pl-4'>
<li>Ein dezentrales Netzwerk aufspannen</li>
<li>Freie Entfaltung unserer kollektiven und individuellen Potentiale</li>
<li>Aufbau kollektiver Strukturen und Co-Kreation</li>
</ol>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Ablauf </div>
<div className='collapse-content'>
Durch spielerische Elemente / Gamification wird der Spieler motiviert und angeleitet ...
<h3 className='text-base my-3 font-medium'> Spieler Profile</h3>
Der Spieler setzt sich mit sich selbst und seinen Fähigkeiten sowie tieferen Wünschen und
Visionen auseinander und definiert seinen Charakter bzw. Spieler-Profil Dabei stehen
folgende Fragen im Mittelpunkt:
<ul className='list-disc list-inside pl-4 pt-4'>
<li>Wie und in was für einer Welt möchte ich leben?</li>
<li>
Wer bin ich und was sind meine besonderen Fähigkeiten oder meine spezielle Aufgabe in
diesem Leben?
</li>
<li>Was habe ich zu geben? Was kann und möchte ich mit anderen und der Welt teilen?</li>
<li>
Was brauche ich noch um ganz in meine Kraft zu kommen? Wie können mich andere dabei
unterstützen?
</li>
</ul>
<h3 className='text-base my-3 font-medium'> Ressourcen</h3>
Der Spieler erforscht und definiert seine Angebote und Bedürfnisse, teilt seine Ressourcen
und nutzt die des Netzwerks Z.B. Werkzeuge, Maschinen, Elektrogeräte, Fahrzeuge, Essen und
Trinken, Schlafplätze, Mitfahrten, Bücher, Zugang zum Internet, individuelle Fähigkeiten
sowie Hilfe im Alltag
<h3 className='text-base my-3 font-medium'> Projekte umsetzen</h3>
Der Spieler schließt sich Projekten an und startet selbst welche. Das Spiel unterstützt
beim Projektmanagement oder beim Crowdfunding So entstehen Strukturen, Veranstaltungen,
dauerhafte Orte, Infrastruktur und alles was wir brauchen um unsere menschlichen
Bedürfnisse im Einklang mit Mutter Erde zu befriedigen.
<h3 className='text-base my-3 font-medium'> Wandel sichtbar machen</h3>
Der Spieler wird motiviert die neu entstehende Welt zu kartieren und zu dokumentieren
indem er ...
<ul className='list-disc list-inside pl-4 pt-4'>
<li>Orte, Veranstaltungen usw. in der Karte einträgt</li>
<li>Projekte mit Text, Bild und Ton dokumentiert</li>
<li>Geschichten des Wandels erzählt</li>
</ul>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<div className='collapse-title text-xl font-medium'>Web of Trust </div>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
Während wir uns mit anderen Menschen im echten Leben connecten und unser persönliches
Netzwerk aufbauen, tauschen wir gleichzeitig kryptografische Schlüssel aus und bauen ein
sogenanntes Web of Trust.
<div className='basis-full'>
<div className='divider divider-vertical'></div>
</div>
<h3 className='text-base basis-full my-3 font-medium'> Dezentrale IDs</h3>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<img className='float-right h-24 px-4' src='/public-private-key.svg'></img>
<p>
Wenn wir unser Profil erstellen wird gleichzeitig ein Schlüsselpaar bestehend aus
einem privaten und einem öffentlichen Schlüssel erzeugt.
</p>
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<p className='basis-full pb-4'>
Den öffentlichen Schlüssel teilen wir mit unseren Freunden und diese können damit
Daten für uns verschlüsseln.
</p>
<p className='basis-full'>
Den privaten Schlüssel halten wir geheim. Er wird benötigt um Daten die für uns
verschlüsselt wurden auf unserem Gerät wieder zu entschlüsselt.
</p>
</div>
<div className='basis-full'>
<div className='divider divider-vertical'></div>
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Schlüsseltausch</h3>
<img className='float-left h-32 px-4' src='/qr-scan.svg'></img>
Wenn wir Menschen im echten Leben begegnen tauschen wir unsere öffentlichen Schlüssel
via QR-Code-Scan oder auf Papier.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Private Daten teilen</h3>
<img className='float-right h-32 px-4' src='/web-of-trust.svg'></img>
Innerhalb unseres Netzwerkes können wir dann unsere Profile, Angebote, Bedürfnisse,
Projekte, Orte und Veranstaltungen ende-zu-ende-verschlüsselt teilen.
</div>
</div>
</div>
</div>
<div className='collapse collapse-arrow bg-base-200 mb-2'>
<input type='radio' name='my-accordion-2' />
<h2 className='collapse-title text-xl font-semibold'>Prinzipien </h2>
<div className='collapse-content'>
<div className='flex flex-row flex-wrap'>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Alles ist nur ein Spiel</h3>
Alles passiert freiwillig, aus uns selbst heraus im Flow. Jeder ist jederzeit
eingeladen mitzumachen.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Kein Geld</h3>
Da beim Geld der Spaß bekanntlich aufhört, bezahlen wir uns beim Spielen gegenseitig
kein Geld.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Dezentrale Strukturen</h3>
Alle Strukturen, die wir erschaffen, gestalten wir dezentral und frei von Hierarchien.
Für alle gelten die gleichen Regeln.
</div>
<div className='basis-full md:basis-1/2 pr-4 pb-4'>
<h3 className='text-base my-3 font-medium'> Real Life</h3>
Veränderung passiert im echten Leben. Wir nutzen das Internet nur wo es direkt hilft
echte Begegnungen zu organisieren.
</div>
</div>
</div>
</div>
</CardPage>
)
}

View File

@ -0,0 +1,216 @@
/* eslint-disable react/no-unescaped-entities */
/* eslint-disable @typescript-eslint/restrict-template-expressions */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable import/no-relative-parent-imports */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/* eslint-disable new-cap */
/* eslint-disable react-hooks/exhaustive-deps */
/* eslint-disable @typescript-eslint/no-floating-promises */
import { useEffect, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { MapOverlayPage } from 'utopia-ui'
import { itemsApi } from '../api/itemsApi'
export const Landingpage = () => {
const [isLandingpageVisible, setIsLandingpageVisible] = useState(true)
const [isBoxVisible, setIsBoxVisible] = useState(true)
const [isPhoneVisible, setIsPhoneVisible] = useState(true)
const [featuresApi, setFeaturesApi] = useState<itemsApi<any>>()
const [features, setFeatures] = useState<any[]>()
const [teamApi, setTeamApi] = useState<itemsApi<any>>()
const [team, setTeam] = useState<any[]>()
const loadFeatures = async () => {
const items = await featuresApi?.getItems()
setFeatures(items as any)
}
const loadTeam = async () => {
const items = await teamApi?.getItems()
setTeam(items as any)
}
useEffect(() => {
setFeaturesApi(
new itemsApi<any>('features', undefined, undefined, { status: { _eq: 'published' } }),
)
setTeamApi(new itemsApi<any>('team'))
loadTeam()
loadFeatures()
}, [])
useEffect(() => {
loadFeatures()
}, [featuresApi])
useEffect(() => {
loadTeam()
}, [teamApi])
const navigate = useNavigate()
const startGame = () => {
setTimeout(() => {
setIsBoxVisible(false)
}, 200)
setTimeout(() => {
setIsPhoneVisible(false)
}, 200)
setTimeout(() => {
setIsLandingpageVisible(false)
}, 500)
setTimeout(() => {
navigate('/')
}, 1500)
}
return (
<MapOverlayPage
className={`rounded-none! overflow-y-auto p-0! fadeable-div flex-none ${isLandingpageVisible ? '' : 'div-hidden'}`}
card={false}
>
<div className='hero min-h-full text-base'>
<div className='hero-content text-center flex flex-col place-items-center p-0'>
<div
className='bg-no-repeat bg-center w-full'
style={{ backgroundImage: 'url(bg1.webp)' }}
>
<div className='min-h-[calc(100vh-60px)] flex flex-row items-center justify-center '>
<div
className={`max-w-md text-center bg-black p-8 m-8 bg-opacity-50 text-white backdrop-blur-xs rounded-xl movable-div ${isBoxVisible ? '' : 'move-out-left'}`}
>
<h1 className='text-5xl font-bold'>Utopia Game</h1>
<p className='py-6'>
ist mehr als nur ein Spiel. Es ist eine Bewegung, die darauf abzielt, die Spieler
aus ihren virtuellen Welten zu befreien und sie zu inspirieren, das echte Leben zu
erkunden, Fähigkeiten zu entwickeln und die Welt um sie herum zu gestalten. Bist
du bereit, Teil dieser Revolution zu werden?{' '}
</p>
<div className='btn text-white! btn-primary' onClick={startGame}>
Play
</div>
</div>
<div
className={`mockup-phone m-8 hidden lg:block movable-div ${isPhoneVisible ? '' : 'move-out-right'}`}
>
<div className='camera'></div>
<div className='display'>
my-8
<div className='artboard artboard-demo phone-1'>
<iframe src='/' height={568} width={320}></iframe>
</div>
</div>
</div>
</div>
</div>
<section className='min-h-[50em] p-8 flex h-full items-center justify-center'>
<ul className='my-8 grid gap-y-8 gap-x-12 sm:grid-cols-2 lg:grid-cols-3'>
{features?.map((item, idx) => (
<li key={idx} className='space-y-3'>
<div className='w-12tw-card tw-card-body h-12 mx-auto bg-transparent! text-indigo-600 rounded-full flex items-center justify-center text-5xl'>
{item.symbol}
</div>
<h4 className='text-lg font-semibold'>{item.heading}</h4>
<p>{item.text}</p>
</li>
))}
</ul>
</section>
<section className='py-14 min-h-[40em] p-8 flex h-full items-center justify-center mb-28'>
<div className='max-w-(--breakpoint-xl) mx-auto text-center'>
<div className='max-w-xl mx-auto'>
<h3 className='text-3xl font-semibold sm:text-4xl'>Meet our team</h3>
<p className='mt-3'>
Lorem Ipsum is simply dummy text of the printing and typesetting industry.Lorem
Ipsum has been the industry's standard dummy.
</p>
</div>
<div className='mt-12'>
<ul className='grid gap-8 sm:grid-cols-2 md:grid-cols-3'>
{team?.map((item, idx) => (
<li key={idx}>
<div className='w-24 h-24 mx-auto'>
<img
src={`https://api.utopia-lab.org/assets/${item.image}`}
className='w-full h-full rounded-full'
alt=''
/>
</div>
<div className='mt-2'>
<h4 className='font-semibold sm:text-lg'>{item.name}</h4>
<p className='text-indigo-600'>{item.role}</p>
<p className='mt-2'>{item.text}</p>
<div className='mt-4 flex justify-center gap-4'></div>
</div>
</li>
))}
</ul>
</div>
</div>
</section>
</div>
</div>
<footer className='text-gray-500 bg-base-200 px-4 py-5 w-full mx-auto md:px-8 text-base'>
<div className='mt-8 items-center justify-center flex'>
<div className='mt-6 sm:mt-0'>
<ul className='flex items-center space-x-4'>
<li className='w-8 h-8 border-current bg-white rounded-full flex items-center justify-center'>
<a href='https://t.me/UtopiaMap'>
<svg
stroke='currentColor'
fill='#1d93d2'
strokeWidth='0'
viewBox='0 0 512 512'
height='1.4rem'
width='1.4rem'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M446.7 98.6l-67.6 318.8c-5.1 22.5-18.4 28.1-37.3 17.5l-103-75.9-49.7 47.8c-5.5 5.5-10.1 10.1-20.7 10.1l7.4-104.9 190.9-172.5c8.3-7.4-1.8-11.5-12.9-4.1L117.8 284 16.2 252.2c-22.1-6.9-22.5-22.1 4.6-32.7L418.2 66.4c18.4-6.9 34.5 4.1 28.5 32.2z'></path>
</svg>
</a>
</li>
<li className='w-8 h-8 border-current bg-white rounded-full flex items-center justify-center'>
<a href='mailto:hello@utopia-lab.org'>
<svg
stroke='currentColor'
fill='#333'
strokeWidth='0'
viewBox='0 0 24 24'
height='1.25rem'
width='1.25rem'
xmlns='http://www.w3.org/2000/svg'
>
<path fill='none' d='M0 0h24v24H0z'></path>
<path d='M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4-8 5-8-5V6l8 5 8-5v2z'></path>
</svg>{' '}
</a>
</li>
<li className='w-8 h-8 border-current bg-white rounded-full flex items-center justify-center'>
<a href='https://twitter.com/UtopiaMapGame/' className='text-white'>
<svg
className='svg-icon w-[1.4rem] h-[1.4rem] text-[#1d93d2]'
viewBox='0 0 20 20'
>
<path
fill='currentColor'
d='M18.258,3.266c-0.693,0.405-1.46,0.698-2.277,0.857c-0.653-0.686-1.586-1.115-2.618-1.115c-1.98,0-3.586,1.581-3.586,3.53c0,0.276,0.031,0.545,0.092,0.805C6.888,7.195,4.245,5.79,2.476,3.654C2.167,4.176,1.99,4.781,1.99,5.429c0,1.224,0.633,2.305,1.596,2.938C2.999,8.349,2.445,8.19,1.961,7.925C1.96,7.94,1.96,7.954,1.96,7.97c0,1.71,1.237,3.138,2.877,3.462c-0.301,0.08-0.617,0.123-0.945,0.123c-0.23,0-0.456-0.021-0.674-0.062c0.456,1.402,1.781,2.422,3.35,2.451c-1.228,0.947-2.773,1.512-4.454,1.512c-0.291,0-0.575-0.016-0.855-0.049c1.588,1,3.473,1.586,5.498,1.586c6.598,0,10.205-5.379,10.205-10.045c0-0.153-0.003-0.305-0.01-0.456c0.7-0.499,1.308-1.12,1.789-1.827c-0.644,0.28-1.334,0.469-2.06,0.555C17.422,4.782,17.99,4.091,18.258,3.266'
></path>
</svg>
</a>
</li>
</ul>
</div>
</div>
<div className='mt-8 flex item s-center justify-center'>&copy; 2024</div>
</footer>
</MapOverlayPage>
)
}

View File

@ -0,0 +1,153 @@
/* eslint-disable @typescript-eslint/no-unnecessary-condition */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-non-null-assertion */
/* eslint-disable import/no-relative-parent-imports */
/* eslint-disable array-callback-return */
/* eslint-disable new-cap */
/* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
/* eslint-disable react-hooks/exhaustive-deps */
import { useEffect, useState } from 'react'
import {
UtopiaMap,
Layer,
PopupView,
PopupButton,
StartEndView,
TextView,
PopupForm,
PopupStartEndInput,
PopupTextAreaInput,
PopupTextInput,
} from 'utopia-ui'
import { itemsApi } from '../api/itemsApi'
import type { Place } from '../api/directus'
import type { LayerProps } from 'utopia-ui'
interface layerApi {
id: string
api: itemsApi<Place>
}
function MapContainer({ layers, map }: { layers: LayerProps[]; map: any }) {
const [apis, setApis] = useState<layerApi[]>([])
useEffect(() => {
// get timestamp for the end of the current day
const now = new Date()
const startOfDay = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const etartOfDayISO = startOfDay.toISOString()
layers.map((layer: LayerProps) => {
apis &&
setApis((current) => [
...current,
{
id: layer.id!,
api: new itemsApi<Place>('items', layer.id, undefined, {
_or: [
{
end: {
_gt: etartOfDayISO,
},
},
{
end: {
_null: true,
},
},
],
}),
},
])
})
}, [layers])
useEffect(() => {}, [apis])
return (
<>
<UtopiaMap
geo={map.geo}
zoom={map.zoom || 5}
center={map.center ? [map.center?.coordinates[1], map.center?.coordinates[0]] : [50.6, 9.5]}
height='100%'
width='100%'
showFilterControl={map.show_filter_control}
showLayerControl={map.show_layer_control}
showGratitudeControl={map.show_gratitude_control}
donationWidget={map.donation_widget}
showThemeControl={map.show_theme_control}
defaultTheme={map.default_theme}
showZoomControl={map.show_zoom_control}
expandLayerControl={map.expand_layer_control}
>
{layers &&
apis &&
layers.map((layer) => (
<Layer
id={layer.id}
key={layer.id}
name={layer.name}
menuIcon={'https://api.utopia-lab.org/assets/' + layer.menuIcon}
menuText={layer.menuText}
menuColor={layer.menuColor}
markerIcon={layer.markerIcon}
markerShape={layer.markerShape}
userProfileLayer={layer.userProfileLayer}
markerDefaultColor={layer.menuColor}
markerDefaultColor2={
layer.markerDefaultColor2 ? layer.markerDefaultColor2 : 'RGBA(35, 31, 32, 0.2)'
}
itemType={layer.itemType}
customEditLink='/edit-item'
customEditParameter='id'
public_edit_items={layer.public_edit_items}
listed={layer.listed}
api={apis.find((api) => api.id === layer.id)?.api}
>
<PopupView>
{layer.itemType.show_start_end && <StartEndView></StartEndView>}
{layer.itemType.show_profile_button && (
<PopupButton url={'/item'} parameterField={'id'} text={'Profile'} />
)}
{layer.itemType.show_text && <TextView truncate></TextView>}
</PopupView>
<PopupForm>
{layer.itemType.show_name_input && (
<PopupTextInput dataField='name' placeholder='Name'></PopupTextInput>
)}
{layer.itemType.show_start_end_input && <PopupStartEndInput></PopupStartEndInput>}
{layer.itemType.show_text_input && (
<div className='mt-4'>
<PopupTextAreaInput
dataField='text'
placeholder={'Text ...'}
style='tw-h-40'
></PopupTextAreaInput>
</div>
)}
{
// layer.public_edit_items && <PopupCheckboxInput dataField={'public_edit'} label={'public edit'}/>
}
{layer.itemType.custom_text && (
<div className='flex justify-center'>
<p>{layer.itemType.custom_text}</p>
</div>
)}
{layer.item_presets &&
Object.entries(layer.item_presets).map((ip: any) => (
<input key={ip[0]} type='hidden' id={ip[0]} name={ip[0]} value={ip[1]} />
))}
</PopupForm>
</Layer>
))}
</UtopiaMap>
</>
)
}
export default MapContainer

115
frontend/src/pages/data.ts Normal file
View File

@ -0,0 +1,115 @@
import type { Item, Tag } from 'utopia-ui'
export const tags: Tag[] = [
{
id: '423423423423',
name: 'Activism',
color: '#6d398b',
},
{
id: '4234423',
name: 'Art',
color: '#fdc60b',
},
{
id: '4231223423',
name: 'Community',
color: '#FFA439',
},
{
id: '429803423423',
name: 'Culture',
color: '#f18e1c',
},
{
id: '42423423',
name: 'Education',
color: '#444e99',
},
{
id: '4565654423',
name: 'Gardening',
color: '#008e5b',
},
{
id: '4234gfh423',
name: 'Healing',
color: '#c4037d',
},
{
id: '4223423',
name: 'Market',
color: '#2a71b0',
},
{
id: '42342gd3423',
name: 'Nature',
color: '#8cbb26',
},
{
id: '423123423',
name: 'Technology',
color: '#0696bb',
},
]
export const events: Item[] = [
{
id: '243253f3645643',
name: 'Vollmondtrommeln',
text: 'Zu den Vollmonden vom März bis Oktober treffen sich traditionell Menschen zum gemeinsamen Musizieren, Tanzen, Spielen, Grillen und Entspannen am Gerloser Häuschen im Niesiger Wald.\r\n\r\nUhrzeit: immer ab 17 Uhr\r\n\r\nhttps://trommeln-fulda.de/vollmondtrommeln/',
position: {
type: 'Point',
coordinates: [9.667615, 50.588632],
},
start: '2022-03-18T12:00:00',
end: '2022-10-08T12:00:00',
},
{
id: 'fsdfsdfsdfsdfdse',
name: 'anderes Event',
text: 'Zu den Vollmonden vom März bis Oktober treffen sich traditionell Menschen zum gemeinsamen Musizieren, Tanzen, Spielen, Grillen und Entspannen am Gerloser Häuschen im Niesiger Wald.\r\n\r\nUhrzeit: immer ab 17 Uhr\r\n\r\nhttps://trommeln-fulda.de/vollmondtrommeln/',
position: {
type: 'Point',
coordinates: [9.68, 50.59],
},
start: '2022-03-18T12:00:00',
end: '2022-10-08T12:00:00',
},
]
export const places: Item[] = [
{
id: '1asdasdasd',
name: 'Gärtnerei am Leisebach',
text: 'Wir sind eine Bio-Gemüsegärtnerei und suchen noch fleißige Helfer für diese Saison.',
position: {
type: 'Point',
coordinates: [8.476371, 51.0044],
},
date_created: '2021-04-15T07:46:26.906Z',
date_updated: '2021-04-15T07:46:26.906Z',
},
{
id: 'adsasdas2',
name: 'Rainbow Crystal Land',
text: 'https://rainbowcrystal.land',
position: {
type: 'Point',
coordinates: [-76.367426, 1.87],
},
date_created: '2021-04-04T18:01:24.596Z',
date_updated: '2021-04-04T18:01:24.596Z',
},
{
id: 'f345v34',
name: '🌈 RainbowCrystaleARThshipKinderGarten',
text: "AMYTIME WELCOME HOME\r\n\r\n#lebenliebenlernen\r\n#symbioticsynergysolutions\r\n#lebenlanglachen\r\n#soriendosiempresaludpazyamor\r\n#laughinglearninglivingloving\r\n#souriresantétoujoursamoure\r\n\r\n\r\n<b>Garden IntroductionVideo</b>\r\n<br>\r\n\r\n\r\n<iframe width='250' src='https://www.youtube-nocookie.com/embed/7jUaixJGK08' title='YouTube video player' frameborder='0' allow='accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture' allowfullscreen></iframe>",
position: {
type: 'Point',
coordinates: [9.502648, 51.334741],
},
date_created: '2022-03-13T23:09:56.305Z',
date_updated: '2022-03-13T23:09:56.305Z',
},
]

View File

@ -0,0 +1,68 @@
import { MapIcon } from '@heroicons/react/24/outline'
import { SVG } from 'utopia-ui'
export const routes = [
{
path: '/',
icon: <MapIcon style={{ width: 24 }} />,
name: 'Map',
} /**
{
path: '/people', // url
icon: <UsersIcon style={{width: 24 }}/>, // icon component
name: 'People', // name that appear in Sidebar
}, */,
/*
{
path: '', //no url needed as this has submenu
icon: <DocumentDuplicateIcon className={`${iconClasses} inline` }/>, // icon component
name: 'Pages', // name that appear in Sidebar
submenu : [
{
path: '/login',
icon: <ArrowRightOnRectangleIcon className={submenuIconClasses}/>,
name: 'Kanban',
},
{
path: '/register', //url
icon: <UserIcon className={submenuIconClasses}/>, // icon component
name: 'Gitlab', // name that appear in Sidebar
},
{
path: '/forgot-password',
icon: <KeyIcon className={submenuIconClasses}/>,
name: 'Wiki',
},
]
}
*/
]
export const getBottomRoutes = (currentUrl: string) => {
const url = new URL(currentUrl)
const isEmbedded = url.searchParams.get('embedded') === 'true'
const bottomRoutes = [
// Other routes can be added here
]
if (!isEmbedded) {
bottomRoutes.push(
{
path: 'https://github.com/utopia-os/utopia-ui', // url
icon: <SVG src='/github.svg' className='w-6 h-6' />,
name: 'GitHub', // name that appear in Sidebar
blank: true,
},
{
path: 'https://opencollective.com/utopia-project', // url
icon: <SVG src='/opencollective.svg' className='w-6 h-6' />,
name: 'Open Collective', // name that appear in Sidebar
blank: true,
},
)
}
return bottomRoutes
}

View File

@ -0,0 +1,12 @@
/** @type {import('tailwindcss').Config} */
export default {
content: ['./src/**/*.{html,js,jsx,tsx,ts}'],
theme: {
extend: {
// that is animation class
animation: {
fade: 'fadeOut 1s ease-in-out',
},
},
},
}

24
frontend/tsconfig.json Normal file
View File

@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": ["DOM", "DOM.Iterable", "ESNext"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}

View File

@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

BIN
frontend/video_preview.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 KiB

47
frontend/vite.config.ts Normal file
View File

@ -0,0 +1,47 @@
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import fs from 'fs';
import path from 'path';
// __dirname-Ersatz für ESModules
const __dirname = path.dirname(new URL(import.meta.url).pathname);
export default defineConfig({
server: {
host: true,
port: 5174,
/**
* https: {
* key: fs.readFileSync(path.resolve(__dirname, 'localhost-key.pem')),
* cert: fs.readFileSync(path.resolve(__dirname, 'localhost-cert.pem')),
* },
*/
},
plugins: [
react(),
tailwindcss(),
],
build: {
rollupOptions: {
output: {
manualChunks(id) {
if (
id.includes('node_modules/utopia-ui/dist/Profile') &&
/\.(esm|cjs)\.js$/.test(id)
) {
return 'profile-form'
}
if (id.includes('node_modules/utopia-ui/')) {
return 'utopia-ui-vendor'
}
if (id.includes('node_modules/')) {
return 'vendor'
}
}
}
}
}
});

View File

14
lib/.prettierrc.json Normal file
View File

@ -0,0 +1,14 @@
{
"printWidth": 100,
"tabWidth": 2,
"useTabs": false,
"semi": false,
"singleQuote": true,
"quoteProps": "as-needed",
"jsxSingleQuote": true,
"trailingComma": "all",
"bracketSpacing": true,
"bracketSameLine": false,
"arrowParens": "always",
"endOfLine": "auto"
}

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB

2
lib/FUNDING.yaml Normal file
View File

@ -0,0 +1,2 @@
github: [utopia-os]
open_collective: utopia-project

View File

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Some files were not shown because too many files have changed in this diff Show More