Compare commits
No commits in common. "main" and "group-forum" have entirely different histories.
main
...
group-foru
@ -1,3 +0,0 @@
|
||||
api/.venv
|
||||
web/node_modules
|
||||
*.pyc
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1 @@
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
@ -8,10 +8,17 @@ steps:
|
||||
- VITE_API_URL=https://api.treadl.com
|
||||
- VITE_IMAGINARY_URL=https://images.treadl.com
|
||||
- VITE_SENTRY_DSN=https://7c88f77dd19c57bfb92bb9eb53e33c4b@o4508066290532352.ingest.de.sentry.io/4508075022090320
|
||||
- VITE_SOURCE_REPO_URL=https://git.wilw.dev/wilw/treadl
|
||||
- VITE_PATREON_URL=https://www.patreon.com/treadl
|
||||
- VITE_KOFI_URL=https://ko-fi.com/wilw88
|
||||
- VITE_IOS_APP_URL=https://apps.apple.com/gb/app/treadl/id1525094357
|
||||
- "VITE_ANDROID_APP_URL=https://play.google.com/store/apps/details/Treadl?id=com.treadl"
|
||||
- VITE_CONTACT_EMAIL=hello@treadl.com
|
||||
- VITE_APP_NAME=Treadl
|
||||
commands:
|
||||
- cd web
|
||||
- npm install
|
||||
- npx vite build
|
||||
- yarn install
|
||||
- yarn build
|
||||
|
||||
buildapi:
|
||||
group: build
|
||||
|
106
README.md
106
README.md
@ -2,43 +2,54 @@
|
||||
|
||||
This is a monorepo containing the code for the web and mobile front-ends and web API for the Treadl platform.
|
||||
|
||||
## Running and developing Treadl locally
|
||||
|
||||
To run Treadl locally, we recommend taking the following steps:
|
||||
|
||||
1. Check out this repository locally.
|
||||
1. Follow the instructions in the `api/` directory to launch a MongoDB instance and to run the Treadl API.
|
||||
1. Follow the instructions in the `web/` directory to install the local dependencies and run the web UI.
|
||||
|
||||
## Deploying your own version of Treadl
|
||||
|
||||
### Run with Docker (recommended)
|
||||
If you'd like to launch your own version of Treadl in a web production environment, follow the steps below. These instructions set-up a basic version of Treadl, and you may want or need to take additional steps for more advanced options.
|
||||
|
||||
We publish and maintain a [Docker image](https://hub.docker.com/r/wilw/treadl) for Treadl, which is the easiest way to get started.
|
||||
We recommend forking this repository. That way you can make adjustments to the code to suit your needs, and pull in upstream updates as we continue to develop them.
|
||||
|
||||
We recommend using Docker Compose and our [template `docker-compose.yml`](https://git.wilw.dev/wilw/treadl/src/branch/main/docker/docker-compose.yml) to configure the app and the MongoDB database. Download this file to your computer and then run `docker compose up` to start Treadl.
|
||||
### 1. Launch a MongoDB cluster/instance
|
||||
|
||||
In production, it is very important to change the values in the file's `environment` block to suit your own setup. We also strongly recommend the use of a reverse-proxy to handle TLS connections to the app.
|
||||
Treadl uses MongoDB as its data store, and this should be setup first. You can either use a commercial hosted offering, or host the database yourself.
|
||||
|
||||
Hosted options:
|
||||
|
||||
### Alternative deployment
|
||||
* [MongoDB Atlas](https://www.mongodb.com)
|
||||
* [DigitalOcean managed MongoDB](https://www.digitalocean.com/products/managed-databases-mongodb)
|
||||
|
||||
In scenarios where you want more control over the deployment, or you are more concerned with scalability, you may wish to use a more manual approach.
|
||||
Self-hosted guides:
|
||||
|
||||
In this case you'll need to:
|
||||
- Launch (or re-use) a MongoDB cluster/instance
|
||||
- Provision a server or service for running the Flask app (in the `api/` directory), ensuring all dependencies are installed and that it runs with the needed [environment variables](https://git.wilw.dev/wilw/treadl/src/branch/main/api/envfile.template)
|
||||
- Build the web front-end (with `npx vite build` using your needed [environment variables](https://git.wilw.dev/wilw/treadl/src/branch/main/web/.env), having installed dependencies with `npm install`) and host the resulting `dist/` directory on a server or object store.
|
||||
* [Creating a MongoDB Replica Set](https://www.linode.com/docs/guides/create-a-mongodb-replica-set)
|
||||
* [MongoDB official Docker Image](https://hub.docker.com/_/mongo)
|
||||
|
||||
Either way, once launched, make a note of the cluster/instance's:
|
||||
|
||||
### S3-compatible object storage
|
||||
* URI: The database's URI, probably in a format like `mongodb+srv://USERNAME:PASSWORD@host.com/AUTHDATABASE?retryWrites=true&w=majority`
|
||||
* Database: The name of the database, within your cluster/instance, where you want Treadl to store the data.
|
||||
|
||||
Treadl uses S3-compatible object storage for storing user uploads. If you want to allow file uploads (apart from WIF files, which are processed directly), you should create and configure a bucket for Treadl to use.
|
||||
### 2. Provision an S3-compatible bucket
|
||||
|
||||
Treadl uses S3-compatible object storage for storing assets (e.g. uploaded files). You should create and configure a bucket for Treadl to use.
|
||||
|
||||
Hosted options:
|
||||
|
||||
* [Amazon S3](https://aws.amazon.com/s3)
|
||||
* [Linode Object Storage](https://www.linode.com/products/object-storage)
|
||||
* [Linode Object Storage](https://www.linode.com/products/object-storage) - Recommended option.
|
||||
* [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces)
|
||||
|
||||
Self-hosted options:
|
||||
|
||||
* [MinIO](https://min.io/download)
|
||||
|
||||
Once you have a bucket, generate some access keys for the bucket that will enable Treadl to read from and write to it. Ensure you make a record of the following for inclusion in your environment file/variables:
|
||||
Once you have a bucket, generate some access keys for the bucket that will enable Treadl to read from and write to it. Ensure you make a record of the following for later:
|
||||
|
||||
* Bucket name: The name of the S3-compatible bucket you created
|
||||
* Endpoint URL: The endpoint for your bucket. This helps Treadl understand which provider you are using.
|
||||
@ -47,50 +58,65 @@ Once you have a bucket, generate some access keys for the bucket that will enabl
|
||||
|
||||
_Note: assets in your bucket should be public. Treadl does not currently used signed requests to access uploaded files._
|
||||
|
||||
### 3. Provision the API
|
||||
|
||||
## Running Treadl locally in development mode
|
||||
The best way to run the web API is to do so via Docker. A `Dockerfile` is provided in the `api/` directory.
|
||||
|
||||
To run Treadl locally, first ensure you have the needed software installed:
|
||||
Simply build the image and transfer it to your server (or just build it directly on the server, if easier).
|
||||
|
||||
- Python ^3.12
|
||||
- Node.js (we recommend v22.x)
|
||||
- Docker (we use this for the Mongo database)
|
||||
- It can be installed via the Docker website or your package manager
|
||||
- Ensure the Docker service is running
|
||||
- [Taskfile](https://taskfile.dev) (convenience tool for running tasks)
|
||||
- This can be installed using `brew install go-task`
|
||||
Make a copy of the `envfile.template` file included in the `api/` directory into a new file named `envfile` and make changes to this file to suit your needs. For example, you will likely need to:
|
||||
|
||||
To begin, clone this repository to your computer:
|
||||
* Add in the Mongo URI and database into the relevant parts
|
||||
* Add the S3 detais into the relevant parts
|
||||
* Add Mailgun connection details (for sending outbound mail)
|
||||
* Change the app's URL and email addresses
|
||||
|
||||
```bash
|
||||
git clone https://git.wilw.dev/wilw/treadl.git
|
||||
Once ready, you can launch the API by passing in this envfile (assuming you built the image with a name of `treadl-api`):
|
||||
|
||||
```shell
|
||||
$ docker run --env-file envfile -d treadl-api
|
||||
```
|
||||
|
||||
Next, initialise the project by installing dependencies and creating an environment file for the API:
|
||||
_Note: a reverse proxy (such as Nginx or Traefik) should be running on your server to proxy traffic through to port 8000 on your running Treadl API container._
|
||||
|
||||
```bash
|
||||
task init
|
||||
### 4. Host the front-end
|
||||
|
||||
The front-end is formed from static files that can be simply served from a webserver, from a CDN-fronted object store, or anything else.
|
||||
|
||||
Before building or hosting the front-end, please copy the `.env.development` file into a new file called `.env.production` and make changes to it as required. For example, you will need to:
|
||||
|
||||
* Include the URL of the web API you deployed earlier in the relevant field.
|
||||
* Include a contact email address.
|
||||
|
||||
**Vercel**
|
||||
|
||||
We use [Vercel](https://vercel.com) to host the web UI. Once you have an account to which you are logged-in to locally, the front-end can be deployed by simply running:
|
||||
|
||||
```shell
|
||||
$ vercel --prod
|
||||
```
|
||||
|
||||
This generates a 'envfile' in your 'api' directory. You can edit this as needed (though the defaults should allow you to at least launch the app). Note: if you run this command again then any changes you made to your `envfile` will be overwritten.
|
||||
_Note: You will need to configure Vercel to use your own domain, and set-up a project, etc. first._
|
||||
|
||||
Finally, you can start the API and web UI by running:
|
||||
**Manual**
|
||||
|
||||
```bash
|
||||
task
|
||||
Simply build the app and then deploy the resulting `build/` directory to a server or storage of your choice:
|
||||
|
||||
```shell
|
||||
$ yarn build
|
||||
$ s3cmd cp build/ s3://my-treadl-ui # Example
|
||||
```
|
||||
|
||||
Note: this command also starts the MongoDB database on port 27017. If the DB is already running, you'll see errors reported, but the API and web will still be launched.
|
||||
### 5. Optional extras
|
||||
|
||||
You can now navigate to [http://localhost:8002](http://localhost:8002) to start using the app.
|
||||
**Imaginary server**
|
||||
|
||||
If you pull updates from the repository in the future (e.g. with `git pull`) you may need to ensure your dependencies are up-to-date before starting the app again. This can be done with:
|
||||
To help improve the performance of the app, you may wish to make use of [Imaginary](https://github.com/h2non/imaginary) to crop/resize large images. The web UI is already equipped to handle Imaginary if a server is configured.
|
||||
|
||||
```bash
|
||||
task install-deps
|
||||
```
|
||||
To use this feature, simply rebuild the app ensuring that an environment entry is made into `.env.production` that includes `"VITE_IMAGINARY_URL=https://your.imaginaryserver.com"`.
|
||||
|
||||
_Note: If this is not set, Treadl will by default fetch the full size images straight from the S3 source._
|
||||
|
||||
## Contributions
|
||||
|
||||
Contributions to the core project are certainly welcomed. Please [get in touch with the developer](https://wilw.dev) for an invitation to join this repository.
|
||||
Contributions to the core project are certainly welcomed. Please [get in touch with the developer](https://wilw.dev) for an invitation to join this repository.
|
121
Taskfile.yml
121
Taskfile.yml
@ -1,121 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
VENV: ".venv/bin/activate"
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: Run web bundler and API
|
||||
deps:
|
||||
- start-db
|
||||
- run-api
|
||||
- run-web
|
||||
|
||||
run-web:
|
||||
desc: Run web frontend
|
||||
dir: 'web'
|
||||
cmds:
|
||||
- echo "[Web] Starting React app..."
|
||||
- npx vite --port 8002
|
||||
|
||||
run-api:
|
||||
desc: Run API server
|
||||
dir: 'api'
|
||||
dotenv: ['envfile']
|
||||
cmds:
|
||||
- echo "[FLASK] Starting Flask app..."
|
||||
- bash -c "source {{.VENV}} && flask run --debug"
|
||||
|
||||
start-db:
|
||||
desc: Start database
|
||||
ignore_error: true
|
||||
cmds:
|
||||
- echo "[DB] Starting database..."
|
||||
- docker run --rm -d --name mongo -v ~/.mongo:/data/db -p 27017:27017 mongo:6
|
||||
|
||||
init:
|
||||
desc: Initialize project
|
||||
cmds:
|
||||
- task: install-deps
|
||||
- cp api/envfile.template api/envfile
|
||||
|
||||
install-deps:
|
||||
desc: Install all dependencies
|
||||
deps:
|
||||
- install-deps-web
|
||||
- install-deps-api
|
||||
|
||||
install-deps-web:
|
||||
desc: Install web dependencies
|
||||
dir: 'web'
|
||||
cmds:
|
||||
- echo "[Web] Installing dependencies..."
|
||||
- npm install
|
||||
|
||||
install-deps-api:
|
||||
desc: Install API dependencies
|
||||
dir: 'api'
|
||||
cmds:
|
||||
- echo "[FLASK] Installing dependencies..."
|
||||
- cmd: python3.12 -m venv .venv
|
||||
ignore_error: true
|
||||
- bash -c "source {{.VENV}} && pip install poetry"
|
||||
- bash -c "source {{.VENV}} && poetry install"
|
||||
|
||||
lint:
|
||||
desc: Lint all
|
||||
deps:
|
||||
- lint-web
|
||||
- lint-api
|
||||
|
||||
lint-web:
|
||||
desc: Lint web frontend
|
||||
dir: 'web'
|
||||
cmds:
|
||||
- echo "[Web] Linting React app..."
|
||||
- npx standard --fix
|
||||
|
||||
lint-api:
|
||||
desc: Lint API server
|
||||
dir: 'api'
|
||||
cmds:
|
||||
- echo "[FLASK] Linting Flask app..."
|
||||
- bash -c "source {{.VENV}} && ruff format ."
|
||||
- bash -c "source {{.VENV}} && ruff check --fix ."
|
||||
|
||||
clean:
|
||||
desc: Remove all dependencies
|
||||
cmds:
|
||||
- rm -rf web/node_modules
|
||||
- rm -rf api/.venv
|
||||
|
||||
build-docker:
|
||||
desc: Build all-in-one Docker image
|
||||
cmds:
|
||||
- echo "Building Docker image..."
|
||||
- docker build -f docker/Dockerfile -t wilw/treadl --platform linux/amd64,linux/arm64 .
|
||||
|
||||
deploy:
|
||||
desc: Deploy all
|
||||
deps:
|
||||
- deploy-web
|
||||
- deploy-api
|
||||
|
||||
deploy-web:
|
||||
desc: Deploy web front-end
|
||||
dir: 'web'
|
||||
env:
|
||||
AWS_REQUEST_CHECKSUM_CALCULATION: when_required
|
||||
AWS_RESPONSE_CHECKSUM_VALIDATION: when_required
|
||||
cmds:
|
||||
- npm install
|
||||
- npx vite build
|
||||
- aws --profile personal s3 sync dist s3://treadl.com
|
||||
- 'curl -X POST -H "AccessKey: $BUNNY_PERSONAL" https://api.bunny.net/pullzone/782753/purgeCache'
|
||||
|
||||
deploy-api:
|
||||
desc: Deploy API
|
||||
dir: 'api'
|
||||
cmds:
|
||||
- docker build -t wilw/treadl-api --platform linux/amd64 .
|
||||
- docker push wilw/treadl-api
|
@ -1,4 +1,4 @@
|
||||
FROM amd64/python:3.12-slim
|
||||
FROM python:3.12-slim
|
||||
|
||||
# set work directory
|
||||
WORKDIR /app
|
||||
|
@ -1,3 +1,67 @@
|
||||
# Treadl web API
|
||||
|
||||
This directory contains the code for the back-end Treadl API.
|
||||
|
||||
## Run locally
|
||||
|
||||
To run this web API locally, follow the steps below.
|
||||
|
||||
### 1. Run a local MongoDB instance
|
||||
|
||||
Install MongoDB for your operating system and then launch a local version in the background. For example:
|
||||
|
||||
```shell
|
||||
$ mongod --fork --dbpath=/path/to/.mongo --logpath /dev/null
|
||||
```
|
||||
|
||||
(Remember to restart the database upon system restart or if the instance stops for another reason.)
|
||||
|
||||
### 2. Create and activate a virtual environment
|
||||
|
||||
Install and activate the environment using `virtualenv`:
|
||||
|
||||
```shell
|
||||
$ virtualenv -p python3 .venv # You only need to run this the first time
|
||||
$ source .venv/bin/activate
|
||||
```
|
||||
|
||||
### 3. Install dependencies
|
||||
|
||||
We use Poetry to manage dependencies. If you don't have this yet, please refer to [the Poetry documentation](https://python-poetry.org) to install it. Once done, install the dependencies (ensuring you have `source`d your virtualenv first):
|
||||
|
||||
```shell
|
||||
$ poetry install
|
||||
```
|
||||
|
||||
### 4. Create an `envfile`
|
||||
|
||||
Copy the template file into a new `envfile`:
|
||||
|
||||
```shell
|
||||
$ cp envfile.template envfile
|
||||
```
|
||||
|
||||
If you need to, make any changes to your new `envfile`. Note that changes are probably not required if you are running this locally. When happy, you can `source` this file too:
|
||||
|
||||
```shell
|
||||
$ source envfile
|
||||
```
|
||||
|
||||
### 5. Run the API
|
||||
|
||||
Ensure that both the virtualenv and `envfile` have been loaded into the environment:
|
||||
|
||||
```shell
|
||||
$ source .venv/bin/activate
|
||||
$ source envfile
|
||||
```
|
||||
|
||||
Now you can run the API:
|
||||
|
||||
```shell
|
||||
$ flask run
|
||||
```
|
||||
|
||||
The API will now be available on port 2001.
|
||||
|
||||
Remember that you will need a local instance of [MongoDB](https://www.mongodb.com) running for the API to connect to.
|
@ -5,7 +5,6 @@ import re
|
||||
import os
|
||||
from bson.objectid import ObjectId
|
||||
from util import database, mail, util
|
||||
from api import projects, uploads
|
||||
|
||||
jwt_secret = os.environ["JWT_SECRET"]
|
||||
MIN_PASSWORD_LENGTH = 8
|
||||
@ -238,13 +237,13 @@ def delete(user, password):
|
||||
raise util.errors.BadRequest("Incorrect password")
|
||||
db = database.get_db()
|
||||
for project in db.projects.find({"user": user["_id"]}):
|
||||
projects.delete(user, user["username"], project.get("path"))
|
||||
db.objects.delete_many({"project": project["_id"]})
|
||||
db.projects.delete_one({"_id": project["_id"]})
|
||||
db.comments.delete_many({"user": user["_id"]})
|
||||
db.users.update_many(
|
||||
{"following.user": user["_id"]}, {"$pull": {"following": {"user": user["_id"]}}}
|
||||
)
|
||||
db.users.delete_one({"_id": user["_id"]})
|
||||
uploads.delete_folder("users/" + str(user["_id"]))
|
||||
return {"deletedUser": user["_id"]}
|
||||
|
||||
|
||||
|
@ -37,7 +37,6 @@ def create(user, data):
|
||||
"name": data["name"],
|
||||
"description": data.get("description", ""),
|
||||
"closed": data.get("closed", False),
|
||||
"advertised": data.get("advertised", False),
|
||||
"memberPermissions": [
|
||||
"viewMembers",
|
||||
"viewNoticeboard",
|
||||
@ -92,21 +91,9 @@ def update(user, id, update):
|
||||
raise util.errors.NotFound("Group not found")
|
||||
if user["_id"] not in group.get("admins", []):
|
||||
raise util.errors.Forbidden("You're not a group admin")
|
||||
allowed_keys = [
|
||||
"name",
|
||||
"description",
|
||||
"closed",
|
||||
"advertised",
|
||||
"memberPermissions",
|
||||
"image",
|
||||
]
|
||||
allowed_keys = ["name", "description", "closed", "memberPermissions", "image"]
|
||||
updater = util.build_updater(update, allowed_keys)
|
||||
if updater:
|
||||
if "$set" in updater and (
|
||||
"name" in update or "description" in update or "image" in update
|
||||
):
|
||||
updater["$set"]["moderationRequired"] = True
|
||||
util.send_moderation_request(user, "groups", group)
|
||||
db.groups.update_one({"_id": id}, updater)
|
||||
return get_one(user, id)
|
||||
|
||||
@ -142,7 +129,6 @@ def create_entry(user, id, data):
|
||||
"group": id,
|
||||
"user": user["_id"],
|
||||
"content": data["content"],
|
||||
"moderationRequired": True,
|
||||
}
|
||||
if "attachments" in data:
|
||||
entry["attachments"] = data["attachments"]
|
||||
@ -167,24 +153,12 @@ def create_entry(user, id, data):
|
||||
entry["authorUser"]["avatarUrl"] = uploads.get_presigned_url(
|
||||
"users/{0}/{1}".format(user["_id"], user["avatar"])
|
||||
)
|
||||
util.send_moderation_request(user, "groupEntries", entry)
|
||||
return entry
|
||||
|
||||
|
||||
def send_entry_notification(id):
|
||||
db = database.get_db()
|
||||
entry = db.groupEntries.find_one({"_id": ObjectId(id)})
|
||||
# If this is a reply, then send the reply email instead
|
||||
if entry.get("inReplyTo"):
|
||||
return send_entry_reply_notification(id)
|
||||
group = db.groups.find_one({"_id": entry["group"]})
|
||||
user = db.users.find_one({"_id": entry["user"]})
|
||||
|
||||
for u in db.users.find(
|
||||
{
|
||||
"_id": {"$ne": user["_id"]},
|
||||
"groups": group["_id"],
|
||||
"subscriptions.email": "groupFeed-" + str(group["_id"]),
|
||||
"groups": id,
|
||||
"subscriptions.email": "groupFeed-" + str(id),
|
||||
},
|
||||
{"email": 1, "username": 1},
|
||||
):
|
||||
@ -196,17 +170,18 @@ def send_entry_notification(id):
|
||||
u["username"],
|
||||
user["username"],
|
||||
group["name"],
|
||||
entry["content"],
|
||||
"{}/groups/{}".format(APP_URL, str(group["_id"])),
|
||||
data["content"],
|
||||
"{}/groups/{}".format(APP_URL, str(id)),
|
||||
APP_NAME,
|
||||
),
|
||||
}
|
||||
)
|
||||
push.send_multiple(
|
||||
list(db.users.find({"_id": {"$ne": user["_id"]}, "groups": group["_id"]})),
|
||||
list(db.users.find({"_id": {"$ne": user["_id"]}, "groups": id})),
|
||||
"{} posted in {}".format(user["username"], group["name"]),
|
||||
entry["content"][:30] + "...",
|
||||
data["content"][:30] + "...",
|
||||
)
|
||||
return entry
|
||||
|
||||
|
||||
def get_entries(user, id):
|
||||
@ -219,14 +194,8 @@ def get_entries(user, id):
|
||||
raise util.errors.BadRequest("You're not a member of this group")
|
||||
if not has_group_permission(user, group, "viewNoticeboard"):
|
||||
raise util.errors.Forbidden("You don't have permission to view the feed")
|
||||
# Only return entries that have been moderated or are owned by the user
|
||||
entries = list(
|
||||
db.groupEntries.find(
|
||||
{
|
||||
"group": id,
|
||||
"$or": [{"user": user["_id"]}, {"moderationRequired": {"$ne": True}}],
|
||||
}
|
||||
).sort("createdAt", pymongo.DESCENDING)
|
||||
db.groupEntries.find({"group": id}).sort("createdAt", pymongo.DESCENDING)
|
||||
)
|
||||
authors = list(
|
||||
db.users.find(
|
||||
@ -289,7 +258,6 @@ def create_entry_reply(user, id, entry_id, data):
|
||||
"inReplyTo": entry_id,
|
||||
"user": user["_id"],
|
||||
"content": data["content"],
|
||||
"moderationRequired": True,
|
||||
}
|
||||
if "attachments" in data:
|
||||
reply["attachments"] = data["attachments"]
|
||||
@ -314,22 +282,9 @@ def create_entry_reply(user, id, entry_id, data):
|
||||
reply["authorUser"]["avatarUrl"] = uploads.get_presigned_url(
|
||||
"users/{0}/{1}".format(user["_id"], user["avatar"])
|
||||
)
|
||||
util.send_moderation_request(user, "groupEntries", entry)
|
||||
return reply
|
||||
|
||||
|
||||
def send_entry_reply_notification(id):
|
||||
db = database.get_db()
|
||||
reply = db.groupEntries.find_one({"_id": ObjectId(id)})
|
||||
user = db.users.find_one({"_id": reply["user"]})
|
||||
original_entry = db.groupEntries.find_one({"_id": reply["inReplyTo"]})
|
||||
group = db.groups.find_one({"_id": original_entry["group"]})
|
||||
op = db.users.find_one(
|
||||
{
|
||||
"$and": [
|
||||
{"_id": original_entry.get("user")},
|
||||
{"_id": {"$ne": user["_id"]}},
|
||||
],
|
||||
"$and": [{"_id": entry.get("user")}, {"_id": {"$ne": user["_id"]}}],
|
||||
"subscriptions.email": "messages.replied",
|
||||
}
|
||||
)
|
||||
@ -342,12 +297,13 @@ def send_entry_reply_notification(id):
|
||||
op["username"],
|
||||
user["username"],
|
||||
group["name"],
|
||||
reply["content"],
|
||||
"{}/groups/{}".format(APP_URL, str(group["_id"])),
|
||||
data["content"],
|
||||
"{}/groups/{}".format(APP_URL, str(id)),
|
||||
APP_NAME,
|
||||
),
|
||||
}
|
||||
)
|
||||
return reply
|
||||
|
||||
|
||||
def delete_entry_reply(user, id, entry_id, reply_id):
|
||||
@ -630,7 +586,6 @@ def create_forum_topic_reply(user, id, topic_id, data):
|
||||
"user": user["_id"],
|
||||
"content": data["content"],
|
||||
"attachments": data.get("attachments", []),
|
||||
"moderationRequired": True,
|
||||
}
|
||||
result = db.groupForumTopicReplies.insert_one(reply)
|
||||
db.groupForumTopics.update_one(
|
||||
@ -668,21 +623,11 @@ def create_forum_topic_reply(user, id, topic_id, data):
|
||||
)
|
||||
)
|
||||
|
||||
util.send_moderation_request(user, "groupForumTopicReplies", reply)
|
||||
return reply
|
||||
|
||||
|
||||
def send_forum_topic_reply_notification(id):
|
||||
db = database.get_db()
|
||||
reply = db.groupForumTopicReplies.find_one({"_id": ObjectId(id)})
|
||||
user = db.users.find_one({"_id": reply["user"]})
|
||||
topic = db.groupForumTopics.find_one({"_id": reply["topic"]})
|
||||
group = db.groups.find_one({"_id": topic["group"]})
|
||||
for u in db.users.find(
|
||||
{
|
||||
"_id": {"$ne": reply["user"]},
|
||||
"groups": topic["group"],
|
||||
"subscriptions.email": "groupForumTopic-" + str(topic["_id"]),
|
||||
"_id": {"$ne": user["_id"]},
|
||||
"groups": id,
|
||||
"subscriptions.email": "groupForumTopic-" + str(topic_id),
|
||||
},
|
||||
{"email": 1, "username": 1},
|
||||
):
|
||||
@ -695,15 +640,17 @@ def send_forum_topic_reply_notification(id):
|
||||
user["username"],
|
||||
topic["title"],
|
||||
group["name"],
|
||||
reply["content"],
|
||||
data["content"],
|
||||
"{}/groups/{}/forum/topics/{}".format(
|
||||
APP_URL, str(group["_id"]), str(topic["_id"])
|
||||
APP_URL, str(id), str(topic_id)
|
||||
),
|
||||
APP_NAME,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
return reply
|
||||
|
||||
|
||||
def get_forum_topic_replies(user, id, topic_id, data):
|
||||
REPLIES_PER_PAGE = 20
|
||||
@ -723,12 +670,7 @@ def get_forum_topic_replies(user, id, topic_id, data):
|
||||
)
|
||||
total_replies = db.groupForumTopicReplies.count_documents({"topic": topic_id})
|
||||
replies = list(
|
||||
db.groupForumTopicReplies.find(
|
||||
{
|
||||
"topic": topic_id,
|
||||
"$or": [{"moderationRequired": {"$ne": True}}, {"user": user["_id"]}],
|
||||
}
|
||||
)
|
||||
db.groupForumTopicReplies.find({"topic": topic_id})
|
||||
.sort("createdAt", pymongo.ASCENDING)
|
||||
.skip((page - 1) * REPLIES_PER_PAGE)
|
||||
.limit(REPLIES_PER_PAGE)
|
||||
|
@ -12,7 +12,7 @@ APP_URL = os.environ.get("APP_URL")
|
||||
|
||||
def delete(user, id):
|
||||
db = database.get_db()
|
||||
obj = db.objects.find_one(ObjectId(id), {"project": 1, "storedName": 1})
|
||||
obj = db.objects.find_one(ObjectId(id), {"project": 1})
|
||||
if not obj:
|
||||
raise util.errors.NotFound("Object not found")
|
||||
project = db.projects.find_one(obj.get("project"), {"user": 1})
|
||||
@ -21,8 +21,6 @@ def delete(user, id):
|
||||
if not util.can_edit_project(user, project):
|
||||
raise util.errors.Forbidden("Forbidden", 403)
|
||||
db.objects.delete_one({"_id": ObjectId(id)})
|
||||
db.comments.delete_many({"object": ObjectId(id)})
|
||||
uploads.delete_file(f"projects/{project['_id']}/{obj.get('storedName')}")
|
||||
return {"deletedObject": id}
|
||||
|
||||
|
||||
@ -36,9 +34,7 @@ def get(user, id):
|
||||
raise util.errors.NotFound("Project not found")
|
||||
is_owner = user and (user.get("_id") == proj["user"])
|
||||
if not is_owner and proj["visibility"] != "public":
|
||||
raise util.errors.Forbidden("Forbidden")
|
||||
if not util.can_edit_project(user, proj) and obj.get("moderationRequired"):
|
||||
raise util.errors.Forbidden("Awaiting moderation")
|
||||
raise util.errors.BadRequest("Forbidden")
|
||||
owner = db.users.find_one({"_id": proj["user"]}, {"username": 1, "avatar": 1})
|
||||
if obj["type"] == "file" and "storedName" in obj:
|
||||
obj["url"] = uploads.get_presigned_url(
|
||||
@ -173,12 +169,12 @@ def create_comment(user, id, data):
|
||||
obj = db.objects.find_one({"_id": ObjectId(id)})
|
||||
if not obj:
|
||||
raise util.errors.NotFound("We could not find the specified object")
|
||||
project = db.projects.find_one({"_id": obj["project"]})
|
||||
comment = {
|
||||
"content": data.get("content", ""),
|
||||
"object": ObjectId(id),
|
||||
"user": user["_id"],
|
||||
"createdAt": datetime.datetime.now(),
|
||||
"moderationRequired": True,
|
||||
}
|
||||
result = db.comments.insert_one(comment)
|
||||
db.objects.update_one({"_id": ObjectId(id)}, {"$inc": {"commentCount": 1}})
|
||||
@ -190,16 +186,6 @@ def create_comment(user, id, data):
|
||||
"users/{0}/{1}".format(user["_id"], user.get("avatar"))
|
||||
),
|
||||
}
|
||||
util.send_moderation_request(user, "comments", comment)
|
||||
return comment
|
||||
|
||||
|
||||
def send_comment_notification(id):
|
||||
db = database.get_db()
|
||||
comment = db.comments.find_one({"_id": ObjectId(id)})
|
||||
user = db.users.find_one({"_id": comment["user"]})
|
||||
obj = db.objects.find_one({"_id": comment["object"]})
|
||||
project = db.projects.find_one({"_id": obj["project"]})
|
||||
project_owner = db.users.find_one(
|
||||
{"_id": project["user"], "subscriptions.email": "projects.commented"}
|
||||
)
|
||||
@ -223,6 +209,7 @@ def send_comment_notification(id):
|
||||
),
|
||||
}
|
||||
)
|
||||
return comment
|
||||
|
||||
|
||||
def get_comments(user, id):
|
||||
@ -237,14 +224,7 @@ def get_comments(user, id):
|
||||
is_owner = user and (user.get("_id") == proj["user"])
|
||||
if not is_owner and proj["visibility"] != "public":
|
||||
raise util.errors.Forbidden("This project is private")
|
||||
query = {
|
||||
"object": id,
|
||||
"$or": [
|
||||
{"moderationRequired": {"$ne": True}},
|
||||
{"user": user["_id"] if user else None},
|
||||
],
|
||||
}
|
||||
comments = list(db.comments.find(query))
|
||||
comments = list(db.comments.find({"object": id}))
|
||||
user_ids = list(map(lambda c: c["user"], comments))
|
||||
users = list(
|
||||
db.users.find({"_id": {"$in": user_ids}}, {"username": 1, "avatar": 1})
|
||||
|
@ -1,8 +1,7 @@
|
||||
import datetime
|
||||
import re
|
||||
import os
|
||||
from bson.objectid import ObjectId
|
||||
from util import database, wif, util, mail
|
||||
from util import database, wif, util
|
||||
from api import uploads, objects
|
||||
|
||||
default_pattern = {
|
||||
@ -12,7 +11,6 @@ default_pattern = {
|
||||
"defaultColour": "178,53,111",
|
||||
"defaultSpacing": 1,
|
||||
"defaultThickness": 1,
|
||||
"guideFrequency": 8,
|
||||
},
|
||||
"weft": {
|
||||
"treadles": 8,
|
||||
@ -20,7 +18,6 @@ default_pattern = {
|
||||
"defaultColour": "53,69,178",
|
||||
"defaultSpacing": 1,
|
||||
"defaultThickness": 1,
|
||||
"guideFrequency": 8,
|
||||
},
|
||||
"tieups": [[]] * 8,
|
||||
"colours": [
|
||||
@ -230,12 +227,8 @@ def delete(user, username, project_path):
|
||||
project = get_by_username(username, project_path)
|
||||
if not util.can_edit_project(user, project):
|
||||
raise util.errors.Forbidden("Forbidden")
|
||||
|
||||
objects = list(db.objects.find({"project": project["_id"]}, {"_id": 1}))
|
||||
db.projects.delete_one({"_id": project["_id"]})
|
||||
db.objects.delete_many({"project": project["_id"]})
|
||||
db.comments.delete_many({"object": {"$in": [o["_id"] for o in objects]}})
|
||||
uploads.delete_folder("projects/" + str(project["_id"]))
|
||||
return {"deletedProject": project["_id"]}
|
||||
|
||||
|
||||
@ -247,12 +240,9 @@ def get_objects(user, username, path):
|
||||
if not util.can_view_project(user, project):
|
||||
raise util.errors.Forbidden("This project is private")
|
||||
|
||||
query = {"project": project["_id"]}
|
||||
if not util.can_edit_project(user, project):
|
||||
query["moderationRequired"] = {"$ne": True}
|
||||
objs = list(
|
||||
db.objects.find(
|
||||
query,
|
||||
{"project": project["_id"]},
|
||||
{
|
||||
"createdAt": 1,
|
||||
"name": 1,
|
||||
@ -304,7 +294,6 @@ def create_object(user, username, path, data):
|
||||
"storedName": data["storedName"],
|
||||
"createdAt": datetime.datetime.now(),
|
||||
"type": "file",
|
||||
"moderationRequired": True,
|
||||
}
|
||||
if re.search(r"(.jpg)|(.png)|(.jpeg)|(.gif)$", data["storedName"].lower()):
|
||||
obj["isImage"] = True
|
||||
@ -323,7 +312,6 @@ def create_object(user, username, path, data):
|
||||
uploads.blur_image(
|
||||
"projects/" + str(project["_id"]) + "/" + data["storedName"], handle_cb
|
||||
)
|
||||
util.send_moderation_request(user, "object", obj)
|
||||
return obj
|
||||
if data["type"] == "pattern":
|
||||
obj = {
|
||||
@ -337,16 +325,7 @@ def create_object(user, username, path, data):
|
||||
if pattern:
|
||||
obj["name"] = pattern["name"]
|
||||
obj["pattern"] = pattern
|
||||
except Exception as e:
|
||||
mail.send(
|
||||
{
|
||||
"to": os.environ.get("ADMIN_EMAIL"),
|
||||
"subject": "Error loading WIF file",
|
||||
"text": "A WIF file failed to parse with error: {}. The contents are below:\n\n{}".format(
|
||||
e, data["wif"]
|
||||
),
|
||||
}
|
||||
)
|
||||
except Exception:
|
||||
raise util.errors.BadRequest(
|
||||
"Unable to load WIF file. It is either invalid or in a format we cannot understand."
|
||||
)
|
||||
|
@ -1,7 +1,5 @@
|
||||
import datetime
|
||||
from bson.objectid import ObjectId
|
||||
from util import database, util
|
||||
from api import uploads, objects, groups
|
||||
from api import uploads
|
||||
|
||||
|
||||
def get_users(user):
|
||||
@ -54,82 +52,3 @@ def get_groups(user):
|
||||
for group in groups:
|
||||
group["memberCount"] = db.users.count_documents({"groups": group["_id"]})
|
||||
return {"groups": groups}
|
||||
|
||||
|
||||
def get_moderation(user):
|
||||
db = database.get_db()
|
||||
if not util.is_root(user):
|
||||
raise util.errors.Forbidden("Not allowed")
|
||||
object_list = list(db.objects.find({"moderationRequired": True}))
|
||||
for obj in object_list:
|
||||
if obj["type"] == "file" and "storedName" in obj:
|
||||
obj["url"] = uploads.get_presigned_url(
|
||||
"projects/{0}/{1}".format(obj["project"], obj["storedName"])
|
||||
)
|
||||
comment_list = list(db.comments.find({"moderationRequired": True}))
|
||||
user_list = list(db.users.find({"moderationRequired": True}, {"username": 1}))
|
||||
group_list = list(db.groups.find({"moderationRequired": True}, {"name": 1}))
|
||||
group_entry_list = list(db.groupEntries.find({"moderationRequired": True}))
|
||||
for entry in group_entry_list:
|
||||
for a in entry.get("attachments", []):
|
||||
if a["type"] == "file" and "storedName" in a:
|
||||
a["url"] = uploads.get_presigned_url(
|
||||
"groups/{0}/{1}".format(entry["group"], a["storedName"])
|
||||
)
|
||||
group_topic_reply_list = list(
|
||||
db.groupForumTopicReplies.find({"moderationRequired": True})
|
||||
)
|
||||
for reply in group_topic_reply_list:
|
||||
for a in reply.get("attachments", []):
|
||||
if a["type"] == "file" and "storedName" in a:
|
||||
a["url"] = uploads.get_presigned_url(
|
||||
"groups/{0}/topics/{1}/{2}".format(
|
||||
reply["group"], reply["topic"], a["storedName"]
|
||||
)
|
||||
)
|
||||
return {
|
||||
"objects": object_list,
|
||||
"comments": comment_list,
|
||||
"users": user_list,
|
||||
"groups": group_list,
|
||||
"groupEntries": group_entry_list,
|
||||
"groupForumTopicReplies": group_topic_reply_list,
|
||||
}
|
||||
|
||||
|
||||
def moderate(user, item_type, item_id, allowed):
|
||||
db = database.get_db()
|
||||
if not util.is_root(user):
|
||||
raise util.errors.Forbidden("Not allowed")
|
||||
if item_type not in [
|
||||
"objects",
|
||||
"comments",
|
||||
"users",
|
||||
"groups",
|
||||
"groupEntries",
|
||||
"groupForumTopicReplies",
|
||||
]:
|
||||
raise util.errors.BadRequest("Invalid item type")
|
||||
item_id = ObjectId(item_id)
|
||||
item = db[item_type].find_one({"_id": item_id})
|
||||
# For now, handle only allowed moderations.
|
||||
# Disallowed will be manually managed.
|
||||
if item and allowed:
|
||||
db[item_type].update_one(
|
||||
{"_id": item_id},
|
||||
{
|
||||
"$set": {
|
||||
"moderationRequired": False,
|
||||
"moderated": True,
|
||||
"moderatedAt": datetime.datetime.now(),
|
||||
"moderatedBy": user["_id"],
|
||||
}
|
||||
},
|
||||
)
|
||||
if item_type == "comments":
|
||||
objects.send_comment_notification(item_id)
|
||||
if item_type == "groupEntries":
|
||||
groups.send_entry_notification(item_id)
|
||||
if item_type == "groupForumTopicReplies":
|
||||
groups.send_forum_topic_reply_notification(item_id)
|
||||
return {"success": True}
|
||||
|
@ -105,7 +105,6 @@ def discover(user, count=3):
|
||||
db = database.get_db()
|
||||
projects = []
|
||||
users = []
|
||||
groups = []
|
||||
|
||||
all_projects_query = {
|
||||
"name": {"$not": re.compile("my new project", re.IGNORECASE)},
|
||||
@ -166,25 +165,9 @@ def discover(user, count=3):
|
||||
if len(users) >= count:
|
||||
break
|
||||
|
||||
all_groups = list(
|
||||
db.groups.find(
|
||||
{"advertised": True, "name": {"$ne": "My group"}}, {"name": 1, "image": 1}
|
||||
)
|
||||
)
|
||||
random.shuffle(all_groups)
|
||||
for g in all_groups:
|
||||
if "image" in g:
|
||||
g["imageUrl"] = uploads.get_presigned_url(
|
||||
"groups/{0}/{1}".format(g["_id"], g["image"])
|
||||
)
|
||||
groups.append(g)
|
||||
if len(groups) >= count:
|
||||
break
|
||||
|
||||
return {
|
||||
"highlightProjects": projects,
|
||||
"highlightUsers": users,
|
||||
"highlightGroups": groups,
|
||||
}
|
||||
|
||||
|
||||
|
@ -8,8 +8,6 @@ import blurhash
|
||||
from util import database, util
|
||||
from api.groups import has_group_permission
|
||||
|
||||
s3_client = None
|
||||
|
||||
|
||||
def sanitise_filename(s):
|
||||
bad_chars = re.compile("[^a-zA-Z0-9_.]")
|
||||
@ -18,9 +16,6 @@ def sanitise_filename(s):
|
||||
|
||||
|
||||
def get_s3():
|
||||
global s3_client
|
||||
if s3_client:
|
||||
return s3_client
|
||||
session = boto3.session.Session()
|
||||
|
||||
s3_client = session.client(
|
||||
@ -33,6 +28,7 @@ def get_s3():
|
||||
|
||||
|
||||
def get_presigned_url(path):
|
||||
return os.environ["AWS_S3_ENDPOINT"] + os.environ["AWS_S3_BUCKET"] + "/" + path
|
||||
s3 = get_s3()
|
||||
return s3.generate_presigned_url(
|
||||
"get_object", Params={"Bucket": os.environ["AWS_S3_BUCKET"], "Key": path}
|
||||
@ -49,38 +45,10 @@ def upload_file(path, data):
|
||||
|
||||
|
||||
def get_file(key):
|
||||
if not key:
|
||||
return None
|
||||
s3 = get_s3()
|
||||
return s3.get_object(Bucket=os.environ["AWS_S3_BUCKET"], Key=key)
|
||||
|
||||
|
||||
def delete_file(key):
|
||||
if not key:
|
||||
return
|
||||
s3 = get_s3()
|
||||
s3.delete_object(Bucket=os.environ["AWS_S3_BUCKET"], Key=key)
|
||||
|
||||
|
||||
def delete_folder(path):
|
||||
bucket_name = os.environ["AWS_S3_BUCKET"]
|
||||
if not path:
|
||||
return
|
||||
s3 = get_s3()
|
||||
|
||||
response = s3.list_objects_v2(Bucket=bucket_name, Prefix=path)
|
||||
if "Contents" in response:
|
||||
files_in_folder = response["Contents"]
|
||||
files_to_delete = []
|
||||
|
||||
for f in files_in_folder:
|
||||
files_to_delete.append({"Key": f["Key"]})
|
||||
|
||||
response = s3.delete_objects(
|
||||
Bucket=bucket_name, Delete={"Objects": files_to_delete}
|
||||
)
|
||||
|
||||
|
||||
def generate_file_upload_request(
|
||||
user, file_name, file_size, file_type, for_type, for_id
|
||||
):
|
||||
|
@ -115,19 +115,12 @@ def update(user, username, data):
|
||||
uploads.blur_image(
|
||||
"users/" + str(user["_id"]) + "/" + data["avatar"], handle_cb
|
||||
)
|
||||
if "avatar" in data and user.get("avatar") and user["avatar"] != data["avatar"]:
|
||||
uploads.delete_file("users/" + str(user["_id"]) + "/" + user["avatar"])
|
||||
updater = util.build_updater(data, allowed_keys)
|
||||
if updater:
|
||||
if "avatar" in updater.get(
|
||||
"$unset", {}
|
||||
): # Also unset blurhash if removing avatar
|
||||
updater["$unset"]["avatarBlurHash"] = ""
|
||||
if "$set" in updater and (
|
||||
"avatar" in data or "bio" in data or "website" in data or "username" in data
|
||||
):
|
||||
updater["$set"]["moderationRequired"] = True
|
||||
util.send_moderation_request(user, "users", user)
|
||||
db.users.update_one({"username": username}, updater)
|
||||
return get(user, data.get("username", username))
|
||||
|
||||
|
37
api/app.py
37
api/app.py
@ -77,7 +77,7 @@ def handle_unprocessable_entity(e):
|
||||
message += f"""{str(key)}: """
|
||||
return build_message(message, d[key])
|
||||
elif isinstance(d[key], list):
|
||||
message += f"""{str(key)}: {",\n ".join(d[key])}\n"""
|
||||
message += f"""{str(key)}: {',\n '.join(d[key])}\n"""
|
||||
return message
|
||||
|
||||
if validation_errors:
|
||||
@ -234,7 +234,7 @@ def users_username_get(username):
|
||||
@use_args(
|
||||
{
|
||||
"username": fields.Str(validate=validate.Length(min=3)),
|
||||
"avatar": fields.Str(allow_none=True),
|
||||
"avatar": fields.Str(),
|
||||
"bio": fields.Str(),
|
||||
"location": fields.Str(),
|
||||
"website": fields.Str(),
|
||||
@ -458,7 +458,6 @@ def groups_route_get():
|
||||
"name": fields.Str(required=True, validate=validate.Length(min=3)),
|
||||
"description": fields.Str(),
|
||||
"closed": fields.Bool(),
|
||||
"advertised": fields.Bool(),
|
||||
}
|
||||
)
|
||||
def groups_route_post(args):
|
||||
@ -479,7 +478,6 @@ def group_route(id):
|
||||
"name": fields.Str(),
|
||||
"description": fields.Str(),
|
||||
"closed": fields.Bool(),
|
||||
"advertised": fields.Bool(),
|
||||
"memberPermissions": fields.List(fields.Str()),
|
||||
"image": fields.Str(allow_none=True),
|
||||
}
|
||||
@ -758,37 +756,6 @@ def root_groups():
|
||||
return util.jsonify(root.get_groups(util.get_user(required=True)))
|
||||
|
||||
|
||||
@app.route("/root/moderation", methods=["GET"])
|
||||
def root_moderation():
|
||||
return util.jsonify(root.get_moderation(util.get_user(required=True)))
|
||||
|
||||
|
||||
@app.route("/root/moderation/<item_type>/<id>", methods=["PUT", "DELETE"])
|
||||
def root_moderation_item(item_type, id):
|
||||
return util.jsonify(
|
||||
root.moderate(
|
||||
util.get_user(required=True), item_type, id, request.method == "PUT"
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
## REPORTS
|
||||
|
||||
|
||||
@app.route("/reports", methods=["POST"])
|
||||
@use_args(
|
||||
{
|
||||
"referrer": fields.Str(),
|
||||
"url": fields.Str(required=True, validate=validate.Length(min=5)),
|
||||
"description": fields.Str(required=True, validate=validate.Length(min=5)),
|
||||
"email": fields.Email(allow_none=True),
|
||||
}
|
||||
)
|
||||
def reports(args):
|
||||
util.send_report_email(args)
|
||||
return {"success": True}
|
||||
|
||||
|
||||
## ActivityPub Support
|
||||
|
||||
|
||||
|
4
api/lint.sh
Executable file
4
api/lint.sh
Executable file
@ -0,0 +1,4 @@
|
||||
#!/bin/bash
|
||||
|
||||
ruff format .
|
||||
ruff check --fix .
|
1836
api/poetry.lock
generated
1836
api/poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,32 +1,31 @@
|
||||
[tool.poetry]
|
||||
name = "api"
|
||||
version = "0.1.0"
|
||||
package-mode = false
|
||||
description = "Treadl API"
|
||||
authors = ["Will <will@treadl.com>"]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.12"
|
||||
flask = "^3.1.0"
|
||||
bcrypt = "^4.3.0"
|
||||
pyjwt = "^2.10.0"
|
||||
boto3 = "^1.37.4"
|
||||
flask-cors = "^5.0.1"
|
||||
dnspython = "^2.7.0"
|
||||
flask = "^3.0.3"
|
||||
bcrypt = "^4.2.0"
|
||||
pyjwt = "^2.9.0"
|
||||
boto3 = "^1.35.34"
|
||||
flask-cors = "^5.0.0"
|
||||
dnspython = "^2.6.1"
|
||||
requests = "^2.32.3"
|
||||
pymongo = "^4.11.1"
|
||||
flask_limiter = "^3.10.1"
|
||||
firebase-admin = "^6.6.0"
|
||||
pymongo = "^4.10.1"
|
||||
flask_limiter = "^3.8.0"
|
||||
firebase-admin = "^6.5.0"
|
||||
blurhash-python = "^1.2.2"
|
||||
gunicorn = "^23.0.0"
|
||||
sentry-sdk = {extras = ["flask"], version = "^2.22.0"}
|
||||
pyOpenSSL = "^25.0.0"
|
||||
sentry-sdk = {extras = ["flask"], version = "^2.15.0"}
|
||||
pyOpenSSL = "^24.2.1"
|
||||
webargs = "^8.6.0"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
ruff = "^0.9.9"
|
||||
ruff = "^0.6.9"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry>=0.12"]
|
||||
|
@ -1,4 +1,3 @@
|
||||
import os
|
||||
import json
|
||||
import datetime
|
||||
from flask import request, Response
|
||||
@ -8,7 +7,7 @@ from cryptography.hazmat.primitives import serialization
|
||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||
from bson.objectid import ObjectId
|
||||
from api import accounts
|
||||
from util import util, mail
|
||||
from util import util
|
||||
|
||||
errors = werkzeug.exceptions
|
||||
|
||||
@ -93,34 +92,6 @@ def build_updater(obj, allowed_keys):
|
||||
return updater
|
||||
|
||||
|
||||
def send_report_email(report):
|
||||
if not report:
|
||||
return
|
||||
mail.send(
|
||||
{
|
||||
"to": os.environ.get("ADMIN_EMAIL"),
|
||||
"subject": "{} report".format(os.environ.get("APP_NAME")),
|
||||
"text": "A new report has been submitted: {0}".format(
|
||||
json.dumps(report, indent=4)
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def send_moderation_request(from_user, item_type, item):
|
||||
if not from_user or not item_type or not item:
|
||||
return
|
||||
mail.send(
|
||||
{
|
||||
"to": os.environ.get("ADMIN_EMAIL"),
|
||||
"subject": "{} moderation needed".format(os.environ.get("APP_NAME")),
|
||||
"text": "New content has been added by {0} ({1}) and needs moderating: {2} ({3})".format(
|
||||
from_user["username"], from_user["email"], item_type, item["_id"]
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def generate_rsa_keypair():
|
||||
private_key = rsa.generate_private_key(public_exponent=65537, key_size=4096)
|
||||
private_pem = private_key.private_bytes(
|
||||
|
147
api/util/wif.py
147
api/util/wif.py
@ -12,7 +12,7 @@ def normalise_colour(max_color, triplet):
|
||||
components = triplet.split(",")
|
||||
new_components = []
|
||||
for component in components:
|
||||
new_components.append(str(int(float(color_factor) * int(float(component)))))
|
||||
new_components.append(str(int(float(color_factor) * int(component))))
|
||||
return ",".join(new_components)
|
||||
|
||||
|
||||
@ -152,35 +152,11 @@ def dumps(obj):
|
||||
|
||||
|
||||
def loads(wif_file):
|
||||
# Ensure file exists:
|
||||
if not wif_file or type(wif_file) is not str:
|
||||
raise Exception("Invalid file: null or empty or not string")
|
||||
|
||||
# Some user-uploaded files (Quickdraw?) start with strange HTTP header info.
|
||||
# Remove all preceding non-section lines:
|
||||
wif_file = "[" + wif_file.split("[", 1)[1]
|
||||
|
||||
# Make all section names lowercase
|
||||
normalized_lines = []
|
||||
for line in wif_file.splitlines():
|
||||
if line.strip().startswith("[") and line.strip().endswith("]"):
|
||||
section_name = line.strip()[1:-1].lower()
|
||||
normalized_lines.append(f"[{section_name}]")
|
||||
else:
|
||||
normalized_lines.append(line)
|
||||
wif_file = "\n".join(normalized_lines)
|
||||
|
||||
# Load config
|
||||
config = configparser.ConfigParser(
|
||||
allow_no_value=True, strict=False, inline_comment_prefixes=("#", ";")
|
||||
)
|
||||
config.read_string(wif_file)
|
||||
config = configparser.ConfigParser(allow_no_value=True, strict=False)
|
||||
config.read_string(wif_file.lower())
|
||||
DEFAULT_TITLE = "Untitled Pattern"
|
||||
draft = {}
|
||||
|
||||
if "wif" in config:
|
||||
draft["wifInfo"] = dict(config["wif"])
|
||||
draft["wifInfo"]["importedFile"] = wif_file
|
||||
if "text" in config:
|
||||
text = config["text"]
|
||||
draft["name"] = text.get("title") or DEFAULT_TITLE
|
||||
@ -206,36 +182,30 @@ def loads(wif_file):
|
||||
normalise_colour(255, "0,0,255"),
|
||||
]
|
||||
|
||||
weaving = config["weaving"] if "weaving" in config else None
|
||||
weaving = config["weaving"]
|
||||
|
||||
threading = config["threading"] if "threading" in config else []
|
||||
warp = config["warp"] if "warp" in config else None
|
||||
threading = config["threading"]
|
||||
warp = config["warp"]
|
||||
draft["warp"] = {}
|
||||
draft["warp"]["shafts"] = weaving.getint("shafts") if weaving else 0
|
||||
draft["warp"]["shafts"] = weaving.getint("shafts")
|
||||
draft["warp"]["threading"] = []
|
||||
|
||||
# Work out default warp colour
|
||||
if warp and warp.get("color"):
|
||||
if warp.get("color"):
|
||||
warp_colour_index = warp.getint("color") - 1
|
||||
if warp_colour_index < len(draft["colours"]):
|
||||
draft["warp"]["defaultColour"] = draft["colours"][warp_colour_index]
|
||||
if not draft.get("warp").get("defaultColour"):
|
||||
draft["warp"]["defaultColour"] = draft["colours"][warp_colour_index]
|
||||
|
||||
else:
|
||||
# In case of no color table or colour index out of bounds
|
||||
draft["warp"]["defaultColour"] = draft["colours"][0]
|
||||
|
||||
for x in threading:
|
||||
shaft = threading[x].strip()
|
||||
shaft = threading[x]
|
||||
if "," in shaft:
|
||||
shaft = shaft.split(",")[0]
|
||||
shaft = int(shaft) if shaft else 0
|
||||
while int(x) > len(
|
||||
draft["warp"]["threading"]
|
||||
): # grow threading array to current x
|
||||
shaft = int(shaft)
|
||||
while int(x) >= len(draft["warp"]["threading"]) - 1:
|
||||
draft["warp"]["threading"].append({"shaft": 0})
|
||||
draft["warp"]["threading"][int(x) - 1] = {"shaft": shaft}
|
||||
if shaft > draft["warp"]["shafts"]:
|
||||
draft["warp"]["shafts"] = shaft
|
||||
draft["warp"]["guideFrequency"] = draft["warp"]["shafts"]
|
||||
try:
|
||||
warp_colours = config["warp colors"]
|
||||
for x in warp_colours:
|
||||
@ -244,37 +214,28 @@ def loads(wif_file):
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
if not draft["warp"]["threading"]: # Make a bunch of empty threads
|
||||
draft["warp"]["threading"] = [{"shaft": 0} for i in range(20)]
|
||||
|
||||
treadling = config["treadling"] if "treadling" in config else []
|
||||
weft = config["weft"] if "weft" in config else None
|
||||
treadling = config["treadling"]
|
||||
weft = config["weft"]
|
||||
draft["weft"] = {}
|
||||
draft["weft"]["treadles"] = weaving.getint("treadles") if weaving else 0
|
||||
draft["weft"]["treadles"] = weaving.getint("treadles")
|
||||
draft["weft"]["treadling"] = []
|
||||
|
||||
# Work out default weft colour
|
||||
if weft and weft.get("color"):
|
||||
if weft.get("color"):
|
||||
weft_colour_index = weft.getint("color") - 1
|
||||
if weft_colour_index < len(draft["colours"]):
|
||||
draft["weft"]["defaultColour"] = draft["colours"][weft_colour_index]
|
||||
if not draft.get("weft").get("defaultColour"):
|
||||
draft["weft"]["defaultColour"] = draft["colours"][weft_colour_index]
|
||||
else:
|
||||
# In case of no color table or colour index out of bounds
|
||||
draft["weft"]["defaultColour"] = draft["colours"][1]
|
||||
|
||||
for x in treadling:
|
||||
treadle = treadling[x].strip()
|
||||
if "," in treadle:
|
||||
treadle = treadle.split(",")[0]
|
||||
treadle = int(treadle) if treadle else 0
|
||||
while int(x) > len(
|
||||
draft["weft"]["treadling"]
|
||||
): # grow treadling array to current x
|
||||
shaft = treadling[x]
|
||||
if "," in shaft:
|
||||
shaft = shaft.split(",")[0]
|
||||
shaft = int(shaft)
|
||||
while int(x) >= len(draft["weft"]["treadling"]) - 1:
|
||||
draft["weft"]["treadling"].append({"treadle": 0})
|
||||
draft["weft"]["treadling"][int(x) - 1] = {"treadle": treadle}
|
||||
if treadle > draft["weft"]["treadles"]:
|
||||
draft["weft"]["treadles"] = treadle
|
||||
draft["weft"]["guideFrequency"] = draft["weft"]["treadles"]
|
||||
draft["weft"]["treadling"][int(x) - 1] = {"treadle": shaft}
|
||||
try:
|
||||
weft_colours = config["weft colors"]
|
||||
for x in weft_colours:
|
||||
@ -283,20 +244,17 @@ def loads(wif_file):
|
||||
]
|
||||
except Exception:
|
||||
pass
|
||||
if not draft["weft"]["treadling"]: # Make a bunch of empty threads
|
||||
draft["weft"]["treadling"] = [{"treadle": 0} for i in range(20)]
|
||||
|
||||
tieup = config["tieup"] if "tieup" in config else None
|
||||
draft["tieups"] = []
|
||||
if tieup:
|
||||
for x in tieup:
|
||||
while int(x) >= len(draft["tieups"]) - 1:
|
||||
draft["tieups"].append([])
|
||||
try:
|
||||
split = tieup[x].split(",")
|
||||
draft["tieups"][int(x) - 1] = [int(i) for i in split]
|
||||
except Exception:
|
||||
draft["tieups"][int(x) - 1] = []
|
||||
tieup = config["tieup"]
|
||||
draft["tieups"] = [] # [0]*len(tieup)
|
||||
for x in tieup:
|
||||
while int(x) >= len(draft["tieups"]) - 1:
|
||||
draft["tieups"].append([])
|
||||
split = tieup[x].split(",")
|
||||
try:
|
||||
draft["tieups"][int(x) - 1] = [int(i) for i in split]
|
||||
except Exception:
|
||||
draft["tieups"][int(x) - 1] = []
|
||||
|
||||
return draft
|
||||
|
||||
@ -365,9 +323,6 @@ def draw_image(obj, with_plan=False):
|
||||
drawdown_left = warp_left if with_plan else 0
|
||||
drawdown_bottom = weft_bottom if with_plan else full_height
|
||||
|
||||
warp_guides = warp.get("guideFrequency") or 0
|
||||
weft_guides = weft.get("guideFrequency") or 0
|
||||
|
||||
WHITE = (255, 255, 255)
|
||||
GREY = (150, 150, 150)
|
||||
BLACK = (0, 0, 0)
|
||||
@ -393,10 +348,7 @@ def draw_image(obj, with_plan=False):
|
||||
width=1,
|
||||
joint=None,
|
||||
)
|
||||
col_index = 1
|
||||
for i, x in enumerate(range(len(warp["threading"]) - 1, 0, -1)):
|
||||
is_guide = warp_guides and col_index % warp_guides == 0
|
||||
col_index += 1
|
||||
thread = warp["threading"][i]
|
||||
xcoord = x * BASE_SIZE
|
||||
draw.line(
|
||||
@ -404,8 +356,8 @@ def draw_image(obj, with_plan=False):
|
||||
(xcoord, warp_top),
|
||||
(xcoord, warp_bottom),
|
||||
],
|
||||
fill=BLACK if is_guide else GREY,
|
||||
width=2 if is_guide else 1,
|
||||
fill=GREY,
|
||||
width=1,
|
||||
joint=None,
|
||||
)
|
||||
if thread.get("shaft", 0) > 0:
|
||||
@ -445,10 +397,7 @@ def draw_image(obj, with_plan=False):
|
||||
width=1,
|
||||
joint=None,
|
||||
)
|
||||
row_index = 0
|
||||
for i, y in enumerate(range(0, len(weft["treadling"]))):
|
||||
is_guide = weft_guides and row_index % weft_guides == 0
|
||||
row_index += 1
|
||||
thread = weft["treadling"][i]
|
||||
ycoord = weft_top + y * BASE_SIZE
|
||||
draw.line(
|
||||
@ -456,8 +405,8 @@ def draw_image(obj, with_plan=False):
|
||||
(weft_left, ycoord),
|
||||
(weft_right, ycoord),
|
||||
],
|
||||
fill=BLACK if is_guide else GREY,
|
||||
width=2 if is_guide else 1,
|
||||
fill=GREY,
|
||||
width=1,
|
||||
joint=None,
|
||||
)
|
||||
if thread.get("treadle", 0) > 0:
|
||||
@ -536,9 +485,7 @@ def draw_image(obj, with_plan=False):
|
||||
shaft = 0 if warp_thread["shaft"] > warp["shafts"] else warp_thread["shaft"]
|
||||
|
||||
# Work out if should be warp or weft in "front"
|
||||
tieup = (
|
||||
tieups[treadle - 1] if (treadle > 0 and treadle <= len(tieups)) else []
|
||||
)
|
||||
tieup = tieups[treadle - 1] if treadle > 0 else []
|
||||
tieup = [t for t in tieup if t <= warp["shafts"]]
|
||||
thread_type = "warp" if shaft in tieup else "weft"
|
||||
|
||||
@ -581,13 +528,9 @@ def draw_image(obj, with_plan=False):
|
||||
in_mem_file = io.BytesIO()
|
||||
img.save(in_mem_file, "PNG")
|
||||
in_mem_file.seek(0)
|
||||
file_name_prefix = "preview-{0}_{1}".format(
|
||||
"full" if with_plan else "base", obj["_id"]
|
||||
file_name = "preview-{0}_{1}-{2}.png".format(
|
||||
"full" if with_plan else "base", obj["_id"], int(time.time())
|
||||
)
|
||||
file_name = "{0}-{1}.png".format(file_name_prefix, int(time.time()))
|
||||
folder = "projects/{}".format(obj["project"])
|
||||
# Delete existing preview images of this type
|
||||
uploads.delete_folder("{}/{}".format(folder, file_name_prefix))
|
||||
# Upload the new preview image
|
||||
uploads.upload_file("{}/{}".format(folder, file_name), in_mem_file)
|
||||
path = "projects/{}/{}".format(obj["project"], file_name)
|
||||
uploads.upload_file(path, in_mem_file)
|
||||
return file_name
|
||||
|
@ -1,40 +0,0 @@
|
||||
# Stage 1: Build React SPA
|
||||
FROM node:20 AS react-build
|
||||
WORKDIR /app
|
||||
COPY web/package.json web/package-lock.json ./
|
||||
RUN npm install
|
||||
COPY web/ ./
|
||||
RUN npx vite build
|
||||
|
||||
# Stage 2: Set up Nginx with React and Flask
|
||||
FROM python:3.12-slim
|
||||
WORKDIR /app
|
||||
|
||||
# Install Flask and dependencies
|
||||
RUN pip install poetry
|
||||
COPY api/poetry.lock .
|
||||
COPY api/pyproject.toml .
|
||||
RUN poetry config virtualenvs.create false --local
|
||||
RUN poetry install
|
||||
|
||||
# Copy Flask app
|
||||
COPY api/ ./
|
||||
|
||||
# Install Nginx
|
||||
RUN apt-get update && apt-get install -y nginx && rm -rf /var/lib/apt/lists/*
|
||||
RUN unlink /etc/nginx/sites-enabled/default # Ensure default Nginx configuration is not used
|
||||
|
||||
# Copy React build files into Nginx's static directory
|
||||
COPY --from=react-build /app/dist /usr/share/nginx/html
|
||||
|
||||
# Copy custom Nginx configuration file
|
||||
COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
# Expose ports for Nginx
|
||||
EXPOSE 80
|
||||
|
||||
# Start both Flask and Nginx using a script
|
||||
COPY docker/start.sh /start.sh
|
||||
RUN chmod +x /start.sh
|
||||
|
||||
CMD ["/start.sh"]
|
@ -1,33 +0,0 @@
|
||||
services:
|
||||
treadl:
|
||||
image: wilw/treadl:latest
|
||||
ports:
|
||||
- "8080:80"
|
||||
environment:
|
||||
# App settings
|
||||
- JWT_SECRET=secret # Change this to a secure secret
|
||||
- APP_URL=http://example.com
|
||||
- APP_DOMAIN=example.com
|
||||
- APP_NAME=Treadl
|
||||
|
||||
# MongoDB connection
|
||||
- MONGO_URL=mongodb://mongo:27017/treadl
|
||||
- MONGO_DATABASE=treadl
|
||||
|
||||
# Mailgun email settings
|
||||
- MAILGUN_URL=
|
||||
- MAILGUN_KEY
|
||||
- FROM_EMAIL= # An email address to send emails from
|
||||
|
||||
# Email addresses
|
||||
- CONTACT_EMAIL= # An email address for people to contact you
|
||||
- ADMIN_EMAIL= # An email address for admin notifications
|
||||
|
||||
# S3 storage settings
|
||||
- AWS_S3_ENDPOINT=https://eu-central-1.linodeobjects.com/
|
||||
- AWS_S3_BUCKET=treadl
|
||||
- AWS_ACCESS_KEY_ID=
|
||||
- AWS_SECRET_ACCESS_KEY=
|
||||
|
||||
mongo:
|
||||
image: mongo:6
|
@ -1,22 +0,0 @@
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
# Serve React static files for all non-API routes
|
||||
location / {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html;
|
||||
try_files $uri /index.html;
|
||||
}
|
||||
|
||||
# Proxy API requests to Flask backend
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:5000/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
gzip on;
|
||||
gzip_types text/plain application/json text/css application/javascript;
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Start Flask app in the background
|
||||
gunicorn -b 0.0.0.0:5000 app:app &
|
||||
|
||||
# Start Nginx in the foreground
|
||||
nginx -g "daemon off;"
|
||||
|
@ -21,6 +21,6 @@
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>MinimumOSVersion</key>
|
||||
<string>12.0</string>
|
||||
<string>11.0</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
# platform :ios, '11.0'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
@ -1,118 +1,112 @@
|
||||
PODS:
|
||||
- DKImagePickerController/Core (4.3.9):
|
||||
- DKImagePickerController/Core (4.3.4):
|
||||
- DKImagePickerController/ImageDataManager
|
||||
- DKImagePickerController/Resource
|
||||
- DKImagePickerController/ImageDataManager (4.3.9)
|
||||
- DKImagePickerController/PhotoGallery (4.3.9):
|
||||
- DKImagePickerController/ImageDataManager (4.3.4)
|
||||
- DKImagePickerController/PhotoGallery (4.3.4):
|
||||
- DKImagePickerController/Core
|
||||
- DKPhotoGallery
|
||||
- DKImagePickerController/Resource (4.3.9)
|
||||
- DKPhotoGallery (0.0.19):
|
||||
- DKPhotoGallery/Core (= 0.0.19)
|
||||
- DKPhotoGallery/Model (= 0.0.19)
|
||||
- DKPhotoGallery/Preview (= 0.0.19)
|
||||
- DKPhotoGallery/Resource (= 0.0.19)
|
||||
- DKImagePickerController/Resource (4.3.4)
|
||||
- DKPhotoGallery (0.0.17):
|
||||
- DKPhotoGallery/Core (= 0.0.17)
|
||||
- DKPhotoGallery/Model (= 0.0.17)
|
||||
- DKPhotoGallery/Preview (= 0.0.17)
|
||||
- DKPhotoGallery/Resource (= 0.0.17)
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Core (0.0.19):
|
||||
- DKPhotoGallery/Core (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Preview
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Model (0.0.19):
|
||||
- DKPhotoGallery/Model (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Preview (0.0.19):
|
||||
- DKPhotoGallery/Preview (0.0.17):
|
||||
- DKPhotoGallery/Model
|
||||
- DKPhotoGallery/Resource
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- DKPhotoGallery/Resource (0.0.19):
|
||||
- DKPhotoGallery/Resource (0.0.17):
|
||||
- SDWebImage
|
||||
- SwiftyGif
|
||||
- file_picker (0.0.1):
|
||||
- DKImagePickerController/PhotoGallery
|
||||
- Flutter
|
||||
- Firebase/CoreOnly (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- Firebase/Messaging (11.8.0):
|
||||
- Firebase/CoreOnly (10.9.0):
|
||||
- FirebaseCore (= 10.9.0)
|
||||
- Firebase/Messaging (10.9.0):
|
||||
- Firebase/CoreOnly
|
||||
- FirebaseMessaging (~> 11.8.0)
|
||||
- firebase_core (3.12.1):
|
||||
- Firebase/CoreOnly (= 11.8.0)
|
||||
- FirebaseMessaging (~> 10.9.0)
|
||||
- firebase_core (2.13.1):
|
||||
- Firebase/CoreOnly (= 10.9.0)
|
||||
- Flutter
|
||||
- firebase_messaging (15.2.4):
|
||||
- Firebase/Messaging (= 11.8.0)
|
||||
- firebase_messaging (14.6.2):
|
||||
- Firebase/Messaging (= 10.9.0)
|
||||
- firebase_core
|
||||
- Flutter
|
||||
- FirebaseCore (11.8.1):
|
||||
- FirebaseCoreInternal (~> 11.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/Logger (~> 8.0)
|
||||
- FirebaseCoreInternal (11.8.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 8.0)"
|
||||
- FirebaseInstallations (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- FirebaseMessaging (11.8.0):
|
||||
- FirebaseCore (~> 11.8.0)
|
||||
- FirebaseInstallations (~> 11.0)
|
||||
- GoogleDataTransport (~> 10.0)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 8.0)
|
||||
- GoogleUtilities/Environment (~> 8.0)
|
||||
- GoogleUtilities/Reachability (~> 8.0)
|
||||
- GoogleUtilities/UserDefaults (~> 8.0)
|
||||
- nanopb (~> 3.30910.0)
|
||||
- FirebaseCore (10.9.0):
|
||||
- FirebaseCoreInternal (~> 10.0)
|
||||
- GoogleUtilities/Environment (~> 7.8)
|
||||
- GoogleUtilities/Logger (~> 7.8)
|
||||
- FirebaseCoreInternal (10.10.0):
|
||||
- "GoogleUtilities/NSData+zlib (~> 7.8)"
|
||||
- FirebaseInstallations (10.10.0):
|
||||
- FirebaseCore (~> 10.0)
|
||||
- GoogleUtilities/Environment (~> 7.8)
|
||||
- GoogleUtilities/UserDefaults (~> 7.8)
|
||||
- PromisesObjC (~> 2.1)
|
||||
- FirebaseMessaging (10.9.0):
|
||||
- FirebaseCore (~> 10.0)
|
||||
- FirebaseInstallations (~> 10.0)
|
||||
- GoogleDataTransport (~> 9.2)
|
||||
- GoogleUtilities/AppDelegateSwizzler (~> 7.8)
|
||||
- GoogleUtilities/Environment (~> 7.8)
|
||||
- GoogleUtilities/Reachability (~> 7.8)
|
||||
- GoogleUtilities/UserDefaults (~> 7.8)
|
||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||
- Flutter (1.0.0)
|
||||
- GoogleDataTransport (10.1.0):
|
||||
- nanopb (~> 3.30910.0)
|
||||
- PromisesObjC (~> 2.4)
|
||||
- GoogleUtilities/AppDelegateSwizzler (8.0.2):
|
||||
- GoogleDataTransport (9.2.3):
|
||||
- GoogleUtilities/Environment (~> 7.7)
|
||||
- nanopb (< 2.30910.0, >= 2.30908.0)
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/AppDelegateSwizzler (7.11.1):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Network
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Environment (8.0.2):
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Logger (8.0.2):
|
||||
- GoogleUtilities/Environment (7.11.1):
|
||||
- PromisesObjC (< 3.0, >= 1.2)
|
||||
- GoogleUtilities/Logger (7.11.1):
|
||||
- GoogleUtilities/Environment
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Network (8.0.2):
|
||||
- GoogleUtilities/Network (7.11.1):
|
||||
- GoogleUtilities/Logger
|
||||
- "GoogleUtilities/NSData+zlib"
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Reachability
|
||||
- "GoogleUtilities/NSData+zlib (8.0.2)":
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/Privacy (8.0.2)
|
||||
- GoogleUtilities/Reachability (8.0.2):
|
||||
- "GoogleUtilities/NSData+zlib (7.11.1)"
|
||||
- GoogleUtilities/Reachability (7.11.1):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- GoogleUtilities/UserDefaults (8.0.2):
|
||||
- GoogleUtilities/UserDefaults (7.11.1):
|
||||
- GoogleUtilities/Logger
|
||||
- GoogleUtilities/Privacy
|
||||
- image_picker_ios (0.0.1):
|
||||
- Flutter
|
||||
- nanopb (3.30910.0):
|
||||
- nanopb/decode (= 3.30910.0)
|
||||
- nanopb/encode (= 3.30910.0)
|
||||
- nanopb/decode (3.30910.0)
|
||||
- nanopb/encode (3.30910.0)
|
||||
- nanopb (2.30909.0):
|
||||
- nanopb/decode (= 2.30909.0)
|
||||
- nanopb/encode (= 2.30909.0)
|
||||
- nanopb/decode (2.30909.0)
|
||||
- nanopb/encode (2.30909.0)
|
||||
- path_provider_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- PromisesObjC (2.4.0)
|
||||
- SDWebImage (5.21.0):
|
||||
- SDWebImage/Core (= 5.21.0)
|
||||
- SDWebImage/Core (5.21.0)
|
||||
- PromisesObjC (2.2.0)
|
||||
- SDWebImage (5.18.8):
|
||||
- SDWebImage/Core (= 5.18.8)
|
||||
- SDWebImage/Core (5.18.8)
|
||||
- share_plus (0.0.1):
|
||||
- Flutter
|
||||
- shared_preferences_foundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- SwiftyGif (5.4.5)
|
||||
- SwiftyGif (5.4.4)
|
||||
- url_launcher_ios (0.0.1):
|
||||
- Flutter
|
||||
|
||||
@ -164,29 +158,29 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/url_launcher_ios/ios"
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
DKImagePickerController: 946cec48c7873164274ecc4624d19e3da4c1ef3c
|
||||
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
|
||||
file_picker: a0560bc09d61de87f12d246fc47d2119e6ef37be
|
||||
Firebase: d80354ed7f6df5f9aca55e9eb47cc4b634735eaf
|
||||
firebase_core: 8d552814f6c01ccde5d88939fced4ec26f2f5510
|
||||
firebase_messaging: 8b96a4f09841c15a16b96973ef5c3dcfc1a064e4
|
||||
FirebaseCore: 99fe0c4b44a39f37d99e6404e02009d2db5d718d
|
||||
FirebaseCoreInternal: df24ce5af28864660ecbd13596fc8dd3a8c34629
|
||||
FirebaseInstallations: 6c963bd2a86aca0481eef4f48f5a4df783ae5917
|
||||
FirebaseMessaging: 487b634ccdf6f7b7ff180fdcb2a9935490f764e8
|
||||
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
|
||||
GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7
|
||||
GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d
|
||||
image_picker_ios: afb77645f1e1060a27edb6793996ff9b42256909
|
||||
nanopb: fad817b59e0457d11a5dfbde799381cd727c1275
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
SDWebImage: f84b0feeb08d2d11e6a9b843cb06d75ebf5b8868
|
||||
share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4
|
||||
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
|
||||
DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac
|
||||
DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179
|
||||
file_picker: 15fd9539e4eb735dc54bae8c0534a7a9511a03de
|
||||
Firebase: bd152f0f3d278c4060c5c71359db08ebcfd5a3e2
|
||||
firebase_core: ce64b0941c6d87c6ef5022ae9116a158236c8c94
|
||||
firebase_messaging: 42912365e62efc1ea3e00724e5eecba6068ddb88
|
||||
FirebaseCore: b68d3616526ec02e4d155166bbafb8eca64af557
|
||||
FirebaseCoreInternal: 971029061d326000d65bfdc21f5502c75c8b0893
|
||||
FirebaseInstallations: 52153982b057d3afcb4e1fbb3eb0b6d00611e681
|
||||
FirebaseMessaging: 6b7052cc3da7bc8e5f72bef871243e8f04a14eed
|
||||
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
|
||||
GoogleDataTransport: f0308f5905a745f94fb91fea9c6cbaf3831cb1bd
|
||||
GoogleUtilities: 9aa0ad5a7bc171f8bae016300bfcfa3fb8425749
|
||||
image_picker_ios: 4a8aadfbb6dc30ad5141a2ce3832af9214a705b5
|
||||
nanopb: b552cce312b6c8484180ef47159bc0f65a1f0431
|
||||
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
|
||||
PromisesObjC: 09985d6d70fbe7878040aa746d78236e6946d2ef
|
||||
SDWebImage: a81bbb3ba4ea5f810f4069c68727cb118467a04a
|
||||
share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5
|
||||
shared_preferences_foundation: e2dae3258e06f44cc55f49d42024fd8dd03c590c
|
||||
SwiftyGif: 93a1cc87bf3a51916001cf8f3d63835fb64c819f
|
||||
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
|
||||
|
||||
PODFILE CHECKSUM: c4c93c5f6502fe2754f48404d3594bf779584011
|
||||
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3
|
||||
|
||||
COCOAPODS: 1.16.2
|
||||
COCOAPODS: 1.14.2
|
||||
|
@ -156,7 +156,6 @@
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
2341743D762090318428F35C /* [CP] Embed Pods Frameworks */,
|
||||
4777130FB39D1044BC14FC9C /* [CP] Copy Pods Resources */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@ -173,7 +172,7 @@
|
||||
97C146E61CF9000F007C117D /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 1510;
|
||||
LastUpgradeCheck = 1430;
|
||||
ORGANIZATIONNAME = "";
|
||||
TargetAttributes = {
|
||||
97C146ED1CF9000F007C117D = {
|
||||
@ -249,23 +248,6 @@
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||
};
|
||||
4777130FB39D1044BC14FC9C /* [CP] Copy Pods Resources */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
);
|
||||
inputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
|
||||
);
|
||||
name = "[CP] Copy Pods Resources";
|
||||
outputFileListPaths = (
|
||||
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
shellPath = /bin/sh;
|
||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
|
||||
showEnvVarsInLog = 0;
|
||||
};
|
||||
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||
isa = PBXShellScriptBuildPhase;
|
||||
alwaysOutOfDate = 1;
|
||||
|
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1510"
|
||||
LastUpgradeVersion = "1430"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@ -48,7 +48,6 @@
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
enableGPUValidationMode = "1"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
|
@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
import Flutter
|
||||
|
||||
@main
|
||||
@UIApplicationMain
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
override func application(
|
||||
_ application: UIApplication,
|
||||
|
@ -56,7 +56,7 @@ class _ExploreTabState extends State<ExploreTab> {
|
||||
borderRadius: BorderRadius.all(Radius.circular(10)),
|
||||
),
|
||||
child:Center(
|
||||
child: TextButton(
|
||||
child: CupertinoButton(
|
||||
child: Text('Load more'),
|
||||
onPressed: () => getExploreData(),
|
||||
)
|
||||
|
@ -337,16 +337,17 @@ class CustomText extends StatelessWidget {
|
||||
final String type;
|
||||
final double margin;
|
||||
TextStyle? style;
|
||||
CustomText(this.text, this.type, {this.margin = 0}) { }
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
CustomText(this.text, this.type, {this.margin = 0}) {
|
||||
if (this.type == 'h1') {
|
||||
style = Theme.of(context).textTheme.titleLarge;
|
||||
style = TextStyle(fontSize: 25, fontWeight: FontWeight.bold);
|
||||
}
|
||||
else {
|
||||
style = TextStyle();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
margin: EdgeInsets.all(this.margin),
|
||||
child: Text(text, style: style)
|
||||
@ -366,7 +367,7 @@ class LoginNeeded extends StatelessWidget {
|
||||
Text('You need to login to see this', style: TextStyle(fontSize: 20), textAlign: TextAlign.center),
|
||||
Image(image: AssetImage('assets/login.png'), width: 300),
|
||||
text != null ? Text(text!, textAlign: TextAlign.center) : SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
context.push('/welcome');
|
||||
},
|
||||
|
@ -50,7 +50,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
margin: const EdgeInsets.only(top: 40, left: 10, right: 10),
|
||||
child: ListView(
|
||||
children: <Widget>[
|
||||
Text('Login with your Treadl account', style: Theme.of(context).textTheme.titleLarge),
|
||||
Text('Login with your Treadl account', style: TextStyle(fontSize: 20)),
|
||||
SizedBox(height: 30),
|
||||
TextField(
|
||||
autofocus: true,
|
||||
@ -83,6 +83,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
||||
onPressed: () => _submit(context),
|
||||
child: _loggingIn ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Login",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.white, fontSize: 15)
|
||||
)
|
||||
),
|
||||
]
|
||||
|
@ -78,6 +78,7 @@ class _AppState extends State<MyApp> {
|
||||
title: 'Treadl',
|
||||
theme: ThemeData(
|
||||
primarySwatch: Colors.pink,
|
||||
scaffoldBackgroundColor: Color.fromRGBO(255, 251, 248, 1),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
@ -63,7 +63,8 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
SizedBox(height: 10),
|
||||
Text('You can create as many projects as you like. Upload weaving draft patterns, images, and other files to your projects to store or showcase your work.', style: TextStyle(color: Colors.white, fontSize: 13), textAlign: TextAlign.center),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
color: Colors.white,
|
||||
child: Text('OK, I know what projects are!', style: TextStyle(color: Colors.pink)),
|
||||
onPressed: () => _controller.animateToPage(1, duration: Duration(milliseconds: 500), curve: Curves.easeInOut),
|
||||
)
|
||||
@ -84,7 +85,8 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
SizedBox(height: 10),
|
||||
Text('We recommend enabling push notifications so you can keep up-to-date with your groups and projects.', style: TextStyle(color: Colors.white, fontSize: 14, fontWeight: FontWeight.bold), textAlign: TextAlign.center),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
color: Colors.white,
|
||||
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
|
||||
_loading ? CircularProgressIndicator() : SizedBox(width: 0),
|
||||
_loading ? SizedBox(width: 10) : SizedBox(width: 0),
|
||||
@ -107,7 +109,8 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
||||
SizedBox(height: 10),
|
||||
Text('You\'re ready to get started. We hope you enjoy using Treadl.', style: TextStyle(color: Colors.white, fontSize: 13), textAlign: TextAlign.center),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
color: Colors.white,
|
||||
child: Text('Get started', style: TextStyle(color: Colors.pink)),
|
||||
onPressed: () => context.go('/home'),
|
||||
),
|
||||
|
@ -23,7 +23,7 @@ class DrawdownPainter extends CustomPainter {
|
||||
for (double i = 0; i <= size.width; i += BASE_SIZE) {
|
||||
canvas.drawLine(Offset(i.toDouble(), size.height), Offset(i.toDouble(), 0), paint);
|
||||
}
|
||||
for (double y = 0; y <= size.height; y += BASE_SIZE) {
|
||||
for (double y = 0; y <= size.height; y += BASE_SIZE) {
|
||||
canvas.drawLine(Offset(0, y.toDouble()), Offset(size.width, y.toDouble()), paint);
|
||||
}
|
||||
|
||||
@ -38,7 +38,7 @@ class DrawdownPainter extends CustomPainter {
|
||||
// Only capture valid tie-ups (e.g. in case there is data for more shafts, which are then reduced)
|
||||
// Dart throws error if index < 0 so check fiest
|
||||
List<dynamic> tieup = treadle > 0 ? tieups[treadle - 1] : [];
|
||||
List<dynamic> filteredTieup = tieup.where((t) => t <= warp['shafts']).toList();
|
||||
List<dynamic> filteredTieup = tieup.where((t) => t< warp['shafts']).toList();
|
||||
String threadType = filteredTieup.contains(shaft) ? 'warp' : 'weft';
|
||||
|
||||
Rect rect = Offset(
|
||||
|
@ -60,6 +60,7 @@ class Pattern extends StatelessWidget {
|
||||
} else {
|
||||
tieups[tie].add(shaft);
|
||||
}
|
||||
print(tieups);
|
||||
if (onUpdate != null) {
|
||||
onUpdate!({'tieups': tieups});
|
||||
}
|
||||
|
@ -49,5 +49,20 @@ class _PatternViewerState extends State<PatternViewer> {
|
||||
transformationController: controller,
|
||||
child: RepaintBoundary(child: Pattern(pattern))
|
||||
);
|
||||
|
||||
|
||||
/*return Column(
|
||||
children: [
|
||||
Text('Hi'),
|
||||
Expanded(child: InteractiveViewer(
|
||||
minScale: 0.5,
|
||||
maxScale: 5,
|
||||
constrained: false,
|
||||
transformationController: controller,
|
||||
child: RepaintBoundary(child: Pattern(pattern))))
|
||||
,
|
||||
Text('Another'),
|
||||
]
|
||||
);*/
|
||||
}
|
||||
}
|
||||
|
@ -473,7 +473,7 @@ class _ProjectSettingsDialog extends StatelessWidget {
|
||||
onChanged: (c) => _toggleVisibility(context, c),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text('Private project', style: Theme.of(context).textTheme.bodyMedium),
|
||||
Text('Private project', style: Theme.of(context).textTheme.bodyText1),
|
||||
]
|
||||
)
|
||||
),
|
||||
|
@ -155,6 +155,7 @@ class _ProjectsTabState extends State<ProjectsTab> {
|
||||
floatingActionButton: user != null ? FloatingActionButton(
|
||||
onPressed: showNewProjectDialog,
|
||||
child: _creatingProject ? CircularProgressIndicator(backgroundColor: Colors.white) : Icon(Icons.add),
|
||||
backgroundColor: Colors.pink[500],
|
||||
) : null,
|
||||
);
|
||||
}
|
||||
@ -216,12 +217,13 @@ class _NewProjectDialogState extends State<_NewProjectDialog> {
|
||||
title: Text('Make this project private')
|
||||
),
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
color: Colors.pink,
|
||||
onPressed: _createProject,
|
||||
child: Text('Create'),
|
||||
),
|
||||
SizedBox(height: 10),
|
||||
TextButton(
|
||||
CupertinoButton(
|
||||
onPressed: () {
|
||||
context.pop();
|
||||
},
|
||||
|
@ -14,13 +14,13 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
final Api api = Api();
|
||||
bool _registering = false;
|
||||
|
||||
void _submit(BuildContext context) async {
|
||||
void _submit(context) async {
|
||||
setState(() => _registering = true);
|
||||
var data = await api.request('POST', '/accounts/register', {'username': _usernameController.text, 'email': _emailController.text, 'password': _passwordController.text});
|
||||
setState(() => _registering = false);
|
||||
if (data['success'] == true) {
|
||||
AppModel model = Provider.of<AppModel>(context, listen: false);
|
||||
await model.setToken(data['payload']['token']);
|
||||
model.setToken(data['payload']['token']);
|
||||
context.go('/onboarding');
|
||||
}
|
||||
else {
|
||||
@ -82,7 +82,7 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
textAlign: TextAlign.center,
|
||||
text: TextSpan(
|
||||
text: 'By registering you agree to Treadl\'s ',
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
style: Theme.of(context).textTheme.bodyText1,
|
||||
children: <TextSpan>[
|
||||
TextSpan(text: 'Terms of Use', style: TextStyle(fontWeight: FontWeight.bold, color: Colors.pink), recognizer: new TapGestureRecognizer()..onTap = () => launch('https://treadl.com/terms-of-use')),
|
||||
TextSpan(text: ' and '),
|
||||
@ -94,8 +94,10 @@ class _RegisterScreenState extends State<RegisterScreen> {
|
||||
SizedBox(height: 20),
|
||||
ElevatedButton(
|
||||
onPressed: () => _submit(context),
|
||||
//color: Colors.pink,
|
||||
child: _registering ? SizedBox(height: 20, width: 20, child:CircularProgressIndicator(backgroundColor: Colors.white)) : Text("Register",
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(color: Colors.white, fontSize: 15)
|
||||
)
|
||||
),
|
||||
]
|
||||
|
@ -94,7 +94,7 @@ class SettingsScreen extends StatelessWidget {
|
||||
Text('Thanks for using Treadl', style: Theme.of(context).textTheme.titleLarge),
|
||||
),
|
||||
Container(
|
||||
child: Text("Treadl is an app for managing your projects and for keeping in touch with your weaving communities.\n\nWe're always trying to make Treadl better, so if you have any feedback please let us know!", style: Theme.of(context).textTheme.bodyMedium)
|
||||
child: Text("Treadl is an app for managing your projects and for keeping in touch with your weaving communities.\n\nWe're always trying to make Treadl better, so if you have any feedback please let us know!", style: Theme.of(context).textTheme.bodyText1)
|
||||
),
|
||||
|
||||
SizedBox(height: 30),
|
||||
@ -112,8 +112,9 @@ class SettingsScreen extends StatelessWidget {
|
||||
onTap: () => _deleteAccount(context),
|
||||
),
|
||||
]
|
||||
) : ElevatedButton(
|
||||
child: Text('Join Treadl'),
|
||||
) : CupertinoButton(
|
||||
color: Colors.pink,
|
||||
child: Text('Join Treadl', style: TextStyle(color: Colors.white)),
|
||||
onPressed: () => context.push('/welcome'),
|
||||
),
|
||||
|
||||
|
@ -25,22 +25,25 @@ class WelcomeScreen extends StatelessWidget {
|
||||
SizedBox(height: 10),
|
||||
Text('Treadl is a place for weavers to connect and manage their portfolios.', style: TextStyle(color: Colors.white), textAlign: TextAlign.center),
|
||||
SizedBox(height: 30),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
onPressed: () => _login(context),
|
||||
color: Colors.white,
|
||||
child: new Text("Login",
|
||||
style: TextStyle(color: Colors.pink),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
),
|
||||
SizedBox(height: 15),
|
||||
ElevatedButton(
|
||||
CupertinoButton(
|
||||
onPressed: () => _register(context),
|
||||
color: Colors.pink[400],
|
||||
child: new Text("Register",
|
||||
style: TextStyle(color: Colors.white),
|
||||
textAlign: TextAlign.center,
|
||||
)
|
||||
),
|
||||
SizedBox(height: 35),
|
||||
TextButton(
|
||||
CupertinoButton(
|
||||
onPressed: () => context.pop(),
|
||||
child: new Text("Cancel",
|
||||
style: TextStyle(color: Colors.white),
|
||||
|
@ -5,7 +5,6 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import file_picker
|
||||
import file_selector_macos
|
||||
import firebase_core
|
||||
import firebase_messaging
|
||||
@ -15,7 +14,6 @@ import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseMessagingPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseMessagingPlugin"))
|
||||
|
@ -5,90 +5,82 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: _flutterfire_internals
|
||||
sha256: "7fd72d77a7487c26faab1d274af23fb008763ddc10800261abbfb2c067f183d5"
|
||||
sha256: "9ebe81588e666f7e2b21309f2b5653bd9642d7f27fd0a6894278d2ff40cb9481"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.53"
|
||||
version: "1.3.2"
|
||||
archive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: archive
|
||||
sha256: "7dcbd0f87fe5f61cb28da39a1a8b70dbc106e2fe0516f7836eb7bb2948481a12"
|
||||
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.5"
|
||||
version: "3.3.7"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: args
|
||||
sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04
|
||||
sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
version: "2.4.2"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.12.0"
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.1.1"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.2"
|
||||
version: "1.3.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.1.1"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
version: "1.18.0"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cross_file
|
||||
sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670"
|
||||
sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.4+2"
|
||||
version: "0.3.3+4"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -101,34 +93,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: csslib
|
||||
sha256: "09bad715f418841f976c77db72d5398dc1253c21fb9c0c7f0b0b985860b2d58e"
|
||||
sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.2"
|
||||
version: "0.17.3"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
|
||||
sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.8"
|
||||
version: "1.0.5"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
|
||||
sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
version: "1.3.1"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418"
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.0.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -141,10 +133,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: "8d938fd5c11dc81bf1acd4f7f0486c683fe9e79a0b13419e27730f9ce4d8a25b"
|
||||
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.1"
|
||||
version: "6.1.1"
|
||||
file_selector_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -181,50 +173,50 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core
|
||||
sha256: f4d8f49574a4e396f34567f3eec4d38ab9c3910818dec22ca42b2a467c685d8b
|
||||
sha256: e9b36b391690cf329c6fb1de220045e97c13784c303820cd33962319580a56c6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.12.1"
|
||||
version: "2.13.1"
|
||||
firebase_core_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_platform_interface
|
||||
sha256: d7253d255ff10f85cfd2adaba9ac17bae878fa3ba577462451163bd9f1d1f0bf
|
||||
sha256: b63e3be6c96ef5c33bdec1aab23c91eb00696f6452f0519401d640938c94cba2
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.4.0"
|
||||
version: "4.8.0"
|
||||
firebase_core_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_core_web
|
||||
sha256: faa5a76f6380a9b90b53bc3bdcb85bc7926a382e0709b9b5edac9f7746651493
|
||||
sha256: "8c0f4c87d20e2d001a5915df238c1f9c88704231f591324205f5a5d2a7740a45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.21.1"
|
||||
version: "2.5.0"
|
||||
firebase_messaging:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: firebase_messaging
|
||||
sha256: "5fc345c6341f9dc69fd0ffcbf508c784fd6d1b9e9f249587f30434dd8b6aa281"
|
||||
sha256: a01d7b9eb43a4bad54a411edb2b4124089d88eab029191893e83c39e18ab19f7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.4"
|
||||
version: "14.6.2"
|
||||
firebase_messaging_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_platform_interface
|
||||
sha256: a935924cf40925985c8049df4968b1dde5c704f570f3ce380b31d3de6990dd94
|
||||
sha256: c2fef3e30fbfa3a71d74477df102d1c2f5aad860bb68bb4086b0af3b12abedf3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.6.4"
|
||||
version: "4.5.2"
|
||||
firebase_messaging_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: firebase_messaging_web
|
||||
sha256: fafebf6a1921931334f3f10edb5037a5712288efdd022881e2d093e5654a2fd4
|
||||
sha256: "8d280f0110ca4946b9863e578b9879874066ac486ffa596a609aab329fb6fa7e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.10.4"
|
||||
version: "3.5.2"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -234,34 +226,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_expandable_fab
|
||||
sha256: b14caf78720a48f650e6e1a38d724e33b1f5348d646fa1c266570c31a7f87ef3
|
||||
sha256: "2aa5735bebcdbc49f43bcb32a29f9f03a9b7029212b8cd9837ae332ab2edf647"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.0.0"
|
||||
flutter_html:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_html
|
||||
sha256: "38a2fd702ffdf3243fb7441ab58aa1bc7e6922d95a50db76534de8260638558d"
|
||||
sha256: "02ad69e813ecfc0728a455e4bf892b9379983e050722b1dce00192ee2e41d1ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
version: "3.0.0-beta.2"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: bfa04787c85d80ecb3f8777bde5fc10c3de809240c48fa061a2c2bf15ea5211c
|
||||
sha256: "559c600f056e7c704bd843723c21e01b5fba47e8824bd02422165bcc02a5de1d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.14.3"
|
||||
version: "0.9.3"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "5a1e6fb2c0561958d7e4c33574674bda7b77caaca7a33b758876956f2902eea3"
|
||||
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.27"
|
||||
version: "2.0.15"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -276,26 +268,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: go_router
|
||||
sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3
|
||||
sha256: "3b40e751eaaa855179b416974d59d29669e750d2e50fcdb2b37f1cb0ca8c803a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.8.1"
|
||||
version: "13.0.1"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: html
|
||||
sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec"
|
||||
sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.15.5"
|
||||
version: "0.15.4"
|
||||
http:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.0"
|
||||
version: "0.13.6"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -308,18 +300,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928"
|
||||
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.5.4"
|
||||
version: "3.3.0"
|
||||
image_picker:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: image_picker
|
||||
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
|
||||
sha256: "340efe08645537d6b088a30620ee5752298b1630f23a829181172610b868262b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "1.0.6"
|
||||
image_picker_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -364,10 +356,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image_picker_platform_interface
|
||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||
sha256: "7c7b96bb9413a9c28229e717e6fd1e3edd1cc5569c1778fcca060ecf729b65ee"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.1"
|
||||
version: "2.8.0"
|
||||
image_picker_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -380,42 +372,42 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5"
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.20.2"
|
||||
json_annotation:
|
||||
version: "0.17.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||
name: js
|
||||
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.9.0"
|
||||
version: "0.6.5"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
|
||||
sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.0.8"
|
||||
version: "10.0.0"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
|
||||
sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.9"
|
||||
version: "2.0.1"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
|
||||
sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "2.0.1"
|
||||
list_counter:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -436,26 +428,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
|
||||
sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.17"
|
||||
version: "0.12.16+1"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.1"
|
||||
version: "0.8.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
|
||||
sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.16.0"
|
||||
version: "1.11.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -476,34 +468,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
version: "1.9.0"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd"
|
||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.5"
|
||||
version: "2.1.1"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "0ca7359dad67fd7063cb2892ab0c0737b2daafd807cf1acecd62374c8fae6c12"
|
||||
sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.16"
|
||||
version: "2.2.2"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942"
|
||||
sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.3.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -548,18 +540,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
posix:
|
||||
version: "2.1.4"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: posix
|
||||
sha256: a0117dc2167805aa9125b82eee515cc891819bac2f538c83646d355b16f58b9a
|
||||
name: pointycastle
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.1"
|
||||
version: "3.7.3"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -572,95 +564,95 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: provider
|
||||
sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c
|
||||
sha256: cdbe7530b12ecd9eb455bdaa2fcb8d4dad22e80b8afb4798b41479d5ce26847f
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.2"
|
||||
version: "6.0.5"
|
||||
share_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: fce43200aa03ea87b91ce4c3ac79f0cecd52e2a7a56c7a4185023c271fbfa6da
|
||||
sha256: f74fc3f1cbd99f39760182e176802f693fa0ec9625c045561cfad54681ea93dd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.4"
|
||||
version: "7.2.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: share_plus_platform_interface
|
||||
sha256: cc012a23fc2d479854e6c80150696c4a5f5bb62cb89af4de1c505cf78d0a5d0b
|
||||
sha256: df08bc3a07d01f5ea47b45d03ffcba1fa9cd5370fb44b3f38c70e42cced0f956
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.2"
|
||||
version: "3.3.1"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a"
|
||||
sha256: "396f85b8afc6865182610c0a2fc470853d56499f75f7499e2a73a9f0539d23d0"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.2"
|
||||
version: "2.1.2"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: "3ec7210872c4ba945e3244982918e502fa2bfb5230dff6832459ca0e1879b7ad"
|
||||
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.8"
|
||||
version: "2.1.4"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
|
||||
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.4"
|
||||
version: "2.2.2"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.2.0"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
|
||||
sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.2.0"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
version: "2.1.0"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
version: "2.2.0"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
version: "0.0.99"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c"
|
||||
sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.1"
|
||||
version: "1.10.0"
|
||||
sprintf:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -673,42 +665,42 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
version: "1.11.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.2"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
version: "1.2.0"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
version: "1.2.1"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
|
||||
sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.4"
|
||||
version: "0.6.1"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -721,66 +713,66 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
|
||||
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.1"
|
||||
version: "6.1.11"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
|
||||
sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.15"
|
||||
version: "6.0.35"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
|
||||
sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.2"
|
||||
version: "6.1.4"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
|
||||
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.1"
|
||||
version: "3.0.5"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
|
||||
sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.2"
|
||||
version: "3.0.5"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_platform_interface
|
||||
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
|
||||
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.1.2"
|
||||
url_launcher_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
|
||||
sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.0.17"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
|
||||
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "3.0.6"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -801,26 +793,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14"
|
||||
sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.3.1"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
version: "13.0.0"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
|
||||
sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.12.0"
|
||||
version: "5.0.6"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -846,5 +830,5 @@ packages:
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=3.7.0 <4.0.0"
|
||||
flutter: ">=3.27.0"
|
||||
dart: ">=3.2.0-0 <4.0.0"
|
||||
flutter: ">=3.10.0"
|
||||
|
@ -18,26 +18,26 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
version: 1.2.1+11
|
||||
|
||||
environment:
|
||||
sdk: '>=2.17.0 <4.0.0'
|
||||
sdk: '>=2.17.0 <3.0.0'
|
||||
|
||||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
cupertino_icons: ^1.0.8
|
||||
http: ^1.3.0
|
||||
shared_preferences: ^2.5.2
|
||||
provider: ^6.1.2
|
||||
url_launcher: ^6.3.1
|
||||
flutter_html: ^3.0.0
|
||||
intl: ^0.20.2
|
||||
image_picker: ^1.1.2
|
||||
file_picker: ^9.2.1
|
||||
flutter_launcher_icons: ^0.14.3
|
||||
firebase_messaging: ^15.2.4
|
||||
path_provider: ^2.1.5
|
||||
share_plus: ^10.1.4
|
||||
flutter_expandable_fab: ^2.4.0
|
||||
go_router: ^14.8.1
|
||||
cupertino_icons: ^1.0.4
|
||||
http: ^0.13.4
|
||||
shared_preferences: ^2.0.15
|
||||
provider: ^6.0.3
|
||||
url_launcher: ^6.1.2
|
||||
flutter_html: ^3.0.0-alpha.3
|
||||
intl: ^0.17.0
|
||||
image_picker: ^1.0.6
|
||||
file_picker: ^6.1.1
|
||||
flutter_launcher_icons: ^0.9.0
|
||||
firebase_messaging: ^14.4.0
|
||||
path_provider: ^2.1.1
|
||||
share_plus: ^7.2.1
|
||||
flutter_expandable_fab: ^2.0.0
|
||||
go_router: ^13.0.1
|
||||
|
||||
#fluttertoast: ^8.0.9
|
||||
|
||||
|
21
web/.env
21
web/.env
@ -1,21 +0,0 @@
|
||||
VITE_API_URL="https://api.treadl.com"
|
||||
VITE_IMAGINARY_URL="https://images.treadl.com"
|
||||
VITE_SENTRY_DSN="https://7c88f77dd19c57bfb92bb9eb53e33c4b@o4508066290532352.ingest.de.sentry.io/4508075022090320"
|
||||
VITE_SOURCE_REPO_URL="https://git.wilw.dev/wilw/treadl"
|
||||
VITE_PATREON_URL="https://www.patreon.com/treadl"
|
||||
VITE_KOFI_URL="https://ko-fi.com/wilw88"
|
||||
VITE_STATUS_URL="https://status.wilw.dev/status/treadl"
|
||||
VITE_STATUS_BADGE_URL="https://status.wilw.dev/api/badge/1/uptime?labelPrefix=API+"
|
||||
VITE_IOS_APP_URL="https://apps.apple.com/gb/app/treadl/id1525094357"
|
||||
VITE_ANDROID_APP_URL="https://play.google.com/store/apps/details/Treadl?id=com.treadl"
|
||||
VITE_CONTACT_EMAIL="hello@treadl.com"
|
||||
VITE_APP_NAME="Treadl"
|
||||
VITE_APP_DOMAIN="treadl.com"
|
||||
VITE_TERMS_OF_USE_URL="https://git.wilw.dev/wilw/treadl/wiki/Terms-of-Use"
|
||||
VITE_PRIVACY_POLICY_URL="https://git.wilw.dev/wilw/treadl/wiki/Privacy-Policy"
|
||||
VITE_ONLINE_SAFETY_URL="https://git.wilw.dev/wilw/treadl/wiki/Online-Safety"
|
||||
VITE_ONLINE_SAFETY_POLICY_URL="https://git.wilw.dev/wilw/treadl/wiki/Online-Safety-Policy"
|
||||
VITE_FOLLOWING_ENABLED="false"
|
||||
VITE_GROUP_DISCOVERY_ENABLED="false"
|
||||
VITE_USER_DISCOVERY_ENABLED="false"
|
||||
VITE_GROUPS_ENABLED="false"
|
@ -1 +1,10 @@
|
||||
VITE_API_URL="http://localhost:2001"
|
||||
VITE_IMAGINARY_URL=""
|
||||
VITE_SENTRY_DSN=""
|
||||
VITE_SOURCE_REPO_URL="https://git.wilw.dev/wilw/treadl"
|
||||
VITE_PATREON_URL="https://www.patreon.com/treadl"
|
||||
VITE_KOFI_URL="https://ko-fi.com/wilw88"
|
||||
VITE_IOS_APP_URL="https://apps.apple.com/gb/app/treadl/id1525094357"
|
||||
VITE_ANDROID_APP_URL="https://play.google.com/store/apps/details/Treadl?id=com.treadl"
|
||||
VITE_CONTACT_EMAIL="hello@treadl.com"
|
||||
VITE_APP_NAME="Treadl"
|
||||
|
2
web/.eslintignore
Normal file
2
web/.eslintignore
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules/
|
||||
build/
|
17
web/.eslintrc
Normal file
17
web/.eslintrc
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"extends": ["react-app", "airbnb"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module",
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"rules": {
|
||||
"no-underscore-dangle": [2, { "allow": ["_id"] }],
|
||||
"max-len": 0
|
||||
}
|
||||
}
|
BIN
web/.yarn/cache/@ampproject-remapping-npm-2.2.0-114878fa50-d74d170d06.zip
vendored
Normal file
BIN
web/.yarn/cache/@ampproject-remapping-npm-2.2.0-114878fa50-d74d170d06.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@ampproject-remapping-npm-2.3.0-559c14eee4-d3ad7b89d9.zip
vendored
Normal file
BIN
web/.yarn/cache/@ampproject-remapping-npm-2.3.0-559c14eee4-d3ad7b89d9.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-code-frame-npm-7.18.6-25229a7e34-195e2be317.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-code-frame-npm-7.18.6-25229a7e34-195e2be317.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-code-frame-npm-7.25.7-40a9f53f43-f235cdf9c5.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-code-frame-npm-7.25.7-40a9f53f43-f235cdf9c5.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-compat-data-npm-7.20.1-4eea2d1c20-989b9b7a6f.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-compat-data-npm-7.20.1-4eea2d1c20-989b9b7a6f.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-compat-data-npm-7.25.7-81c45097d1-d1188aed1f.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-compat-data-npm-7.25.7-81c45097d1-d1188aed1f.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-core-npm-7.20.2-7fb00344fc-98faaaef26.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-core-npm-7.20.2-7fb00344fc-98faaaef26.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-core-npm-7.25.7-27df82c8ce-80560a962e.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-core-npm-7.25.7-27df82c8ce-80560a962e.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-generator-npm-7.20.4-97edf6df16-967b59f18e.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-generator-npm-7.20.4-97edf6df16-967b59f18e.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-generator-npm-7.25.7-68dd72ad91-f81cf9dc01.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-generator-npm-7.25.7-68dd72ad91-f81cf9dc01.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-annotate-as-pure-npm-7.18.6-36e25293d8-88ccd15ced.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-annotate-as-pure-npm-7.18.6-36e25293d8-88ccd15ced.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-compilation-targets-npm-7.20.0-f842a8700a-bc183f2109.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-compilation-targets-npm-7.20.0-f842a8700a-bc183f2109.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-compilation-targets-npm-7.25.7-912ef98d47-5b57e7d4b9.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-compilation-targets-npm-7.25.7-912ef98d47-5b57e7d4b9.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-environment-visitor-npm-7.18.9-9f5b3635a1-b25101f616.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-environment-visitor-npm-7.18.9-9f5b3635a1-b25101f616.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-function-name-npm-7.19.0-5bcf55236f-eac1f5db42.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-function-name-npm-7.19.0-5bcf55236f-eac1f5db42.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-hoist-variables-npm-7.18.6-6eb061f405-fd9c35bb43.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-hoist-variables-npm-7.18.6-6eb061f405-fd9c35bb43.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-module-imports-npm-7.18.6-1031faa864-f393f8a3b3.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-module-imports-npm-7.18.6-1031faa864-f393f8a3b3.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-module-imports-npm-7.25.7-f7b3a083a0-a7255755e9.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-module-imports-npm-7.25.7-f7b3a083a0-a7255755e9.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-module-transforms-npm-7.20.2-9a4905d6a2-33a60ca115.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-module-transforms-npm-7.20.2-9a4905d6a2-33a60ca115.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-module-transforms-npm-7.25.7-01310522f0-b1daeded78.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-module-transforms-npm-7.25.7-01310522f0-b1daeded78.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-plugin-utils-npm-7.20.2-63f605bb73-f6cae53b7f.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-plugin-utils-npm-7.20.2-63f605bb73-f6cae53b7f.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-plugin-utils-npm-7.25.7-0b7fcf14ca-eef4450361.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-plugin-utils-npm-7.25.7-0b7fcf14ca-eef4450361.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-simple-access-npm-7.20.2-842ec98fbb-ad1e96ee2e.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-simple-access-npm-7.20.2-842ec98fbb-ad1e96ee2e.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-simple-access-npm-7.25.7-3a9e5cd6e8-684d0b0330.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-simple-access-npm-7.25.7-3a9e5cd6e8-684d0b0330.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-split-export-declaration-npm-7.18.6-53ebf8ad4c-c6d3dede53.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-split-export-declaration-npm-7.18.6-53ebf8ad4c-c6d3dede53.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-string-parser-npm-7.19.4-0db110dc3a-b2f8a3920b.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-string-parser-npm-7.19.4-0db110dc3a-b2f8a3920b.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-string-parser-npm-7.24.1-0a40ece7f8-8404e865b0.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-string-parser-npm-7.24.1-0a40ece7f8-8404e865b0.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-string-parser-npm-7.25.7-352069de58-0835fda5ef.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-string-parser-npm-7.25.7-352069de58-0835fda5ef.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.16.7-8599fb00fc-dbb3db9d18.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.16.7-8599fb00fc-dbb3db9d18.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.19.1-d84f19e1dc-0eca5e86a7.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.19.1-d84f19e1dc-0eca5e86a7.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.24.5-d1938535fe-75d6f9f475.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.24.5-d1938535fe-75d6f9f475.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.25.7-1c758f0472-062f55208d.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-validator-identifier-npm-7.25.7-1c758f0472-062f55208d.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-validator-option-npm-7.18.6-cc7d1a3315-f9cc6eb7cc.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-validator-option-npm-7.18.6-cc7d1a3315-f9cc6eb7cc.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helper-validator-option-npm-7.25.7-8c969bf588-87b801fe7d.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helper-validator-option-npm-7.25.7-8c969bf588-87b801fe7d.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helpers-npm-7.20.1-3ab5411f3a-be35f78666.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helpers-npm-7.20.1-3ab5411f3a-be35f78666.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-helpers-npm-7.25.7-267b4cec46-a732428509.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-helpers-npm-7.25.7-267b4cec46-a732428509.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-highlight-npm-7.18.6-9d35ad2e27-92d8ee6154.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-highlight-npm-7.18.6-9d35ad2e27-92d8ee6154.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-highlight-npm-7.25.7-308b20da71-b6aa45c5bf.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-highlight-npm-7.25.7-308b20da71-b6aa45c5bf.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-parser-npm-7.20.3-dba5e30700-33bcdb45de.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-parser-npm-7.20.3-dba5e30700-33bcdb45de.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-parser-npm-7.24.5-a19c3b8c3b-a251ea41bf.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-parser-npm-7.24.5-a19c3b8c3b-a251ea41bf.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-parser-npm-7.25.7-3b9bdaa40d-7c40c2881e.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-parser-npm-7.25.7-3b9bdaa40d-7c40c2881e.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-syntax-jsx-npm-7.18.6-3e378d5f11-6d37ea9729.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-syntax-jsx-npm-7.18.6-3e378d5f11-6d37ea9729.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-transform-react-display-name-npm-7.18.6-dad446a24b-51c087ab9e.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-transform-react-display-name-npm-7.18.6-dad446a24b-51c087ab9e.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-development-npm-7.18.6-1b4b00c77d-ec9fa65db6.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-development-npm-7.18.6-1b4b00c77d-ec9fa65db6.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-npm-7.19.0-a26bc7e3f9-d7d6f0b8f2.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-npm-7.19.0-a26bc7e3f9-d7d6f0b8f2.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-self-npm-7.25.7-1a6e2e6ec0-bce354e287.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-self-npm-7.25.7-1a6e2e6ec0-bce354e287.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-source-npm-7.25.7-1f60cac636-1f87d8fa16.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-transform-react-jsx-source-npm-7.25.7-1f60cac636-1f87d8fa16.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-plugin-transform-react-pure-annotations-npm-7.18.6-ed47d931a2-97c4873d40.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-plugin-transform-react-pure-annotations-npm-7.18.6-ed47d931a2-97c4873d40.zip
vendored
Normal file
Binary file not shown.
BIN
web/.yarn/cache/@babel-preset-react-npm-7.18.6-d1cea43c32-540d9cf0a0.zip
vendored
Normal file
BIN
web/.yarn/cache/@babel-preset-react-npm-7.18.6-d1cea43c32-540d9cf0a0.zip
vendored
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user