Merge branch 'main' into pr/62
This commit is contained in:
commit
339f07373a
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,13 @@
|
||||
.aider*
|
||||
|
||||
# Project-related files
|
||||
|
||||
# Run logs
|
||||
backend/run_logs/*
|
||||
|
||||
.env
|
||||
# Weird Docker setup related files
|
||||
backend/backend/*
|
||||
|
||||
# Env vars
|
||||
frontend/.env.local
|
||||
.env
|
||||
|
||||
29
README.md
29
README.md
@ -1,6 +1,6 @@
|
||||
# screenshot-to-code
|
||||
|
||||
This simple app converts a screenshot to HTML/Tailwind CSS. It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images.
|
||||
This simple app converts a screenshot to code (HTML/Tailwind CSS, or React or Vue or Bootstrap). It uses GPT-4 Vision to generate the code and DALL-E 3 to generate similar-looking images. You can now also enter a URL to clone a live website!
|
||||
|
||||
https://github.com/abi/screenshot-to-code/assets/23818/6cebadae-2fe3-4986-ac6a-8fb9db030045
|
||||
|
||||
@ -8,15 +8,18 @@ See the [Examples](#examples) section below for more demos.
|
||||
|
||||
## 🚀 Try It Out!
|
||||
|
||||
🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions.
|
||||
🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section below for details**). Or see [Getting Started](#-getting-started) below for local install instructions.
|
||||
|
||||
## 🌟 Recent Updates
|
||||
|
||||
- Nov 28 - 🔥 🔥 🔥 Get output code in React or Bootstrap or TailwindCSS
|
||||
- Nov 23 - Send in a screenshot of the current replicated version (sometimes improves quality of subsequent generations)
|
||||
- Nov 21 - Edit code in the code editor and preview changes live thanks to [@clean99](https://github.com/clean99)
|
||||
- Nov 20 - Paste in a URL to screenshot and clone (requires [ScreenshotOne free API key](https://screenshotone.com?via=screenshot-to-code))
|
||||
- Nov 19 - Support for dark/light code editor theme - thanks https://github.com/kachbit
|
||||
- Nov 19 - Support for dark/light code editor theme - thanks [@kachbit](https://github.com/kachbit)
|
||||
- Nov 16 - Added a setting to disable DALL-E image generation if you don't need that
|
||||
- Nov 16 - View code directly within the app
|
||||
- Nov 15 - 🔥 You can now instruct the AI to update the code as you wish. It is helpful if the AI messed up some styles or missed a section.
|
||||
- Nov 15 - You can now instruct the AI to update the code as you wish. It is helpful if the AI messed up some styles or missed a section.
|
||||
|
||||
## 🛠 Getting Started
|
||||
|
||||
@ -44,6 +47,12 @@ Open http://localhost:5173 to use the app.
|
||||
|
||||
If you prefer to run the backend on a different port, update VITE_WS_BACKEND_URL in `frontend/.env.local`
|
||||
|
||||
For debugging purposes, if you don't want to waste GPT4-Vision credits, you can run the backend in mock mode (which streams a pre-recorded response):
|
||||
|
||||
```bash
|
||||
MOCK=true poetry run uvicorn main:app --reload --port 7001
|
||||
```
|
||||
|
||||
## Docker
|
||||
|
||||
If you have Docker installed on your system, in the root directory, run:
|
||||
@ -58,19 +67,17 @@ The app will be up and running at http://localhost:5173. Note that you can't dev
|
||||
## 🙋♂️ FAQs
|
||||
|
||||
- **I'm running into an error when setting up the backend. How can I fix it?** [Try this](https://github.com/abi/screenshot-to-code/issues/3#issuecomment-1814777959). If that still doesn't work, open an issue.
|
||||
- **How do I get an OpenAI API key that has the GPT4 Vision model available?** Create an OpenAI account. And then, you need to buy at least $1 worth of credit on the [Billing dashboard](https://platform.openai.com/account/billing/overview).
|
||||
- **How do I get an OpenAI API key?** See https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md
|
||||
- **How can I provide feedback?** For feedback, feature requests and bug reports, open an issue or ping me on [Twitter](https://twitter.com/_abi_).
|
||||
|
||||
## 📚 Examples
|
||||
|
||||
**NYTimes**
|
||||
|
||||
| Original | Replica |
|
||||
| -------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
||||
| Original | Replica |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| <img width="1238" alt="Screenshot 2023-11-20 at 12 54 03 PM" src="https://github.com/abi/screenshot-to-code/assets/23818/3b644dfa-9ca6-4148-84a7-3405b6671922"> | <img width="1414" alt="Screenshot 2023-11-20 at 12 59 56 PM" src="https://github.com/abi/screenshot-to-code/assets/23818/26201c9f-1a28-4f35-a3b1-1f04e2b8ce2a"> |
|
||||
|
||||
|
||||
|
||||
**Instagram page (with not Taylor Swift pics)**
|
||||
|
||||
https://github.com/abi/screenshot-to-code/assets/23818/503eb86a-356e-4dfc-926a-dabdb1ac7ba1
|
||||
@ -81,4 +88,6 @@ https://github.com/abi/screenshot-to-code/assets/23818/3fec0f77-44e8-4fb3-a769-a
|
||||
|
||||
## 🌍 Hosted Version
|
||||
|
||||
🆕 [Try it here](https://picoapps.xyz/free-tools/screenshot-to-code) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section for details**). Or see [Getting Started](#-getting-started) for local install instructions.
|
||||
🆕 [Try it here](https://screenshottocode.com) (bring your own OpenAI key - **your key must have access to GPT-4 Vision. See [FAQ](#%EF%B8%8F-faqs) section for details**). Or see [Getting Started](#-getting-started) for local install instructions.
|
||||
|
||||
[](https://www.buymeacoffee.com/abiraja)
|
||||
|
||||
17
Troubleshooting.md
Normal file
17
Troubleshooting.md
Normal file
@ -0,0 +1,17 @@
|
||||
### Getting an OpenAI API key with GPT4-Vision model access
|
||||
|
||||
You don't need a ChatGPT Pro account. Screenshot to code uses API keys from your OpenAI developer account. In order to get access to the GPT4 Vision model, log into your OpenAI account and then, follow these instructions:
|
||||
|
||||
1. Open [OpenAI Dashboard](https://platform.openai.com/)
|
||||
1. Go to Settings > Billing
|
||||
1. Click at the Add payment details
|
||||
<img width="1030" alt="285636868-c80deb92-ab47-45cd-988f-deee67fbd44d" src="https://github.com/abi/screenshot-to-code/assets/23818/4e0f4b77-9578-4f9a-803c-c12b1502f3d7">
|
||||
4. You have to buy some credits. The minimum is $5.
|
||||
|
||||
5. Go to Settings > Limits and check at the bottom of the page, your current tier has to be "Tier 1" to have GPT4 access
|
||||
<img width="785" alt="285636973-da38bd4d-8a78-4904-8027-ca67d729b933" src="https://github.com/abi/screenshot-to-code/assets/23818/8d07cd84-0cf9-4f88-bc00-80eba492eadf">
|
||||
6. Go to Screenshot to code and paste it in the Settings dialog under OpenAI key (gear icon). Your key is only stored in your browser. Never stored on our servers.
|
||||
|
||||
Some users have also reported that it can take upto 30 minutes after your credit purchase for the GPT4 vision model to be activated.
|
||||
|
||||
If you've followed these steps, and it still doesn't work, feel free to open a Github issue.
|
||||
3
backend/README.md
Normal file
3
backend/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
Run tests
|
||||
|
||||
pytest test_prompts.py
|
||||
27
backend/access_token.py
Normal file
27
backend/access_token.py
Normal file
@ -0,0 +1,27 @@
|
||||
import json
|
||||
import os
|
||||
import httpx
|
||||
|
||||
|
||||
async def validate_access_token(access_code: str):
|
||||
async with httpx.AsyncClient() as client:
|
||||
url = (
|
||||
"https://backend.buildpicoapps.com/screenshot_to_code/validate_access_token"
|
||||
)
|
||||
data = json.dumps(
|
||||
{
|
||||
"access_code": access_code,
|
||||
"secret": os.environ.get("PICO_BACKEND_SECRET"),
|
||||
}
|
||||
)
|
||||
headers = {"Content-Type": "application/json"}
|
||||
|
||||
response = await client.post(url, content=data, headers=headers)
|
||||
response_data = response.json()
|
||||
|
||||
if response_data["success"]:
|
||||
print("Access token is valid.")
|
||||
return True
|
||||
else:
|
||||
print(f"Access token validation failed: {response_data['failure_reason']}")
|
||||
return False
|
||||
@ -1,6 +1,5 @@
|
||||
# Load environment variables first
|
||||
from dotenv import load_dotenv
|
||||
from pydantic import BaseModel
|
||||
|
||||
load_dotenv()
|
||||
|
||||
@ -16,6 +15,7 @@ from mock import mock_completion
|
||||
from image_generation import create_alt_url_mapping, generate_images
|
||||
from prompts import assemble_prompt
|
||||
from routes import screenshot
|
||||
from access_token import validate_access_token
|
||||
|
||||
app = FastAPI(openapi_url=None, docs_url=None, redoc_url=None)
|
||||
|
||||
@ -33,7 +33,8 @@ app.add_middleware(
|
||||
|
||||
# Useful for debugging purposes when you don't want to waste GPT4-Vision credits
|
||||
# Setting to True will stream a mock response instead of calling the OpenAI API
|
||||
SHOULD_MOCK_AI_RESPONSE = False
|
||||
# TODO: Should only be set to true when value is 'True', not any abitrary truthy value
|
||||
SHOULD_MOCK_AI_RESPONSE = bool(os.environ.get("MOCK", False))
|
||||
|
||||
|
||||
app.include_router(screenshot.router)
|
||||
@ -59,27 +60,46 @@ def write_logs(prompt_messages, completion):
|
||||
|
||||
|
||||
@app.websocket("/generate-code")
|
||||
async def stream_code_test(websocket: WebSocket):
|
||||
async def stream_code(websocket: WebSocket):
|
||||
await websocket.accept()
|
||||
|
||||
print("Incoming websocket connection...")
|
||||
|
||||
params = await websocket.receive_json()
|
||||
|
||||
print("Received params")
|
||||
|
||||
# Read the output settings from the request. Fall back to default if not provided.
|
||||
output_settings = {"css": "tailwind", "js": "vanilla"}
|
||||
if params["outputSettings"] and params["outputSettings"]["css"]:
|
||||
output_settings["css"] = params["outputSettings"]["css"]
|
||||
if params["outputSettings"] and params["outputSettings"]["js"]:
|
||||
output_settings["js"] = params["outputSettings"]["js"]
|
||||
print("Using output settings:", output_settings)
|
||||
|
||||
# Get the OpenAI API key from the request. Fall back to environment variable if not provided.
|
||||
# If neither is provided, we throw an error.
|
||||
if params["openAiApiKey"]:
|
||||
openai_api_key = params["openAiApiKey"]
|
||||
print("Using OpenAI API key from client-side settings dialog")
|
||||
openai_api_key = None
|
||||
if "accessCode" in params and params["accessCode"]:
|
||||
print("Access code - using platform API key")
|
||||
if await validate_access_token(params["accessCode"]):
|
||||
openai_api_key = os.environ.get("PLATFORM_OPENAI_API_KEY")
|
||||
else:
|
||||
await websocket.send_json(
|
||||
{
|
||||
"type": "error",
|
||||
"value": "Invalid access code or you're out of credits. Please try again.",
|
||||
}
|
||||
)
|
||||
return
|
||||
else:
|
||||
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if openai_api_key:
|
||||
print("Using OpenAI API key from environment variable")
|
||||
if params["openAiBaseURL"]:
|
||||
openai_base_url = params["openAiBaseURL"]
|
||||
print("Using OpenAI Base URL from client-side settings dialog")
|
||||
else:
|
||||
openai_base_url = os.environ.get("OPENAI_BASE_URL")
|
||||
if openai_base_url:
|
||||
print("Using OpenAI Base URL from environment variable")
|
||||
if params["openAiApiKey"]:
|
||||
openai_api_key = params["openAiApiKey"]
|
||||
print("Using OpenAI API key from client-side settings dialog")
|
||||
else:
|
||||
openai_api_key = os.environ.get("OPENAI_API_KEY")
|
||||
if openai_api_key:
|
||||
print("Using OpenAI API key from environment variable")
|
||||
|
||||
if not openai_api_key:
|
||||
print("OpenAI API key not found")
|
||||
@ -90,12 +110,21 @@ async def stream_code_test(websocket: WebSocket):
|
||||
}
|
||||
)
|
||||
return
|
||||
# openai_base_url="https://flag.smarttrot.com/v1"
|
||||
|
||||
# Get the OpenAI Base URL from the request. Fall back to environment variable if not provided.
|
||||
openai_base_url = None
|
||||
if params["openAiBaseURL"]:
|
||||
openai_base_url = params["openAiBaseURL"]
|
||||
print("Using OpenAI Base URL from client-side settings dialog")
|
||||
else:
|
||||
openai_base_url = os.environ.get("OPENAI_BASE_URL")
|
||||
if openai_base_url:
|
||||
print("Using OpenAI Base URL from environment variable")
|
||||
|
||||
if not openai_base_url:
|
||||
openai_base_url = None
|
||||
print("Using Offical OpenAI Base URL")
|
||||
|
||||
|
||||
# Get the image generation flag from the request. Fall back to True if not provided.
|
||||
should_generate_images = (
|
||||
params["isImageGenerationEnabled"]
|
||||
if "isImageGenerationEnabled" in params
|
||||
@ -108,7 +137,12 @@ async def stream_code_test(websocket: WebSocket):
|
||||
async def process_chunk(content):
|
||||
await websocket.send_json({"type": "chunk", "value": content})
|
||||
|
||||
prompt_messages = assemble_prompt(params["image"])
|
||||
if params.get("resultImage") and params["resultImage"]:
|
||||
prompt_messages = assemble_prompt(
|
||||
params["image"], output_settings, params["resultImage"]
|
||||
)
|
||||
else:
|
||||
prompt_messages = assemble_prompt(params["image"], output_settings)
|
||||
|
||||
# Image cache for updates so that we don't have to regenerate images
|
||||
image_cache = {}
|
||||
@ -129,7 +163,7 @@ async def stream_code_test(websocket: WebSocket):
|
||||
completion = await stream_openai_response(
|
||||
prompt_messages,
|
||||
api_key=openai_api_key,
|
||||
base_url = openai_base_url,
|
||||
base_url=openai_base_url,
|
||||
callback=lambda x: process_chunk(x),
|
||||
)
|
||||
|
||||
@ -142,7 +176,10 @@ async def stream_code_test(websocket: WebSocket):
|
||||
{"type": "status", "value": "Generating images..."}
|
||||
)
|
||||
updated_html = await generate_images(
|
||||
completion, api_key=openai_api_key, base_url=openai_base_url, image_cache=image_cache
|
||||
completion,
|
||||
api_key=openai_api_key,
|
||||
base_url=openai_base_url,
|
||||
image_cache=image_cache,
|
||||
)
|
||||
else:
|
||||
updated_html = completion
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
SYSTEM_PROMPT = """
|
||||
TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Tailwind developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Tailwind, HTML and JS.
|
||||
You might also be given a screenshot of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image.
|
||||
You might also be given a screenshot(The second image) of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image(The first image).
|
||||
|
||||
- Make sure the app looks exactly like the screenshot.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
@ -23,25 +23,105 @@ Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
BOOTSTRAP_SYSTEM_PROMPT = """
|
||||
You are an expert Bootstrap developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Bootstrap, HTML and JS.
|
||||
You might also be given a screenshot(The second image) of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image(The first image).
|
||||
|
||||
- Make sure the app looks exactly like the screenshot.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" or bad things will happen.
|
||||
- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later.
|
||||
|
||||
In terms of libraries,
|
||||
|
||||
- Use this script to include Bootstrap: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
REACT_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert React/Tailwind developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using React and Tailwind CSS.
|
||||
You might also be given a screenshot(The second image) of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image(The first image).
|
||||
|
||||
- Make sure the app looks exactly like the screenshot.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" or bad things will happen.
|
||||
- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later.
|
||||
|
||||
In terms of libraries,
|
||||
|
||||
- Use these script to include React so that it can run on a standalone page:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
USER_PROMPT = """
|
||||
Generate code for a web page that looks exactly like this.
|
||||
"""
|
||||
|
||||
|
||||
def assemble_prompt(image_data_url):
|
||||
return [
|
||||
{"role": "system", "content": SYSTEM_PROMPT},
|
||||
def assemble_prompt(image_data_url, output_settings: dict, result_image_data_url=None):
|
||||
# Set the system prompt based on the output settings
|
||||
chosen_prompt_name = "tailwind"
|
||||
system_content = TAILWIND_SYSTEM_PROMPT
|
||||
if output_settings["css"] == "bootstrap":
|
||||
chosen_prompt_name = "bootstrap"
|
||||
system_content = BOOTSTRAP_SYSTEM_PROMPT
|
||||
if output_settings["js"] == "react":
|
||||
chosen_prompt_name = "react-tailwind"
|
||||
system_content = REACT_TAILWIND_SYSTEM_PROMPT
|
||||
|
||||
print("Using system prompt:", chosen_prompt_name)
|
||||
|
||||
user_content = [
|
||||
{
|
||||
"role": "user",
|
||||
"content": [
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": image_data_url, "detail": "high"},
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": USER_PROMPT,
|
||||
},
|
||||
],
|
||||
"type": "image_url",
|
||||
"image_url": {"url": image_data_url, "detail": "high"},
|
||||
},
|
||||
{
|
||||
"type": "text",
|
||||
"text": USER_PROMPT,
|
||||
},
|
||||
]
|
||||
|
||||
# Include the result image if it exists
|
||||
if result_image_data_url:
|
||||
user_content.insert(
|
||||
1,
|
||||
{
|
||||
"type": "image_url",
|
||||
"image_url": {"url": result_image_data_url, "detail": "high"},
|
||||
},
|
||||
)
|
||||
return [
|
||||
{
|
||||
"role": "system",
|
||||
"content": system_content,
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": user_content,
|
||||
},
|
||||
]
|
||||
|
||||
97
backend/test_prompts.py
Normal file
97
backend/test_prompts.py
Normal file
@ -0,0 +1,97 @@
|
||||
from prompts import assemble_prompt
|
||||
|
||||
TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert Tailwind developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Tailwind, HTML and JS.
|
||||
You might also be given a screenshot(The second image) of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image(The first image).
|
||||
|
||||
- Make sure the app looks exactly like the screenshot.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" or bad things will happen.
|
||||
- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later.
|
||||
|
||||
In terms of libraries,
|
||||
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
BOOTSTRAP_SYSTEM_PROMPT = """
|
||||
You are an expert Bootstrap developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using Bootstrap, HTML and JS.
|
||||
You might also be given a screenshot(The second image) of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image(The first image).
|
||||
|
||||
- Make sure the app looks exactly like the screenshot.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" or bad things will happen.
|
||||
- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later.
|
||||
|
||||
In terms of libraries,
|
||||
|
||||
- Use this script to include Bootstrap: <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
REACT_TAILWIND_SYSTEM_PROMPT = """
|
||||
You are an expert React/Tailwind developer
|
||||
You take screenshots of a reference web page from the user, and then build single page apps
|
||||
using React and Tailwind CSS.
|
||||
You might also be given a screenshot(The second image) of a web page that you have already built, and asked to
|
||||
update it to look more like the reference image(The first image).
|
||||
|
||||
- Make sure the app looks exactly like the screenshot.
|
||||
- Pay close attention to background color, text color, font size, font family,
|
||||
padding, margin, border, etc. Match the colors and sizes exactly.
|
||||
- Use the exact text from the screenshot.
|
||||
- Do not add comments in the code such as "<!-- Add other navigation links as needed -->" and "<!-- ... other news items ... -->" in place of writing the full code. WRITE THE FULL CODE.
|
||||
- Repeat elements as needed to match the screenshot. For example, if there are 15 items, the code should have 15 items. DO NOT LEAVE comments like "<!-- Repeat for each news item -->" or bad things will happen.
|
||||
- For images, use placeholder images from https://placehold.co and include a detailed description of the image in the alt text so that an image generation AI can generate the image later.
|
||||
|
||||
In terms of libraries,
|
||||
|
||||
- Use these script to include React so that it can run on a standalone page:
|
||||
<script src="https://unpkg.com/react/umd/react.development.js"></script>
|
||||
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
|
||||
<script src="https://unpkg.com/@babel/standalone/babel.js"></script>
|
||||
- Use this script to include Tailwind: <script src="https://cdn.tailwindcss.com"></script>
|
||||
- You can use Google Fonts
|
||||
- Font Awesome for icons: <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"></link>
|
||||
|
||||
Return only the full code in <html></html> tags.
|
||||
Do not include markdown "```" or "```html" at the start or end.
|
||||
"""
|
||||
|
||||
|
||||
def test_prompts():
|
||||
tailwind_prompt = assemble_prompt(
|
||||
"image_data_url", {"css": "tailwind", "js": "vanilla"}, "result_image_data_url"
|
||||
)
|
||||
assert tailwind_prompt[0]["content"] == TAILWIND_SYSTEM_PROMPT
|
||||
|
||||
bootstrap_prompt = assemble_prompt(
|
||||
"image_data_url", {"css": "bootstrap", "js": "vanilla"}, "result_image_data_url"
|
||||
)
|
||||
assert bootstrap_prompt[0]["content"] == BOOTSTRAP_SYSTEM_PROMPT
|
||||
|
||||
react_tailwind_prompt = assemble_prompt(
|
||||
"image_data_url", {"css": "tailwind", "js": "react"}, "result_image_data_url"
|
||||
)
|
||||
assert react_tailwind_prompt[0]["content"] == REACT_TAILWIND_SYSTEM_PROMPT
|
||||
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@ -22,3 +22,6 @@ dist-ssr
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
# Env files
|
||||
.env*
|
||||
|
||||
@ -17,7 +17,38 @@
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<!-- Injected code for hosted version -->
|
||||
<%- injectHead %>
|
||||
|
||||
<title>Screenshot to Code</title>
|
||||
|
||||
<!-- Open Graph Meta Tags -->
|
||||
<meta property="og:title" content="Screenshot to Code" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="Convert any screenshot or design to clean code"
|
||||
/>
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||
/>
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="628" />
|
||||
<meta property="og:url" content="https://screenshottocode.com" />
|
||||
<meta property="og:type" content="website" />
|
||||
<!-- Twitter Card tags -->
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta name="twitter:site" content="@picoapps" />
|
||||
<!-- Keep in sync with og:title, og:description and og:image -->
|
||||
<meta name="twitter:title" content="Screenshot to Code" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="Convert any screenshot or design to clean code"
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
content="https://screenshottocode.com/brand/twitter-summary-card.png"
|
||||
/>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
@ -5,15 +5,22 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev-hosted": "vite --mode prod",
|
||||
"build": "tsc && vite build",
|
||||
"build-hosted": "tsc && vite build --mode prod",
|
||||
"lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-html": "^6.4.6",
|
||||
"@radix-ui/react-accordion": "^1.1.2",
|
||||
"@radix-ui/react-alert-dialog": "^1.0.5",
|
||||
"@radix-ui/react-checkbox": "^1.0.4",
|
||||
"@radix-ui/react-dialog": "^1.0.5",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@radix-ui/react-label": "^2.0.2",
|
||||
"@radix-ui/react-popover": "^1.0.7",
|
||||
"@radix-ui/react-select": "^2.0.0",
|
||||
"@radix-ui/react-separator": "^1.0.3",
|
||||
"@radix-ui/react-slot": "^1.0.2",
|
||||
"@radix-ui/react-switch": "^1.0.3",
|
||||
@ -22,6 +29,8 @@
|
||||
"classnames": "^2.3.2",
|
||||
"clsx": "^2.0.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"html2canvas": "^1.4.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-dropzone": "^14.2.3",
|
||||
@ -46,7 +55,8 @@
|
||||
"postcss": "^8.4.31",
|
||||
"tailwindcss": "^3.3.5",
|
||||
"typescript": "^5.0.2",
|
||||
"vite": "^4.4.5"
|
||||
"vite": "^4.4.5",
|
||||
"vite-plugin-html": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0"
|
||||
|
||||
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
BIN
frontend/public/brand/twitter-summary-card.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 330 KiB |
@ -1,4 +1,4 @@
|
||||
import { useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
import ImageUpload from "./components/ImageUpload";
|
||||
import CodePreview from "./components/CodePreview";
|
||||
import Preview from "./components/Preview";
|
||||
@ -12,23 +12,33 @@ import {
|
||||
FaMobile,
|
||||
FaUndo,
|
||||
} from "react-icons/fa";
|
||||
|
||||
import { Switch } from "./components/ui/switch";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Textarea } from "@/components/ui/textarea";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "./components/ui/tabs";
|
||||
import CodeMirror from "./components/CodeMirror";
|
||||
import SettingsDialog from "./components/SettingsDialog";
|
||||
import { Settings } from "./types";
|
||||
import {
|
||||
Settings,
|
||||
EditorTheme,
|
||||
AppState,
|
||||
CSSOption,
|
||||
OutputSettings,
|
||||
JSFrameworkOption,
|
||||
} from "./types";
|
||||
import { IS_RUNNING_ON_CLOUD } from "./config";
|
||||
import { PicoBadge } from "./components/PicoBadge";
|
||||
import { OnboardingNote } from "./components/OnboardingNote";
|
||||
import { usePersistedState } from "./hooks/usePersistedState";
|
||||
import { UrlInputSection } from "./components/UrlInputSection";
|
||||
import TermsOfServiceDialog from "./components/TermsOfServiceDialog";
|
||||
import html2canvas from "html2canvas";
|
||||
import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants";
|
||||
import CodeTab from "./components/CodeTab";
|
||||
import OutputSettingsSection from "./components/OutputSettingsSection";
|
||||
|
||||
function App() {
|
||||
const [appState, setAppState] = useState<"INITIAL" | "CODING" | "CODE_READY">(
|
||||
"INITIAL"
|
||||
);
|
||||
const [appState, setAppState] = useState<AppState>(AppState.INITIAL);
|
||||
const [generatedCode, setGeneratedCode] = useState<string>("");
|
||||
const [referenceImages, setReferenceImages] = useState<string[]>([]);
|
||||
const [executionConsole, setExecutionConsole] = useState<string[]>([]);
|
||||
@ -40,10 +50,33 @@ function App() {
|
||||
openAiBaseURL: null,
|
||||
screenshotOneApiKey: null,
|
||||
isImageGenerationEnabled: true,
|
||||
editorTheme: "cobalt",
|
||||
editorTheme: EditorTheme.COBALT,
|
||||
isTermOfServiceAccepted: false,
|
||||
accessCode: null,
|
||||
},
|
||||
"setting"
|
||||
);
|
||||
const [outputSettings, setOutputSettings] = useState<OutputSettings>({
|
||||
css: CSSOption.TAILWIND,
|
||||
js: JSFrameworkOption.NO_FRAMEWORK,
|
||||
});
|
||||
const [shouldIncludeResultImage, setShouldIncludeResultImage] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const wsRef = useRef<WebSocket>(null);
|
||||
|
||||
const takeScreenshot = async (): Promise<string> => {
|
||||
const iframeElement = document.querySelector(
|
||||
"#preview-desktop"
|
||||
) as HTMLIFrameElement;
|
||||
if (!iframeElement?.contentWindow?.document.body) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const canvas = await html2canvas(iframeElement.contentWindow.document.body);
|
||||
const png = canvas.toDataURL("image/png");
|
||||
return png;
|
||||
};
|
||||
|
||||
const downloadCode = () => {
|
||||
// Create a blob from the generated code
|
||||
@ -63,31 +96,41 @@ function App() {
|
||||
};
|
||||
|
||||
const reset = () => {
|
||||
setAppState("INITIAL");
|
||||
setAppState(AppState.INITIAL);
|
||||
setGeneratedCode("");
|
||||
setReferenceImages([]);
|
||||
setExecutionConsole([]);
|
||||
setHistory([]);
|
||||
};
|
||||
|
||||
const stop = () => {
|
||||
wsRef.current?.close?.(USER_CLOSE_WEB_SOCKET_CODE);
|
||||
// make sure stop can correct the state even if the websocket is already closed
|
||||
setAppState(AppState.CODE_READY);
|
||||
};
|
||||
|
||||
function doGenerateCode(params: CodeGenerationParams) {
|
||||
setExecutionConsole([]);
|
||||
setAppState("CODING");
|
||||
setAppState(AppState.CODING);
|
||||
|
||||
// Merge settings with params
|
||||
const updatedParams = { ...params, ...settings };
|
||||
const updatedParams = { ...params, ...settings, outputSettings };
|
||||
|
||||
generateCode(
|
||||
wsRef,
|
||||
updatedParams,
|
||||
(token) => setGeneratedCode((prev) => prev + token),
|
||||
(code) => setGeneratedCode(code),
|
||||
(line) => setExecutionConsole((prev) => [...prev, line]),
|
||||
() => setAppState("CODE_READY")
|
||||
() => setAppState(AppState.CODE_READY)
|
||||
);
|
||||
}
|
||||
|
||||
// Initial version creation
|
||||
function doCreate(referenceImages: string[]) {
|
||||
// Reset any existing state
|
||||
reset();
|
||||
|
||||
setReferenceImages(referenceImages);
|
||||
if (referenceImages.length > 0) {
|
||||
doGenerateCode({
|
||||
@ -98,53 +141,88 @@ function App() {
|
||||
}
|
||||
|
||||
// Subsequent updates
|
||||
function doUpdate() {
|
||||
async function doUpdate() {
|
||||
const updatedHistory = [...history, generatedCode, updateInstruction];
|
||||
|
||||
doGenerateCode({
|
||||
generationType: "update",
|
||||
image: referenceImages[0],
|
||||
history: updatedHistory,
|
||||
});
|
||||
if (shouldIncludeResultImage) {
|
||||
const resultImage = await takeScreenshot();
|
||||
doGenerateCode({
|
||||
generationType: "update",
|
||||
image: referenceImages[0],
|
||||
resultImage: resultImage,
|
||||
history: updatedHistory,
|
||||
});
|
||||
} else {
|
||||
doGenerateCode({
|
||||
generationType: "update",
|
||||
image: referenceImages[0],
|
||||
history: updatedHistory,
|
||||
});
|
||||
}
|
||||
|
||||
setHistory(updatedHistory);
|
||||
setGeneratedCode("");
|
||||
setUpdateInstruction("");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="mt-2">
|
||||
{IS_RUNNING_ON_CLOUD && <PicoBadge />}
|
||||
{IS_RUNNING_ON_CLOUD && <TermsOfServiceDialog />}
|
||||
const handleTermDialogOpenChange = (open: boolean) => {
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
isTermOfServiceAccepted: !open,
|
||||
}));
|
||||
};
|
||||
|
||||
<div className="hidden lg:fixed lg:inset-y-0 lg:z-50 lg:flex lg:w-96 lg:flex-col">
|
||||
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6">
|
||||
<div className="flex items-center justify-between mt-10">
|
||||
return (
|
||||
<div className="mt-2 dark:bg-black dark:text-white">
|
||||
{IS_RUNNING_ON_CLOUD && <PicoBadge settings={settings} />}
|
||||
{IS_RUNNING_ON_CLOUD && (
|
||||
<TermsOfServiceDialog
|
||||
open={!settings.isTermOfServiceAccepted}
|
||||
onOpenChange={handleTermDialogOpenChange}
|
||||
/>
|
||||
)}
|
||||
<div className="lg:fixed lg:inset-y-0 lg:z-40 lg:flex lg:w-96 lg:flex-col">
|
||||
<div className="flex grow flex-col gap-y-2 overflow-y-auto border-r border-gray-200 bg-white px-6 dark:bg-zinc-950 dark:text-white">
|
||||
<div className="flex items-center justify-between mt-10 mb-2">
|
||||
<h1 className="text-2xl ">Screenshot to Code</h1>
|
||||
<SettingsDialog settings={settings} setSettings={setSettings} />
|
||||
</div>
|
||||
{appState === "INITIAL" && (
|
||||
<h2 className="text-sm text-gray-500 mb-2">
|
||||
Drag & drop a screenshot to get started.
|
||||
</h2>
|
||||
)}
|
||||
|
||||
{IS_RUNNING_ON_CLOUD && !settings.openAiApiKey && <OnboardingNote />}
|
||||
<OutputSettingsSection
|
||||
outputSettings={outputSettings}
|
||||
setOutputSettings={setOutputSettings}
|
||||
shouldDisableUpdates={
|
||||
appState === AppState.CODING || appState === AppState.CODE_READY
|
||||
}
|
||||
/>
|
||||
|
||||
{(appState === "CODING" || appState === "CODE_READY") && (
|
||||
{IS_RUNNING_ON_CLOUD &&
|
||||
!(settings.openAiApiKey || settings.accessCode) && (
|
||||
<OnboardingNote />
|
||||
)}
|
||||
|
||||
{(appState === AppState.CODING ||
|
||||
appState === AppState.CODE_READY) && (
|
||||
<>
|
||||
{/* Show code preview only when coding */}
|
||||
{appState === "CODING" && (
|
||||
{appState === AppState.CODING && (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex items-center gap-x-1">
|
||||
<Spinner />
|
||||
{executionConsole.slice(-1)[0]}
|
||||
</div>
|
||||
<div className="flex mt-4 w-full">
|
||||
<Button
|
||||
onClick={stop}
|
||||
className="w-full dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
Stop
|
||||
</Button>
|
||||
</div>
|
||||
<CodePreview code={generatedCode} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{appState === "CODE_READY" && (
|
||||
{appState === AppState.CODE_READY && (
|
||||
<div>
|
||||
<div className="grid w-full gap-2">
|
||||
<Textarea
|
||||
@ -152,18 +230,33 @@ function App() {
|
||||
onChange={(e) => setUpdateInstruction(e.target.value)}
|
||||
value={updateInstruction}
|
||||
/>
|
||||
<Button onClick={doUpdate}>Update</Button>
|
||||
<div className="flex justify-between items-center gap-x-2">
|
||||
<div className="font-500 text-xs text-slate-700 dark:text-white">
|
||||
Include screenshot of current version?
|
||||
</div>
|
||||
<Switch
|
||||
checked={shouldIncludeResultImage}
|
||||
onCheckedChange={setShouldIncludeResultImage}
|
||||
className="dark:bg-gray-700"
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={doUpdate}
|
||||
className="dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
Update
|
||||
</Button>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2 mt-2">
|
||||
<Button
|
||||
onClick={downloadCode}
|
||||
className="flex items-center gap-x-2"
|
||||
className="flex items-center gap-x-2 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
<FaDownload /> Download
|
||||
</Button>
|
||||
<Button
|
||||
onClick={reset}
|
||||
className="flex items-center gap-x-2"
|
||||
className="flex items-center gap-x-2 dark:text-white dark:bg-gray-700"
|
||||
>
|
||||
<FaUndo />
|
||||
Reset
|
||||
@ -177,7 +270,7 @@ function App() {
|
||||
<div className="flex flex-col">
|
||||
<div
|
||||
className={classNames({
|
||||
"scanning relative": appState === "CODING",
|
||||
"scanning relative": appState === AppState.CODING,
|
||||
})}
|
||||
>
|
||||
<img
|
||||
@ -210,7 +303,7 @@ function App() {
|
||||
</div>
|
||||
|
||||
<main className="py-2 lg:pl-96">
|
||||
{appState === "INITIAL" && (
|
||||
{appState === AppState.INITIAL && (
|
||||
<div className="flex flex-col justify-center items-center gap-y-10">
|
||||
<ImageUpload setReferenceImages={doCreate} />
|
||||
<UrlInputSection
|
||||
@ -220,7 +313,7 @@ function App() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(appState === "CODING" || appState === "CODE_READY") && (
|
||||
{(appState === AppState.CODING || appState === AppState.CODE_READY) && (
|
||||
<div className="ml-4">
|
||||
<Tabs defaultValue="desktop">
|
||||
<div className="flex justify-end mr-8 mb-4">
|
||||
@ -244,9 +337,10 @@ function App() {
|
||||
<Preview code={generatedCode} device="mobile" />
|
||||
</TabsContent>
|
||||
<TabsContent value="code">
|
||||
<CodeMirror
|
||||
<CodeTab
|
||||
code={generatedCode}
|
||||
editorTheme={settings.editorTheme}
|
||||
setCode={setGeneratedCode}
|
||||
settings={settings}
|
||||
/>
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useRef, useEffect } from "react";
|
||||
import { useRef, useEffect, useMemo } from "react";
|
||||
import { EditorState } from "@codemirror/state";
|
||||
import { EditorView, keymap, lineNumbers } from "@codemirror/view";
|
||||
import { EditorView, keymap, lineNumbers, ViewUpdate } from "@codemirror/view";
|
||||
import { espresso, cobalt } from "thememirror";
|
||||
import {
|
||||
defaultKeymap,
|
||||
@ -11,24 +11,20 @@ import {
|
||||
} from "@codemirror/commands";
|
||||
import { bracketMatching } from "@codemirror/language";
|
||||
import { html } from "@codemirror/lang-html";
|
||||
import { EditorTheme } from "@/types";
|
||||
|
||||
interface Props {
|
||||
code: string;
|
||||
editorTheme: string;
|
||||
editorTheme: EditorTheme;
|
||||
onCodeChange: (code: string) => void;
|
||||
}
|
||||
|
||||
function CodeMirror({ code, editorTheme }: Props) {
|
||||
function CodeMirror({ code, editorTheme, onCodeChange }: Props) {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const view = useRef<EditorView | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
let selectedTheme = cobalt;
|
||||
if (editorTheme === "espresso") {
|
||||
selectedTheme = espresso;
|
||||
}
|
||||
view.current = new EditorView({
|
||||
state: EditorState.create({
|
||||
doc: code,
|
||||
const editorState = useMemo(
|
||||
() =>
|
||||
EditorState.create({
|
||||
extensions: [
|
||||
history(),
|
||||
keymap.of([
|
||||
@ -40,10 +36,21 @@ function CodeMirror({ code, editorTheme }: Props) {
|
||||
lineNumbers(),
|
||||
bracketMatching(),
|
||||
html(),
|
||||
selectedTheme,
|
||||
editorTheme === EditorTheme.ESPRESSO ? espresso : cobalt,
|
||||
EditorView.lineWrapping,
|
||||
EditorView.updateListener.of((update: ViewUpdate) => {
|
||||
if (update.docChanged) {
|
||||
const updatedCode = update.state.doc.toString();
|
||||
onCodeChange(updatedCode);
|
||||
}
|
||||
}),
|
||||
],
|
||||
}),
|
||||
[editorTheme]
|
||||
);
|
||||
useEffect(() => {
|
||||
view.current = new EditorView({
|
||||
state: editorState,
|
||||
parent: ref.current as Element,
|
||||
});
|
||||
|
||||
@ -53,7 +60,7 @@ function CodeMirror({ code, editorTheme }: Props) {
|
||||
view.current = null;
|
||||
}
|
||||
};
|
||||
}, [code, editorTheme]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (view.current && view.current.state.doc.toString() !== code) {
|
||||
@ -64,7 +71,10 @@ function CodeMirror({ code, editorTheme }: Props) {
|
||||
}, [code]);
|
||||
|
||||
return (
|
||||
<div className="overflow-x-scroll overflow-y-scroll mx-2 border-[4px] border-black rounded-[20px]" ref={ref} />
|
||||
<div
|
||||
className="overflow-x-scroll overflow-y-scroll mx-2 border-[4px] border-black rounded-[20px]"
|
||||
ref={ref}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
87
frontend/src/components/CodeTab.tsx
Normal file
87
frontend/src/components/CodeTab.tsx
Normal file
@ -0,0 +1,87 @@
|
||||
import { FaCopy } from "react-icons/fa";
|
||||
import CodeMirror from "./CodeMirror";
|
||||
import { Button } from "./ui/button";
|
||||
import { Settings } from "../types";
|
||||
import copy from "copy-to-clipboard";
|
||||
import { useCallback } from "react";
|
||||
import toast from "react-hot-toast";
|
||||
|
||||
interface Props {
|
||||
code: string;
|
||||
setCode: React.Dispatch<React.SetStateAction<string>>;
|
||||
settings: Settings;
|
||||
}
|
||||
|
||||
function CodeTab({ code, setCode, settings }: Props) {
|
||||
const copyCode = useCallback(() => {
|
||||
copy(code);
|
||||
toast.success("Copied to clipboard");
|
||||
}, [code]);
|
||||
|
||||
const doOpenInCodepenio = useCallback(async () => {
|
||||
// TODO: Update CSS and JS external links depending on the framework being used
|
||||
const data = {
|
||||
html: code,
|
||||
editors: "100", // 1: Open HTML, 0: Close CSS, 0: Close JS
|
||||
layout: "left",
|
||||
css_external:
|
||||
"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" +
|
||||
(code.includes("<ion-")
|
||||
? ",https://cdn.jsdelivr.net/npm/@ionic/core/css/ionic.bundle.css"
|
||||
: ""),
|
||||
js_external:
|
||||
"https://cdn.tailwindcss.com " +
|
||||
(code.includes("<ion-")
|
||||
? ",https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.esm.js,https://cdn.jsdelivr.net/npm/@ionic/core/dist/ionic/ionic.js"
|
||||
: ""),
|
||||
};
|
||||
|
||||
// Create a hidden form and submit it to open the code in CodePen
|
||||
// Can't use fetch API directly because we want to open the URL in a new tab
|
||||
const input = document.createElement("input");
|
||||
input.setAttribute("type", "hidden");
|
||||
input.setAttribute("name", "data");
|
||||
input.setAttribute("value", JSON.stringify(data));
|
||||
|
||||
const form = document.createElement("form");
|
||||
form.setAttribute("method", "POST");
|
||||
form.setAttribute("action", "https://codepen.io/pen/define");
|
||||
form.setAttribute("target", "_blank");
|
||||
form.appendChild(input);
|
||||
|
||||
document.body.appendChild(form);
|
||||
form.submit();
|
||||
}, [code]);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="flex justify-start items-center px-4 mb-2">
|
||||
<span
|
||||
title="Copy Code"
|
||||
className="bg-black text-white flex items-center justify-center hover:text-black hover:bg-gray-100 cursor-pointer rounded-lg text-sm p-2.5"
|
||||
onClick={copyCode}
|
||||
>
|
||||
Copy Code <FaCopy className="ml-2" />
|
||||
</span>
|
||||
<Button
|
||||
onClick={doOpenInCodepenio}
|
||||
className="bg-gray-100 text-black ml-2 py-2 px-4 border border-black rounded-md hover:bg-gray-400 focus:outline-none"
|
||||
>
|
||||
Open in{" "}
|
||||
<img
|
||||
src="https://assets.codepen.io/t-1/codepen-logo.svg"
|
||||
alt="codepen.io"
|
||||
className="h-4 ml-1"
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
<CodeMirror
|
||||
code={code}
|
||||
editorTheme={settings.editorTheme}
|
||||
onCodeChange={setCode}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default CodeTab;
|
||||
@ -106,7 +106,9 @@ function ImageUpload({ setReferenceImages }: Props) {
|
||||
// Convert images to data URLs and set the prompt images state
|
||||
Promise.all(files.map((file) => fileToDataURL(file)))
|
||||
.then((dataUrls) => {
|
||||
setReferenceImages(dataUrls.map((dataUrl) => dataUrl as string));
|
||||
if (dataUrls.length > 0) {
|
||||
setReferenceImages(dataUrls.map((dataUrl) => dataUrl as string));
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
// TODO: Display error to user
|
||||
@ -140,7 +142,11 @@ function ImageUpload({ setReferenceImages }: Props) {
|
||||
{/* eslint-disable-next-line @typescript-eslint/no-explicit-any */}
|
||||
<div {...getRootProps({ style: style as any })}>
|
||||
<input {...getInputProps()} />
|
||||
<p>Drop a screenshot here, paste from clipboard, or click to select</p>
|
||||
<p className="text-slate-700 text-lg">
|
||||
Drag & drop a screenshot here, <br />
|
||||
or paste from clipboard, <br />
|
||||
or click to upload
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@ -1,20 +1,25 @@
|
||||
export function OnboardingNote() {
|
||||
return (
|
||||
<div className="flex flex-col space-y-4 bg-slate-500 p-2 rounded text-stone-200">
|
||||
Please add your OpenAI API key (must have GPT4 vision access) in the
|
||||
settings dialog (gear icon above).
|
||||
<br />
|
||||
<br />
|
||||
How do you get an OpenAI API key that has the GPT4 Vision model available?
|
||||
Create an OpenAI account. And then, you need to buy at least $1 worth of
|
||||
credit on the Billing dashboard.
|
||||
<br />
|
||||
<div className="flex flex-col space-y-4 bg-green-700 p-2 rounded text-stone-200 text-sm">
|
||||
<span>
|
||||
This key is only stored in your browser. Never stored on servers. This
|
||||
app is open source. You can{" "}
|
||||
<a href="https://github.com/abi/screenshot-to-code" className="inline">
|
||||
check the code to confirm.
|
||||
</a>
|
||||
To use Screenshot to Code,{" "}
|
||||
<a
|
||||
className="inline underline hover:opacity-70"
|
||||
href="https://buy.stripe.com/8wM6sre70gBW1nqaEE"
|
||||
target="_blank"
|
||||
>
|
||||
buy some credits (100 generations for $36)
|
||||
</a>{" "}
|
||||
or use your own OpenAI API key with GPT4 vision access.{" "}
|
||||
<a
|
||||
href="https://github.com/abi/screenshot-to-code/blob/main/Troubleshooting.md"
|
||||
className="inline underline hover:opacity-70"
|
||||
target="_blank"
|
||||
>
|
||||
Follow these instructions to get yourself a key.
|
||||
</a>{" "}
|
||||
and paste it in the Settings dialog (gear icon above). Your key is only
|
||||
stored in your browser. Never stored on our servers.
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
201
frontend/src/components/OutputSettingsSection.tsx
Normal file
201
frontend/src/components/OutputSettingsSection.tsx
Normal file
@ -0,0 +1,201 @@
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectGroup,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
} from "./ui/select";
|
||||
import { CSSOption, JSFrameworkOption, OutputSettings } from "../types";
|
||||
import toast from "react-hot-toast";
|
||||
import { Label } from "@radix-ui/react-label";
|
||||
import { Button } from "./ui/button";
|
||||
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover";
|
||||
|
||||
function displayCSSOption(option: CSSOption) {
|
||||
switch (option) {
|
||||
case CSSOption.TAILWIND:
|
||||
return "Tailwind";
|
||||
case CSSOption.BOOTSTRAP:
|
||||
return "Bootstrap";
|
||||
default:
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
function displayJSOption(option: JSFrameworkOption) {
|
||||
switch (option) {
|
||||
case JSFrameworkOption.REACT:
|
||||
return "React";
|
||||
case JSFrameworkOption.NO_FRAMEWORK:
|
||||
return "No Framework";
|
||||
default:
|
||||
return option;
|
||||
}
|
||||
}
|
||||
|
||||
function convertStringToCSSOption(option: string) {
|
||||
switch (option) {
|
||||
case "tailwind":
|
||||
return CSSOption.TAILWIND;
|
||||
case "bootstrap":
|
||||
return CSSOption.BOOTSTRAP;
|
||||
default:
|
||||
throw new Error(`Unknown CSS option: ${option}`);
|
||||
}
|
||||
}
|
||||
|
||||
function generateDisplayString(settings: OutputSettings) {
|
||||
if (
|
||||
settings.js === JSFrameworkOption.REACT &&
|
||||
settings.css === CSSOption.TAILWIND
|
||||
) {
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-white">
|
||||
Generating <span className="font-bold">React</span> +{" "}
|
||||
<span className="font-bold">Tailwind</span> code
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||
settings.css === CSSOption.TAILWIND
|
||||
) {
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-white">
|
||||
Generating <span className="font-bold">HTML</span> +{" "}
|
||||
<span className="font-bold">Tailwind</span> code
|
||||
</div>
|
||||
);
|
||||
} else if (
|
||||
settings.js === JSFrameworkOption.NO_FRAMEWORK &&
|
||||
settings.css === CSSOption.BOOTSTRAP
|
||||
) {
|
||||
return (
|
||||
<div className="text-gray-800 dark:text-white">
|
||||
Generating <span className="font-bold">HTML</span> +{" "}
|
||||
<span className="font-bold">Bootstrap</span> code
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface Props {
|
||||
outputSettings: OutputSettings;
|
||||
setOutputSettings: React.Dispatch<React.SetStateAction<OutputSettings>>;
|
||||
shouldDisableUpdates?: boolean;
|
||||
}
|
||||
|
||||
function OutputSettingsSection({
|
||||
outputSettings,
|
||||
setOutputSettings,
|
||||
shouldDisableUpdates = false,
|
||||
}: Props) {
|
||||
const onCSSValueChange = (value: string) => {
|
||||
setOutputSettings((prev) => {
|
||||
if (prev.js === JSFrameworkOption.REACT) {
|
||||
if (value !== CSSOption.TAILWIND) {
|
||||
toast.error(
|
||||
'React only supports Tailwind CSS. Change JS framework to "No Framework" to use Bootstrap.'
|
||||
);
|
||||
}
|
||||
return {
|
||||
css: CSSOption.TAILWIND,
|
||||
js: JSFrameworkOption.REACT,
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
...prev,
|
||||
css: convertStringToCSSOption(value),
|
||||
};
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const onJsFrameworkChange = (value: string) => {
|
||||
if (value === JSFrameworkOption.REACT) {
|
||||
setOutputSettings(() => ({
|
||||
css: CSSOption.TAILWIND,
|
||||
js: value as JSFrameworkOption,
|
||||
}));
|
||||
} else {
|
||||
setOutputSettings((prev) => ({
|
||||
...prev,
|
||||
js: value as JSFrameworkOption,
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-y-2 justify-between text-sm">
|
||||
{generateDisplayString(outputSettings)}{" "}
|
||||
{!shouldDisableUpdates && (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>
|
||||
<Button variant="outline">Customize</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-80 text-sm">
|
||||
<div className="grid gap-4">
|
||||
<div className="space-y-2">
|
||||
<h4 className="font-medium leading-none">Code Settings</h4>
|
||||
<p className="text-muted-foreground">
|
||||
Customize your code output
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid gap-2">
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="output-settings-js">JS</Label>
|
||||
<Select
|
||||
value={outputSettings.js}
|
||||
onValueChange={onJsFrameworkChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="col-span-2 h-8"
|
||||
id="output-settings-js"
|
||||
>
|
||||
{displayJSOption(outputSettings.js)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={JSFrameworkOption.NO_FRAMEWORK}>
|
||||
No Framework
|
||||
</SelectItem>
|
||||
<SelectItem value={JSFrameworkOption.REACT}>
|
||||
React
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 items-center gap-4">
|
||||
<Label htmlFor="output-settings-css">CSS</Label>
|
||||
<Select
|
||||
value={outputSettings.css}
|
||||
onValueChange={onCSSValueChange}
|
||||
>
|
||||
<SelectTrigger
|
||||
className="col-span-2 h-8"
|
||||
id="output-settings-css"
|
||||
>
|
||||
{displayCSSOption(outputSettings.css)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectGroup>
|
||||
<SelectItem value={CSSOption.TAILWIND}>
|
||||
Tailwind
|
||||
</SelectItem>
|
||||
<SelectItem value={CSSOption.BOOTSTRAP}>
|
||||
Bootstrap
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default OutputSettingsSection;
|
||||
@ -1,12 +1,39 @@
|
||||
export function PicoBadge() {
|
||||
import { Settings } from "../types";
|
||||
|
||||
export function PicoBadge({ settings }: { settings: Settings }) {
|
||||
return (
|
||||
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
||||
<div
|
||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow bg-black
|
||||
text-white px-4 text-xs py-3 cursor-pointer"
|
||||
<>
|
||||
<a
|
||||
href="https://screenshot-to-code.canny.io/feature-requests"
|
||||
target="_blank"
|
||||
>
|
||||
an open source project by Pico
|
||||
</div>
|
||||
</a>
|
||||
<div
|
||||
className="fixed z-50 bottom-16 right-5 rounded-md shadow bg-black
|
||||
text-white px-4 text-xs py-3 cursor-pointer"
|
||||
>
|
||||
feature requests?
|
||||
</div>
|
||||
</a>
|
||||
{!settings.accessCode && (
|
||||
<a href="https://picoapps.xyz?ref=screenshot-to-code" target="_blank">
|
||||
<div
|
||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||
bg-white px-4 text-xs py-3 cursor-pointer"
|
||||
>
|
||||
an open source project by Pico
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
{settings.accessCode && (
|
||||
<a href="mailto:support@picoapps.xyz" target="_blank">
|
||||
<div
|
||||
className="fixed z-50 bottom-5 right-5 rounded-md shadow text-black
|
||||
bg-white px-4 text-xs py-3 cursor-pointer"
|
||||
>
|
||||
email support
|
||||
</div>
|
||||
</a>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import classNames from "classnames";
|
||||
import useThrottle from "../hooks/useThrottle";
|
||||
|
||||
@ -8,12 +9,23 @@ interface Props {
|
||||
|
||||
function Preview({ code, device }: Props) {
|
||||
const throttledCode = useThrottle(code, 200);
|
||||
const iframeRef = useRef<HTMLIFrameElement | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const iframe = iframeRef.current;
|
||||
if (iframe && iframe.contentDocument) {
|
||||
iframe.contentDocument.open();
|
||||
iframe.contentDocument.write(throttledCode);
|
||||
iframe.contentDocument.close();
|
||||
}
|
||||
}, [throttledCode]);
|
||||
|
||||
return (
|
||||
<div className="flex justify-center mx-2">
|
||||
<iframe
|
||||
id={`preview-${device}`}
|
||||
ref={iframeRef}
|
||||
title="Preview"
|
||||
srcDoc={throttledCode}
|
||||
className={classNames(
|
||||
"border-[4px] border-black rounded-[20px] shadow-lg",
|
||||
"transform scale-[0.9] origin-top",
|
||||
@ -26,4 +38,5 @@ function Preview({ code, device }: Props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Preview;
|
||||
|
||||
export default Preview;
|
||||
@ -9,11 +9,19 @@ import {
|
||||
DialogTrigger,
|
||||
} from "@/components/ui/dialog";
|
||||
import { FaCog } from "react-icons/fa";
|
||||
import { Settings } from "../types";
|
||||
import { EditorTheme, Settings } from "../types";
|
||||
import { Switch } from "./ui/switch";
|
||||
import { Label } from "./ui/label";
|
||||
import { Input } from "./ui/input";
|
||||
import { Select } from "./ui/select";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger } from "./ui/select";
|
||||
import { capitalize } from "../lib/utils";
|
||||
import { IS_RUNNING_ON_CLOUD } from "../config";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionContent,
|
||||
AccordionItem,
|
||||
AccordionTrigger,
|
||||
} from "./ui/accordion";
|
||||
|
||||
interface Props {
|
||||
settings: Settings;
|
||||
@ -21,7 +29,7 @@ interface Props {
|
||||
}
|
||||
|
||||
function SettingsDialog({ settings, setSettings }: Props) {
|
||||
const handleThemeChange = (theme: string) => {
|
||||
const handleThemeChange = (theme: EditorTheme) => {
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
editorTheme: theme,
|
||||
@ -37,6 +45,31 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
||||
<DialogHeader>
|
||||
<DialogTitle className="mb-4">Settings</DialogTitle>
|
||||
</DialogHeader>
|
||||
|
||||
{/* Access code */}
|
||||
{IS_RUNNING_ON_CLOUD && (
|
||||
<div className="flex flex-col space-y-4 bg-slate-300 p-4 rounded dark:text-white dark:bg-slate-800">
|
||||
<Label htmlFor="access-code">
|
||||
<div>Access Code</div>
|
||||
<div className="font-light mt-1 leading-relaxed">
|
||||
Buy an access code.
|
||||
</div>
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
id="access-code dark:border-gray-700 dark:bg-gray-800 dark:text-white"
|
||||
placeholder="Enter your Screenshot to Code access code"
|
||||
value={settings.accessCode || ""}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
accessCode: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Label htmlFor="image-generation">
|
||||
<div>DALL-E Placeholder Image Generation</div>
|
||||
@ -95,47 +128,90 @@ function SettingsDialog({ settings, setSettings }: Props) {
|
||||
}
|
||||
/>
|
||||
|
||||
<Label htmlFor="screenshot-one-api-key">
|
||||
<div>ScreenshotOne API key</div>
|
||||
<div className="font-light mt-2 leading-relaxed">
|
||||
Only stored in your browser. Never stored on servers.{" "}
|
||||
<a
|
||||
href="https://screenshotone.com?via=screenshot-to-code"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
Get 100 screenshots/mo for free.
|
||||
</a>
|
||||
</div>
|
||||
</Label>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Screenshot by URL Config</AccordionTrigger>
|
||||
<AccordionContent>
|
||||
<Label htmlFor="screenshot-one-api-key">
|
||||
<div className="leading-normal font-normal text-xs">
|
||||
If you want to use URLs directly instead of taking the
|
||||
screenshot yourself, add a ScreenshotOne API key.{" "}
|
||||
<a
|
||||
href="https://screenshotone.com?via=screenshot-to-code"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
Get 100 screenshots/mo for free.
|
||||
</a>
|
||||
</div>
|
||||
</Label>
|
||||
|
||||
<Input
|
||||
id="screenshot-one-api-key"
|
||||
placeholder="ScreenshotOne API key"
|
||||
value={settings.screenshotOneApiKey || ""}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
screenshotOneApiKey: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
id="screenshot-one-api-key"
|
||||
className="mt-2"
|
||||
placeholder="ScreenshotOne API key"
|
||||
value={settings.screenshotOneApiKey || ""}
|
||||
onChange={(e) =>
|
||||
setSettings((s) => ({
|
||||
...s,
|
||||
screenshotOneApiKey: e.target.value,
|
||||
}))
|
||||
}
|
||||
/>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
||||
<Label htmlFor="editor-theme">
|
||||
<div>Editor Theme</div>
|
||||
</Label>
|
||||
<div>
|
||||
<Select // Use the custom Select component here
|
||||
id="editor-theme"
|
||||
value={settings.editorTheme}
|
||||
onChange={(e: React.ChangeEvent<HTMLSelectElement>) =>
|
||||
handleThemeChange(e.target.value)
|
||||
}
|
||||
>
|
||||
<option value="cobalt">Cobalt</option>
|
||||
<option value="espresso">Espresso</option>
|
||||
</Select>
|
||||
</div>
|
||||
<Accordion type="single" collapsible className="w-full">
|
||||
<AccordionItem value="item-1">
|
||||
<AccordionTrigger>Theme Settings</AccordionTrigger>
|
||||
<AccordionContent className="space-y-4 flex flex-col">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="app-theme">
|
||||
<div>App Theme</div>
|
||||
</Label>
|
||||
<div>
|
||||
<button
|
||||
className="flex rounded-md border border-input bg-transparent px-3 py-1 text-sm shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50t"
|
||||
onClick={() => {
|
||||
document
|
||||
.querySelector("div.mt-2")
|
||||
?.classList.toggle("dark"); // enable dark mode for sidebar
|
||||
document.body.classList.toggle("dark");
|
||||
document
|
||||
.querySelector('div[role="presentation"]')
|
||||
?.classList.toggle("dark"); // enable dark mode for upload container
|
||||
}}
|
||||
>
|
||||
Toggle dark mode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="editor-theme">
|
||||
<div>Code Editor Theme</div>
|
||||
</Label>
|
||||
<div>
|
||||
<Select // Use the custom Select component here
|
||||
name="editor-theme"
|
||||
value={settings.editorTheme}
|
||||
onValueChange={(value) =>
|
||||
handleThemeChange(value as EditorTheme)
|
||||
}
|
||||
>
|
||||
<SelectTrigger className="w-[180px]">
|
||||
{capitalize(settings.editorTheme)}
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="cobalt">Cobalt</SelectItem>
|
||||
<SelectItem value="espresso">Espresso</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
</div>
|
||||
</AccordionContent>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
|
||||
@ -1,25 +1,54 @@
|
||||
import React from "react";
|
||||
import {
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import { useState } from "react";
|
||||
AlertDialog,
|
||||
AlertDialogAction,
|
||||
AlertDialogContent,
|
||||
AlertDialogFooter,
|
||||
AlertDialogHeader,
|
||||
AlertDialogTitle,
|
||||
} from "./ui/alert-dialog";
|
||||
import { Input } from "./ui/input";
|
||||
import toast from "react-hot-toast";
|
||||
import { PICO_BACKEND_FORM_SECRET } from "../config";
|
||||
|
||||
function TermsOfServiceDialog() {
|
||||
const [isOpen, setIsOpen] = useState(true);
|
||||
const TermsOfServiceDialog: React.FC<{
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
}> = ({ open, onOpenChange }) => {
|
||||
const [email, setEmail] = React.useState("");
|
||||
|
||||
const onSubscribe = async () => {
|
||||
await fetch("https://backend.buildpicoapps.com/form", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({ email, secret: PICO_BACKEND_FORM_SECRET }),
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle className="mb-4">Terms of Service</DialogTitle>
|
||||
</DialogHeader>
|
||||
<AlertDialog open={open} onOpenChange={onOpenChange}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle className="mb-2">
|
||||
Enter your email to get started
|
||||
</AlertDialogTitle>
|
||||
</AlertDialogHeader>
|
||||
|
||||
<div className="mb-2">
|
||||
<Input
|
||||
placeholder="Email"
|
||||
value={email}
|
||||
onChange={(e) => {
|
||||
setEmail(e.target.value);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center space-x-2">
|
||||
<span>
|
||||
By using this website, you agree to the{" "}
|
||||
By providing your email, you consent to receiving occasional product
|
||||
updates, and you accept the{" "}
|
||||
<a
|
||||
href="https://a.picoapps.xyz/camera-write"
|
||||
target="_blank"
|
||||
@ -27,24 +56,36 @@ function TermsOfServiceDialog() {
|
||||
>
|
||||
terms of service
|
||||
</a>
|
||||
. This project is MIT licensed.{" "}
|
||||
. <br />
|
||||
<br />
|
||||
Prefer to run it yourself locally? This project is open source.{" "}
|
||||
<a
|
||||
href="https://github.com/abi/screenshot-to-code"
|
||||
target="_blank"
|
||||
className="underline"
|
||||
>
|
||||
You can run this app locally by downloading the source code from
|
||||
Github.
|
||||
Download the code and get started on Github.
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<DialogFooter>
|
||||
<DialogClose>Agree</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogAction
|
||||
onClick={(e) => {
|
||||
if (!email.trim() || !email.trim().includes("@")) {
|
||||
e.preventDefault();
|
||||
toast.error("Please enter your email");
|
||||
} else {
|
||||
onSubscribe();
|
||||
}
|
||||
}}
|
||||
>
|
||||
Agree
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default TermsOfServiceDialog;
|
||||
|
||||
@ -12,11 +12,13 @@ interface Props {
|
||||
export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [referenceUrl, setReferenceUrl] = useState("");
|
||||
const isDisabled = !screenshotOneApiKey;
|
||||
|
||||
async function takeScreenshot() {
|
||||
if (!screenshotOneApiKey) {
|
||||
toast.error("Please add a Screenshot API key in the settings dialog");
|
||||
toast.error(
|
||||
"Please add a ScreenshotOne API key in the Settings dialog. This is optional - you can also drag/drop and upload images directly.",
|
||||
{ duration: 8000 }
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -64,24 +66,13 @@ export function UrlInputSection({ doCreate, screenshotOneApiKey }: Props) {
|
||||
onChange={(e) => setReferenceUrl(e.target.value)}
|
||||
value={referenceUrl}
|
||||
/>
|
||||
<Button onClick={takeScreenshot} disabled={isDisabled || isLoading}>
|
||||
<Button
|
||||
onClick={takeScreenshot}
|
||||
disabled={isLoading}
|
||||
className="bg-slate-400"
|
||||
>
|
||||
{isLoading ? "Capturing..." : "Capture"}
|
||||
</Button>
|
||||
{isDisabled && (
|
||||
<div className="flex space-y-4 bg-slate-200 p-2 rounded text-stone-800 text-sm">
|
||||
<span>
|
||||
To screenshot a URL, add a{" "}
|
||||
<a
|
||||
href="https://screenshotone.com?via=screenshot-to-code"
|
||||
className="underline"
|
||||
target="_blank"
|
||||
>
|
||||
ScreenshotOne API key
|
||||
</a>{" "}
|
||||
in the settings dialog.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
55
frontend/src/components/ui/accordion.tsx
Normal file
55
frontend/src/components/ui/accordion.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import * as React from "react"
|
||||
import * as AccordionPrimitive from "@radix-ui/react-accordion"
|
||||
import { ChevronDownIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Accordion = AccordionPrimitive.Root
|
||||
|
||||
const AccordionItem = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AccordionPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn("border-b", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AccordionItem.displayName = "AccordionItem"
|
||||
|
||||
const AccordionTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Header className="flex">
|
||||
<AccordionPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex flex-1 items-center justify-between py-4 text-sm font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-180",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronDownIcon className="h-4 w-4 shrink-0 text-muted-foreground transition-transform duration-200" />
|
||||
</AccordionPrimitive.Trigger>
|
||||
</AccordionPrimitive.Header>
|
||||
))
|
||||
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName
|
||||
|
||||
const AccordionContent = React.forwardRef<
|
||||
React.ElementRef<typeof AccordionPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<AccordionPrimitive.Content
|
||||
ref={ref}
|
||||
className="overflow-hidden text-sm data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
|
||||
{...props}
|
||||
>
|
||||
<div className={cn("pb-4 pt-0", className)}>{children}</div>
|
||||
</AccordionPrimitive.Content>
|
||||
))
|
||||
AccordionContent.displayName = AccordionPrimitive.Content.displayName
|
||||
|
||||
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent }
|
||||
139
frontend/src/components/ui/alert-dialog.tsx
Normal file
139
frontend/src/components/ui/alert-dialog.tsx
Normal file
@ -0,0 +1,139 @@
|
||||
import * as React from "react"
|
||||
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
import { buttonVariants } from "@/components/ui/button"
|
||||
|
||||
const AlertDialog = AlertDialogPrimitive.Root
|
||||
|
||||
const AlertDialogTrigger = AlertDialogPrimitive.Trigger
|
||||
|
||||
const AlertDialogPortal = AlertDialogPrimitive.Portal
|
||||
|
||||
const AlertDialogOverlay = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Overlay
|
||||
className={cn(
|
||||
"fixed inset-0 z-50 bg-background/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
ref={ref}
|
||||
/>
|
||||
))
|
||||
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName
|
||||
|
||||
const AlertDialogContent = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPortal>
|
||||
<AlertDialogOverlay />
|
||||
<AlertDialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</AlertDialogPortal>
|
||||
))
|
||||
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName
|
||||
|
||||
const AlertDialogHeader = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col space-y-2 text-center sm:text-left",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogHeader.displayName = "AlertDialogHeader"
|
||||
|
||||
const AlertDialogFooter = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
||||
<div
|
||||
className={cn(
|
||||
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
AlertDialogFooter.displayName = "AlertDialogFooter"
|
||||
|
||||
const AlertDialogTitle = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Title>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName
|
||||
|
||||
const AlertDialogDescription = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Description>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-sm text-muted-foreground", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogDescription.displayName =
|
||||
AlertDialogPrimitive.Description.displayName
|
||||
|
||||
const AlertDialogAction = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Action>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Action
|
||||
ref={ref}
|
||||
className={cn(buttonVariants(), className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName
|
||||
|
||||
const AlertDialogCancel = React.forwardRef<
|
||||
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
|
||||
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<AlertDialogPrimitive.Cancel
|
||||
ref={ref}
|
||||
className={cn(
|
||||
buttonVariants({ variant: "outline" }),
|
||||
"mt-2 sm:mt-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName
|
||||
|
||||
export {
|
||||
AlertDialog,
|
||||
AlertDialogPortal,
|
||||
AlertDialogOverlay,
|
||||
AlertDialogTrigger,
|
||||
AlertDialogContent,
|
||||
AlertDialogHeader,
|
||||
AlertDialogFooter,
|
||||
AlertDialogTitle,
|
||||
AlertDialogDescription,
|
||||
AlertDialogAction,
|
||||
AlertDialogCancel,
|
||||
}
|
||||
36
frontend/src/components/ui/badge.tsx
Normal file
36
frontend/src/components/ui/badge.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import * as React from "react"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
|
||||
secondary:
|
||||
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
destructive:
|
||||
"border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
|
||||
outline: "text-foreground",
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default",
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
export interface BadgeProps
|
||||
extends React.HTMLAttributes<HTMLDivElement>,
|
||||
VariantProps<typeof badgeVariants> {}
|
||||
|
||||
function Badge({ className, variant, ...props }: BadgeProps) {
|
||||
return (
|
||||
<div className={cn(badgeVariants({ variant }), className)} {...props} />
|
||||
)
|
||||
}
|
||||
|
||||
export { Badge, badgeVariants }
|
||||
28
frontend/src/components/ui/checkbox.tsx
Normal file
28
frontend/src/components/ui/checkbox.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import * as React from "react"
|
||||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
|
||||
import { CheckIcon } from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Checkbox = React.forwardRef<
|
||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<CheckboxPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<CheckboxPrimitive.Indicator
|
||||
className={cn("flex items-center justify-center text-current")}
|
||||
>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</CheckboxPrimitive.Indicator>
|
||||
</CheckboxPrimitive.Root>
|
||||
))
|
||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
||||
|
||||
export { Checkbox }
|
||||
29
frontend/src/components/ui/popover.tsx
Normal file
29
frontend/src/components/ui/popover.tsx
Normal file
@ -0,0 +1,29 @@
|
||||
import * as React from "react"
|
||||
import * as PopoverPrimitive from "@radix-ui/react-popover"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Popover = PopoverPrimitive.Root
|
||||
|
||||
const PopoverTrigger = PopoverPrimitive.Trigger
|
||||
|
||||
const PopoverContent = React.forwardRef<
|
||||
React.ElementRef<typeof PopoverPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
|
||||
>(({ className, align = "center", sideOffset = 4, ...props }, ref) => (
|
||||
<PopoverPrimitive.Portal>
|
||||
<PopoverPrimitive.Content
|
||||
ref={ref}
|
||||
align={align}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
"z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</PopoverPrimitive.Portal>
|
||||
))
|
||||
PopoverContent.displayName = PopoverPrimitive.Content.displayName
|
||||
|
||||
export { Popover, PopoverTrigger, PopoverContent }
|
||||
@ -1,24 +1,162 @@
|
||||
import * as React from "react";
|
||||
import { cn } from "@/lib/utils";
|
||||
import * as React from "react"
|
||||
import {
|
||||
CaretSortIcon,
|
||||
CheckIcon,
|
||||
ChevronDownIcon,
|
||||
ChevronUpIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
import * as SelectPrimitive from "@radix-ui/react-select"
|
||||
|
||||
export interface SelectProps
|
||||
extends React.SelectHTMLAttributes<HTMLSelectElement> {}
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const Select = React.forwardRef<HTMLSelectElement, SelectProps>(
|
||||
({ className, ...props }, ref) => {
|
||||
return (
|
||||
<select
|
||||
const Select = SelectPrimitive.Root
|
||||
|
||||
const SelectGroup = SelectPrimitive.Group
|
||||
|
||||
const SelectValue = SelectPrimitive.Value
|
||||
|
||||
const SelectTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Trigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<SelectPrimitive.Icon asChild>
|
||||
<CaretSortIcon className="h-4 w-4 opacity-50" />
|
||||
</SelectPrimitive.Icon>
|
||||
</SelectPrimitive.Trigger>
|
||||
))
|
||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
||||
|
||||
const SelectScrollUpButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollUpButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronUpIcon />
|
||||
</SelectPrimitive.ScrollUpButton>
|
||||
))
|
||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
||||
|
||||
const SelectScrollDownButton = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.ScrollDownButton
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default items-center justify-center py-1",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<ChevronDownIcon />
|
||||
</SelectPrimitive.ScrollDownButton>
|
||||
))
|
||||
SelectScrollDownButton.displayName =
|
||||
SelectPrimitive.ScrollDownButton.displayName
|
||||
|
||||
const SelectContent = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||
<SelectPrimitive.Portal>
|
||||
<SelectPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
position === "popper" &&
|
||||
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||
className
|
||||
)}
|
||||
position={position}
|
||||
{...props}
|
||||
>
|
||||
<SelectScrollUpButton />
|
||||
<SelectPrimitive.Viewport
|
||||
className={cn(
|
||||
"block appearance-none w-full text-sm shadow-sm rounded-md border border-input bg-white py-2 px-3 focus:outline-none focus:border-black",
|
||||
className
|
||||
"p-1",
|
||||
position === "popper" &&
|
||||
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||
)}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
>
|
||||
{children}
|
||||
</SelectPrimitive.Viewport>
|
||||
<SelectScrollDownButton />
|
||||
</SelectPrimitive.Content>
|
||||
</SelectPrimitive.Portal>
|
||||
))
|
||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
||||
|
||||
Select.displayName = "Select";
|
||||
const SelectLabel = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn("px-2 py-1.5 text-sm font-semibold", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
||||
|
||||
export { Select };
|
||||
const SelectItem = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<SelectPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-2 pr-8 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<SelectPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</SelectPrimitive.ItemIndicator>
|
||||
</span>
|
||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||
</SelectPrimitive.Item>
|
||||
))
|
||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
||||
|
||||
const SelectSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<SelectPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
|
||||
|
||||
export {
|
||||
Select,
|
||||
SelectGroup,
|
||||
SelectValue,
|
||||
SelectTrigger,
|
||||
SelectContent,
|
||||
SelectLabel,
|
||||
SelectItem,
|
||||
SelectSeparator,
|
||||
SelectScrollUpButton,
|
||||
SelectScrollDownButton,
|
||||
}
|
||||
@ -7,3 +7,6 @@ export const WS_BACKEND_URL =
|
||||
|
||||
export const HTTP_BACKEND_URL =
|
||||
import.meta.env.VITE_HTTP_BACKEND_URL || "http://127.0.0.1:7001";
|
||||
|
||||
export const PICO_BACKEND_FORM_SECRET =
|
||||
import.meta.env.VITE_PICO_BACKEND_FORM_SECRET || null;
|
||||
|
||||
1
frontend/src/constants.ts
Normal file
1
frontend/src/constants.ts
Normal file
@ -0,0 +1 @@
|
||||
export const USER_CLOSE_WEB_SOCKET_CODE = 4333;
|
||||
@ -1,17 +1,22 @@
|
||||
import toast from "react-hot-toast";
|
||||
import { WS_BACKEND_URL } from "./config";
|
||||
import { USER_CLOSE_WEB_SOCKET_CODE } from "./constants";
|
||||
|
||||
const ERROR_MESSAGE =
|
||||
"Error generating code. Check the Developer Console for details. Feel free to open a Github issue";
|
||||
"Error generating code. Check the Developer Console AND the backend logs for details. Feel free to open a Github issue.";
|
||||
|
||||
const STOP_MESSAGE = "Code generation stopped";
|
||||
|
||||
export interface CodeGenerationParams {
|
||||
generationType: "create" | "update";
|
||||
image: string;
|
||||
resultImage?: string;
|
||||
history?: string[];
|
||||
// isImageGenerationEnabled: boolean; // TODO: Merge with Settings type in types.ts
|
||||
}
|
||||
|
||||
export function generateCode(
|
||||
wsRef: React.MutableRefObject<WebSocket | null>,
|
||||
params: CodeGenerationParams,
|
||||
onChange: (chunk: string) => void,
|
||||
onSetCode: (code: string) => void,
|
||||
@ -22,6 +27,7 @@ export function generateCode(
|
||||
console.log("Connecting to backend @ ", wsUrl);
|
||||
|
||||
const ws = new WebSocket(wsUrl);
|
||||
wsRef.current = ws;
|
||||
|
||||
ws.addEventListener("open", () => {
|
||||
ws.send(JSON.stringify(params));
|
||||
@ -40,15 +46,15 @@ export function generateCode(
|
||||
toast.error(response.value);
|
||||
}
|
||||
});
|
||||
|
||||
ws.addEventListener("close", (event) => {
|
||||
console.log("Connection closed", event.code, event.reason);
|
||||
if (event.code != 1000) {
|
||||
if (event.code === USER_CLOSE_WEB_SOCKET_CODE) {
|
||||
toast.success(STOP_MESSAGE);
|
||||
} else if (event.code !== 1000) {
|
||||
console.error("WebSocket error code", event);
|
||||
toast.error(ERROR_MESSAGE);
|
||||
} else {
|
||||
onComplete();
|
||||
}
|
||||
onComplete();
|
||||
});
|
||||
|
||||
ws.addEventListener("error", (error) => {
|
||||
|
||||
@ -61,9 +61,21 @@
|
||||
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
body.dark {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
div[role="presentation"].dark {
|
||||
background-color: #09090b !important;
|
||||
}
|
||||
|
||||
iframe {
|
||||
background-color: white !important;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 222.2 84% 4.9%;
|
||||
--background: 222.2 0% 0%;
|
||||
--foreground: 210 40% 98%;
|
||||
|
||||
--card: 222.2 84% 4.9%;
|
||||
|
||||
@ -1,6 +1,10 @@
|
||||
import { type ClassValue, clsx } from "clsx"
|
||||
import { twMerge } from "tailwind-merge"
|
||||
|
||||
import { type ClassValue, clsx } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs))
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function capitalize(str: string) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
@ -7,6 +7,6 @@ import { Toaster } from "react-hot-toast";
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
<Toaster />
|
||||
<Toaster toastOptions={{ className:"dark:bg-zinc-950 dark:text-white" }}/>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
||||
@ -1,7 +1,36 @@
|
||||
export enum EditorTheme {
|
||||
ESPRESSO = "espresso",
|
||||
COBALT = "cobalt",
|
||||
}
|
||||
|
||||
export enum CSSOption {
|
||||
TAILWIND = "tailwind",
|
||||
BOOTSTRAP = "bootstrap",
|
||||
}
|
||||
|
||||
export enum JSFrameworkOption {
|
||||
NO_FRAMEWORK = "vanilla",
|
||||
REACT = "react",
|
||||
VUE = "vue",
|
||||
}
|
||||
|
||||
export interface OutputSettings {
|
||||
css: CSSOption;
|
||||
js: JSFrameworkOption;
|
||||
}
|
||||
|
||||
export interface Settings {
|
||||
openAiApiKey: string | null;
|
||||
openAiBaseURL: string | null;
|
||||
screenshotOneApiKey: string | null;
|
||||
isImageGenerationEnabled: boolean;
|
||||
editorTheme: string;
|
||||
editorTheme: EditorTheme;
|
||||
isTermOfServiceAccepted: boolean; // Only relevant for hosted version
|
||||
accessCode: string | null; // Only relevant for hosted version
|
||||
}
|
||||
|
||||
export enum AppState {
|
||||
INITIAL = "INITIAL",
|
||||
CODING = "CODING",
|
||||
CODE_READY = "CODE_READY",
|
||||
}
|
||||
|
||||
@ -1,15 +1,31 @@
|
||||
import path from "path";
|
||||
import { defineConfig } from "vite";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
import checker from "vite-plugin-checker";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import { createHtmlPlugin } from "vite-plugin-html";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
base: process.env.VITE_IS_DEPLOYED ? "/free-tools/screenshot-to-code/" : "",
|
||||
plugins: [react(), checker({ typescript: true })],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
export default ({ mode }) => {
|
||||
process.env = { ...process.env, ...loadEnv(mode, process.cwd()) };
|
||||
return defineConfig({
|
||||
base: "",
|
||||
plugins: [
|
||||
react(),
|
||||
checker({ typescript: true }),
|
||||
createHtmlPlugin({
|
||||
inject: {
|
||||
data: {
|
||||
injectHead: process.env.VITE_IS_DEPLOYED
|
||||
? '<script defer="" data-domain="screenshottocode.com" src="https://plausible.io/js/script.js"></script>'
|
||||
: "",
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
@ -470,6 +470,33 @@
|
||||
resolved "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz"
|
||||
integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==
|
||||
|
||||
"@floating-ui/core@^1.4.2":
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c"
|
||||
integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==
|
||||
dependencies:
|
||||
"@floating-ui/utils" "^0.1.3"
|
||||
|
||||
"@floating-ui/dom@^1.5.1":
|
||||
version "1.5.3"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa"
|
||||
integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==
|
||||
dependencies:
|
||||
"@floating-ui/core" "^1.4.2"
|
||||
"@floating-ui/utils" "^0.1.3"
|
||||
|
||||
"@floating-ui/react-dom@^2.0.0":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.0.4.tgz#b076fafbdfeb881e1d86ae748b7ff95150e9f3ec"
|
||||
integrity sha512-CF8k2rgKeh/49UrnIBs4BdxPUV6vize/Db1d/YbCLyp9GiVZ0BEwf5AiDSxJRCr6yOkGqTFHtmrULxkEfYZ7dQ==
|
||||
dependencies:
|
||||
"@floating-ui/dom" "^1.5.1"
|
||||
|
||||
"@floating-ui/utils@^0.1.3":
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.6.tgz#22958c042e10b67463997bd6ea7115fe28cbcaf9"
|
||||
integrity sha512-OfX7E2oUDYxtBvsuS4e/jSn4Q9Qb6DzgeYtsAdkPZ47znpoNsMgZw0+tVijiv3uGNR6dgNlty6r9rzIzHjtd/A==
|
||||
|
||||
"@humanwhocodes/config-array@^0.11.13":
|
||||
version "0.11.13"
|
||||
resolved "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz"
|
||||
@ -508,6 +535,14 @@
|
||||
resolved "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz"
|
||||
integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
|
||||
|
||||
"@jridgewell/source-map@^0.3.3":
|
||||
version "0.3.5"
|
||||
resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
|
||||
integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
|
||||
dependencies:
|
||||
"@jridgewell/gen-mapping" "^0.3.0"
|
||||
"@jridgewell/trace-mapping" "^0.3.9"
|
||||
|
||||
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
|
||||
version "1.4.15"
|
||||
resolved "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz"
|
||||
@ -586,6 +621,13 @@
|
||||
"@nodelib/fs.scandir" "2.1.5"
|
||||
fastq "^1.6.0"
|
||||
|
||||
"@radix-ui/number@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/number/-/number-1.0.1.tgz#644161a3557f46ed38a042acf4a770e826021674"
|
||||
integrity sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/primitive@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd"
|
||||
@ -593,6 +635,73 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-accordion@^1.1.2":
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-accordion/-/react-accordion-1.1.2.tgz#738441f7343e5142273cdef94d12054c3287966f"
|
||||
integrity sha512-fDG7jcoNKVjSK6yfmuAs0EnPDro0WMXIhMtXdTBWqEioVW206ku+4Lw07e+13lUkFkpoEQ2PdeMIAGpdqEAmDg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-collapsible" "1.0.3"
|
||||
"@radix-ui/react-collection" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
|
||||
"@radix-ui/react-alert-dialog@^1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz#70dd529cbf1e4bff386814d3776901fcaa131b8c"
|
||||
integrity sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-dialog" "1.0.5"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
|
||||
"@radix-ui/react-arrow@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d"
|
||||
integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/react-checkbox@^1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-checkbox/-/react-checkbox-1.0.4.tgz#98f22c38d5010dd6df4c5744cac74087e3275f4b"
|
||||
integrity sha512-CBuGQa52aAYnADZVt/KBQzXrwx6TqnlwtcIPGtVt5JkkzQwMOLJjPukimhfKEr4GQNd43C+djUh5Ikopj8pSLg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-use-previous" "1.0.1"
|
||||
"@radix-ui/react-use-size" "1.0.1"
|
||||
|
||||
"@radix-ui/react-collapsible@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz#df0e22e7a025439f13f62d4e4a9e92c4a0df5b81"
|
||||
integrity sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-collection@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159"
|
||||
@ -618,7 +727,7 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-dialog@^1.0.5":
|
||||
"@radix-ui/react-dialog@1.0.5", "@radix-ui/react-dialog@^1.0.5":
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300"
|
||||
integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==
|
||||
@ -696,6 +805,45 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/react-popover@^1.0.7":
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popover/-/react-popover-1.0.7.tgz#23eb7e3327330cb75ec7b4092d685398c1654e3c"
|
||||
integrity sha512-shtvVnlsxT6faMnK/a7n0wptwBD23xc1Z5mdrtKLwVEfsEMXodS0r5s0/g5P0hX//EKYZS2sxUjqfzlg52ZSnQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.4"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.3"
|
||||
"@radix-ui/react-portal" "1.0.4"
|
||||
"@radix-ui/react-presence" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-popper@1.1.3":
|
||||
version "1.1.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42"
|
||||
integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@floating-ui/react-dom" "^2.0.0"
|
||||
"@radix-ui/react-arrow" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-rect" "1.0.1"
|
||||
"@radix-ui/react-use-size" "1.0.1"
|
||||
"@radix-ui/rect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-portal@1.0.4":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15"
|
||||
@ -737,6 +885,34 @@
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
|
||||
"@radix-ui/react-select@^2.0.0":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-select/-/react-select-2.0.0.tgz#a3511792a51a7018d6559357323a7f52e0e38887"
|
||||
integrity sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/number" "1.0.1"
|
||||
"@radix-ui/primitive" "1.0.1"
|
||||
"@radix-ui/react-collection" "1.0.3"
|
||||
"@radix-ui/react-compose-refs" "1.0.1"
|
||||
"@radix-ui/react-context" "1.0.1"
|
||||
"@radix-ui/react-direction" "1.0.1"
|
||||
"@radix-ui/react-dismissable-layer" "1.0.5"
|
||||
"@radix-ui/react-focus-guards" "1.0.1"
|
||||
"@radix-ui/react-focus-scope" "1.0.4"
|
||||
"@radix-ui/react-id" "1.0.1"
|
||||
"@radix-ui/react-popper" "1.1.3"
|
||||
"@radix-ui/react-portal" "1.0.4"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
"@radix-ui/react-slot" "1.0.2"
|
||||
"@radix-ui/react-use-callback-ref" "1.0.1"
|
||||
"@radix-ui/react-use-controllable-state" "1.0.1"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
"@radix-ui/react-use-previous" "1.0.1"
|
||||
"@radix-ui/react-visually-hidden" "1.0.3"
|
||||
aria-hidden "^1.1.1"
|
||||
react-remove-scroll "2.5.5"
|
||||
|
||||
"@radix-ui/react-separator@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-separator/-/react-separator-1.0.3.tgz#be5a931a543d5726336b112f465f58585c04c8aa"
|
||||
@ -819,6 +995,14 @@
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@radix-ui/react-use-rect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2"
|
||||
integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/rect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-use-size@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2"
|
||||
@ -827,6 +1011,29 @@
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-use-layout-effect" "1.0.1"
|
||||
|
||||
"@radix-ui/react-visually-hidden@1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz#51aed9dd0fe5abcad7dee2a234ad36106a6984ac"
|
||||
integrity sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@radix-ui/react-primitive" "1.0.3"
|
||||
|
||||
"@radix-ui/rect@1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f"
|
||||
integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
|
||||
"@rollup/pluginutils@^4.2.0":
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d"
|
||||
integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==
|
||||
dependencies:
|
||||
estree-walker "^2.0.1"
|
||||
picomatch "^2.2.2"
|
||||
|
||||
"@types/babel__core@^7.20.3":
|
||||
version "7.20.4"
|
||||
resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.4.tgz"
|
||||
@ -1009,7 +1216,7 @@ acorn-jsx@^5.3.2:
|
||||
resolved "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz"
|
||||
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
|
||||
|
||||
acorn@^8.9.0:
|
||||
acorn@^8.8.2, acorn@^8.9.0:
|
||||
version "8.11.2"
|
||||
resolved "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz"
|
||||
integrity sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==
|
||||
@ -1085,6 +1292,11 @@ array-union@^2.1.0:
|
||||
resolved "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz"
|
||||
integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
|
||||
|
||||
async@^3.2.3:
|
||||
version "3.2.5"
|
||||
resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66"
|
||||
integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==
|
||||
|
||||
attr-accept@^2.2.2:
|
||||
version "2.2.2"
|
||||
resolved "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz"
|
||||
@ -1107,11 +1319,21 @@ balanced-match@^1.0.0:
|
||||
resolved "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz"
|
||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||
|
||||
base64-arraybuffer@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz#1c37589a7c4b0746e34bd1feb951da2df01c1bdc"
|
||||
integrity sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==
|
||||
|
||||
binary-extensions@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz"
|
||||
integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
|
||||
|
||||
boolbase@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
|
||||
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
|
||||
|
||||
brace-expansion@^1.1.7:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz"
|
||||
@ -1120,6 +1342,13 @@ brace-expansion@^1.1.7:
|
||||
balanced-match "^1.0.0"
|
||||
concat-map "0.0.1"
|
||||
|
||||
brace-expansion@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.0.1.tgz#1edc459e0f0c548486ecf9fc99f2221364b9a0ae"
|
||||
integrity sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==
|
||||
dependencies:
|
||||
balanced-match "^1.0.0"
|
||||
|
||||
braces@^3.0.2, braces@~3.0.2:
|
||||
version "3.0.2"
|
||||
resolved "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz"
|
||||
@ -1137,11 +1366,24 @@ browserslist@^4.21.10, browserslist@^4.21.9:
|
||||
node-releases "^2.0.13"
|
||||
update-browserslist-db "^1.0.13"
|
||||
|
||||
buffer-from@^1.0.0:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
|
||||
integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
|
||||
|
||||
callsites@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz"
|
||||
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
|
||||
|
||||
camel-case@^4.1.2:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.2.tgz#9728072a954f805228225a6deea6b38461e1bd5a"
|
||||
integrity sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==
|
||||
dependencies:
|
||||
pascal-case "^3.1.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
camelcase-css@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz"
|
||||
@ -1161,7 +1403,7 @@ chalk@^2.4.2:
|
||||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
chalk@^4.0.0, chalk@^4.1.1:
|
||||
chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.1:
|
||||
version "4.1.2"
|
||||
resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz"
|
||||
integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
|
||||
@ -1196,6 +1438,13 @@ classnames@^2.3.2:
|
||||
resolved "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz"
|
||||
integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
|
||||
|
||||
clean-css@^5.2.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-5.3.2.tgz#70ecc7d4d4114921f5d298349ff86a31a9975224"
|
||||
integrity sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==
|
||||
dependencies:
|
||||
source-map "~0.6.0"
|
||||
|
||||
clsx@2.0.0, clsx@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/clsx/-/clsx-2.0.0.tgz#12658f3fd98fafe62075595a5c30e43d18f3d00b"
|
||||
@ -1238,12 +1487,22 @@ color-name@~1.1.4:
|
||||
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz"
|
||||
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
|
||||
|
||||
colorette@^2.0.16:
|
||||
version "2.0.20"
|
||||
resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a"
|
||||
integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==
|
||||
|
||||
commander@^2.20.0:
|
||||
version "2.20.3"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
|
||||
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
|
||||
|
||||
commander@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz"
|
||||
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
|
||||
|
||||
commander@^8.0.0:
|
||||
commander@^8.0.0, commander@^8.3.0:
|
||||
version "8.3.0"
|
||||
resolved "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz"
|
||||
integrity sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==
|
||||
@ -1253,11 +1512,28 @@ concat-map@0.0.1:
|
||||
resolved "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
connect-history-api-fallback@^1.6.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/connect-history-api-fallback/-/connect-history-api-fallback-1.6.0.tgz#8b32089359308d111115d81cad3fceab888f97bc"
|
||||
integrity sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==
|
||||
|
||||
consola@^2.15.3:
|
||||
version "2.15.3"
|
||||
resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.3.tgz#2e11f98d6a4be71ff72e0bdf07bd23e12cb61550"
|
||||
integrity sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==
|
||||
|
||||
convert-source-map@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz"
|
||||
integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==
|
||||
|
||||
copy-to-clipboard@^3.3.3:
|
||||
version "3.3.3"
|
||||
resolved "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz#55ac43a1db8ae639a4bd99511c148cdd1b83a1b0"
|
||||
integrity sha512-2KV8NhB5JqC3ky0r9PMCAZKbUHSwtEo4CwCs0KXgruG43gX5PMqDEBbVU4OUzw2MuAWUfsuFmWvEKG5QRfSnJA==
|
||||
dependencies:
|
||||
toggle-selection "^1.0.6"
|
||||
|
||||
crelt@^1.0.5:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.yarnpkg.com/crelt/-/crelt-1.0.6.tgz#7cc898ea74e190fb6ef9dae57f8f81cf7302df72"
|
||||
@ -1272,6 +1548,29 @@ cross-spawn@^7.0.2:
|
||||
shebang-command "^2.0.0"
|
||||
which "^2.0.1"
|
||||
|
||||
css-line-break@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz#bfef660dfa6f5397ea54116bb3cb4873edbc4fa0"
|
||||
integrity sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
css-select@^4.2.1:
|
||||
version "4.3.0"
|
||||
resolved "https://registry.yarnpkg.com/css-select/-/css-select-4.3.0.tgz#db7129b2846662fd8628cfc496abb2b59e41529b"
|
||||
integrity sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
css-what "^6.0.1"
|
||||
domhandler "^4.3.1"
|
||||
domutils "^2.8.0"
|
||||
nth-check "^2.0.1"
|
||||
|
||||
css-what@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4"
|
||||
integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==
|
||||
|
||||
cssesc@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz"
|
||||
@ -1323,11 +1622,71 @@ doctrine@^3.0.0:
|
||||
dependencies:
|
||||
esutils "^2.0.2"
|
||||
|
||||
dom-serializer@^1.0.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.4.1.tgz#de5d41b1aea290215dc45a6dae8adcf1d32e2d30"
|
||||
integrity sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==
|
||||
dependencies:
|
||||
domelementtype "^2.0.1"
|
||||
domhandler "^4.2.0"
|
||||
entities "^2.0.0"
|
||||
|
||||
domelementtype@^2.0.1, domelementtype@^2.2.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d"
|
||||
integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==
|
||||
|
||||
domhandler@^4.2.0, domhandler@^4.3.1:
|
||||
version "4.3.1"
|
||||
resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-4.3.1.tgz#8d792033416f59d68bc03a5aa7b018c1ca89279c"
|
||||
integrity sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==
|
||||
dependencies:
|
||||
domelementtype "^2.2.0"
|
||||
|
||||
domutils@^2.8.0:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.8.0.tgz#4437def5db6e2d1f5d6ee859bd95ca7d02048135"
|
||||
integrity sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==
|
||||
dependencies:
|
||||
dom-serializer "^1.0.1"
|
||||
domelementtype "^2.2.0"
|
||||
domhandler "^4.2.0"
|
||||
|
||||
dot-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751"
|
||||
integrity sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==
|
||||
dependencies:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
dotenv-expand@^8.0.2:
|
||||
version "8.0.3"
|
||||
resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e"
|
||||
integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==
|
||||
|
||||
dotenv@^16.0.0:
|
||||
version "16.3.1"
|
||||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.3.1.tgz#369034de7d7e5b120972693352a3bf112172cc3e"
|
||||
integrity sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==
|
||||
|
||||
ejs@^3.1.6:
|
||||
version "3.1.9"
|
||||
resolved "https://registry.yarnpkg.com/ejs/-/ejs-3.1.9.tgz#03c9e8777fe12686a9effcef22303ca3d8eeb361"
|
||||
integrity sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==
|
||||
dependencies:
|
||||
jake "^10.8.5"
|
||||
|
||||
electron-to-chromium@^1.4.535:
|
||||
version "1.4.582"
|
||||
resolved "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.582.tgz"
|
||||
integrity sha512-89o0MGoocwYbzqUUjc+VNpeOFSOK9nIdC5wY4N+PVUarUK0MtjyTjks75AZS2bW4Kl8MdewdFsWaH0jLy+JNoA==
|
||||
|
||||
entities@^2.0.0:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55"
|
||||
integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==
|
||||
|
||||
esbuild@^0.18.10:
|
||||
version "0.18.20"
|
||||
resolved "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz"
|
||||
@ -1466,6 +1825,11 @@ estraverse@^5.1.0, estraverse@^5.2.0:
|
||||
resolved "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz"
|
||||
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
|
||||
|
||||
estree-walker@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac"
|
||||
integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==
|
||||
|
||||
esutils@^2.0.2:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz"
|
||||
@ -1476,7 +1840,7 @@ fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
|
||||
resolved "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz"
|
||||
integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
|
||||
|
||||
fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0:
|
||||
fast-glob@^3.2.11, fast-glob@^3.2.7, fast-glob@^3.2.9, fast-glob@^3.3.0:
|
||||
version "3.3.2"
|
||||
resolved "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz"
|
||||
integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==
|
||||
@ -1518,6 +1882,13 @@ file-selector@^0.6.0:
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
filelist@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.4.tgz#f78978a1e944775ff9e62e744424f215e58352b5"
|
||||
integrity sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==
|
||||
dependencies:
|
||||
minimatch "^5.0.1"
|
||||
|
||||
fill-range@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz"
|
||||
@ -1552,6 +1923,15 @@ fraction.js@^4.3.6:
|
||||
resolved "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz"
|
||||
integrity sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==
|
||||
|
||||
fs-extra@^10.0.1:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
fs-extra@^11.1.0:
|
||||
version "11.1.1"
|
||||
resolved "https://registry.npmjs.org/fs-extra/-/fs-extra-11.1.1.tgz"
|
||||
@ -1680,6 +2060,32 @@ hasown@^2.0.0:
|
||||
dependencies:
|
||||
function-bind "^1.1.2"
|
||||
|
||||
he@1.2.0, he@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
|
||||
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
|
||||
|
||||
html-minifier-terser@^6.1.0:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz#bfc818934cc07918f6b3669f5774ecdfd48f32ab"
|
||||
integrity sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==
|
||||
dependencies:
|
||||
camel-case "^4.1.2"
|
||||
clean-css "^5.2.2"
|
||||
commander "^8.3.0"
|
||||
he "^1.2.0"
|
||||
param-case "^3.0.4"
|
||||
relateurl "^0.2.7"
|
||||
terser "^5.10.0"
|
||||
|
||||
html2canvas@^1.4.1:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz#7cef1888311b5011d507794a066041b14669a543"
|
||||
integrity sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==
|
||||
dependencies:
|
||||
css-line-break "^2.1.0"
|
||||
text-segmentation "^1.0.3"
|
||||
|
||||
ignore@^5.2.0, ignore@^5.2.4:
|
||||
version "5.2.4"
|
||||
resolved "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz"
|
||||
@ -1759,6 +2165,16 @@ isexe@^2.0.0:
|
||||
resolved "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
jake@^10.8.5:
|
||||
version "10.8.7"
|
||||
resolved "https://registry.yarnpkg.com/jake/-/jake-10.8.7.tgz#63a32821177940c33f356e0ba44ff9d34e1c7d8f"
|
||||
integrity sha512-ZDi3aP+fG/LchyBzUM804VjddnwfSfsdeYkwt8NcbKRvo4rFkjhs456iLFn3k2ZUWvNe4i48WACDbza8fhq2+w==
|
||||
dependencies:
|
||||
async "^3.2.3"
|
||||
chalk "^4.0.2"
|
||||
filelist "^1.0.4"
|
||||
minimatch "^3.1.2"
|
||||
|
||||
jiti@^1.19.1:
|
||||
version "1.21.0"
|
||||
resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz"
|
||||
@ -1864,6 +2280,13 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0:
|
||||
dependencies:
|
||||
js-tokens "^3.0.0 || ^4.0.0"
|
||||
|
||||
lower-case@^2.0.2:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-2.0.2.tgz#6fa237c63dbdc4a82ca0fd882e4722dc5e634e28"
|
||||
integrity sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==
|
||||
dependencies:
|
||||
tslib "^2.0.3"
|
||||
|
||||
lru-cache@^5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz"
|
||||
@ -1898,6 +2321,13 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
|
||||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
minimatch@^5.0.1:
|
||||
version "5.1.6"
|
||||
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96"
|
||||
integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==
|
||||
dependencies:
|
||||
brace-expansion "^2.0.1"
|
||||
|
||||
ms@2.1.2:
|
||||
version "2.1.2"
|
||||
resolved "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz"
|
||||
@ -1922,6 +2352,22 @@ natural-compare@^1.4.0:
|
||||
resolved "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz"
|
||||
integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
|
||||
|
||||
no-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/no-case/-/no-case-3.0.4.tgz#d361fd5c9800f558551a8369fc0dcd4662b6124d"
|
||||
integrity sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==
|
||||
dependencies:
|
||||
lower-case "^2.0.2"
|
||||
tslib "^2.0.3"
|
||||
|
||||
node-html-parser@^5.3.3:
|
||||
version "5.4.2"
|
||||
resolved "https://registry.yarnpkg.com/node-html-parser/-/node-html-parser-5.4.2.tgz#93e004038c17af80226c942336990a0eaed8136a"
|
||||
integrity sha512-RaBPP3+51hPne/OolXxcz89iYvQvKOydaqoePpOgXcrOKZhjVIzmpKZz+Hd/RBO2/zN2q6CNJhQzucVz+u3Jyw==
|
||||
dependencies:
|
||||
css-select "^4.2.1"
|
||||
he "1.2.0"
|
||||
|
||||
node-releases@^2.0.13:
|
||||
version "2.0.13"
|
||||
resolved "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz"
|
||||
@ -1944,6 +2390,13 @@ npm-run-path@^4.0.1:
|
||||
dependencies:
|
||||
path-key "^3.0.0"
|
||||
|
||||
nth-check@^2.0.1:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d"
|
||||
integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==
|
||||
dependencies:
|
||||
boolbase "^1.0.0"
|
||||
|
||||
object-assign@^4.0.1, object-assign@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz"
|
||||
@ -1987,6 +2440,14 @@ p-locate@^5.0.0:
|
||||
dependencies:
|
||||
p-limit "^3.0.2"
|
||||
|
||||
param-case@^3.0.4:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/param-case/-/param-case-3.0.4.tgz#7d17fe4aa12bde34d4a77d91acfb6219caad01c5"
|
||||
integrity sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==
|
||||
dependencies:
|
||||
dot-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz"
|
||||
@ -1994,6 +2455,14 @@ parent-module@^1.0.0:
|
||||
dependencies:
|
||||
callsites "^3.0.0"
|
||||
|
||||
pascal-case@^3.1.2:
|
||||
version "3.1.2"
|
||||
resolved "https://registry.yarnpkg.com/pascal-case/-/pascal-case-3.1.2.tgz#b48e0ef2b98e205e7c1dae747d0b1508237660eb"
|
||||
integrity sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==
|
||||
dependencies:
|
||||
no-case "^3.0.4"
|
||||
tslib "^2.0.3"
|
||||
|
||||
path-exists@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz"
|
||||
@ -2019,12 +2488,17 @@ path-type@^4.0.0:
|
||||
resolved "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz"
|
||||
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
|
||||
|
||||
pathe@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pathe/-/pathe-0.2.0.tgz#30fd7bbe0a0d91f0e60bae621f5d19e9e225c339"
|
||||
integrity sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==
|
||||
|
||||
picocolors@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz"
|
||||
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
|
||||
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1:
|
||||
picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
@ -2209,6 +2683,11 @@ regenerator-runtime@^0.14.0:
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
|
||||
integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
|
||||
|
||||
relateurl@^0.2.7:
|
||||
version "0.2.7"
|
||||
resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9"
|
||||
integrity sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==
|
||||
|
||||
resolve-from@^4.0.0:
|
||||
version "4.0.0"
|
||||
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz"
|
||||
@ -2290,6 +2769,19 @@ source-map-js@^1.0.2:
|
||||
resolved "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz"
|
||||
integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
|
||||
|
||||
source-map-support@~0.5.20:
|
||||
version "0.5.21"
|
||||
resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
|
||||
integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
|
||||
dependencies:
|
||||
buffer-from "^1.0.0"
|
||||
source-map "^0.6.0"
|
||||
|
||||
source-map@^0.6.0, source-map@~0.6.0:
|
||||
version "0.6.1"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
strip-ansi@^6.0.0, strip-ansi@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz"
|
||||
@ -2379,6 +2871,23 @@ tailwindcss@^3.3.5:
|
||||
resolve "^1.22.2"
|
||||
sucrase "^3.32.0"
|
||||
|
||||
terser@^5.10.0:
|
||||
version "5.24.0"
|
||||
resolved "https://registry.yarnpkg.com/terser/-/terser-5.24.0.tgz#4ae50302977bca4831ccc7b4fef63a3c04228364"
|
||||
integrity sha512-ZpGR4Hy3+wBEzVEnHvstMvqpD/nABNelQn/z2r0fjVWGQsN3bpOLzQlqDxmb4CDZnXq5lpjnQ+mHQLAOpfM5iw==
|
||||
dependencies:
|
||||
"@jridgewell/source-map" "^0.3.3"
|
||||
acorn "^8.8.2"
|
||||
commander "^2.20.0"
|
||||
source-map-support "~0.5.20"
|
||||
|
||||
text-segmentation@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz#52a388159efffe746b24a63ba311b6ac9f2d7943"
|
||||
integrity sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==
|
||||
dependencies:
|
||||
utrie "^1.0.2"
|
||||
|
||||
text-table@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz"
|
||||
@ -2420,6 +2929,11 @@ to-regex-range@^5.0.1:
|
||||
dependencies:
|
||||
is-number "^7.0.0"
|
||||
|
||||
toggle-selection@^1.0.6:
|
||||
version "1.0.6"
|
||||
resolved "https://registry.npmjs.org/toggle-selection/-/toggle-selection-1.0.6.tgz#6e45b1263f2017fa0acc7d89d78b15b8bf77da32"
|
||||
integrity sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==
|
||||
|
||||
ts-api-utils@^1.0.1:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz"
|
||||
@ -2430,7 +2944,7 @@ ts-interface-checker@^0.1.9:
|
||||
resolved "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz"
|
||||
integrity sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==
|
||||
|
||||
tslib@^2.0.0, tslib@^2.1.0, tslib@^2.4.0:
|
||||
tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.4.0:
|
||||
version "2.6.2"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz"
|
||||
integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==
|
||||
@ -2502,6 +3016,13 @@ util-deprecate@^1.0.2:
|
||||
resolved "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
utrie@^1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz#d42fe44de9bc0119c25de7f564a6ed1b2c87a645"
|
||||
integrity sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==
|
||||
dependencies:
|
||||
base64-arraybuffer "^1.0.2"
|
||||
|
||||
vite-plugin-checker@^0.6.2:
|
||||
version "0.6.2"
|
||||
resolved "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.6.2.tgz"
|
||||
@ -2525,6 +3046,24 @@ vite-plugin-checker@^0.6.2:
|
||||
vscode-languageserver-textdocument "^1.0.1"
|
||||
vscode-uri "^3.0.2"
|
||||
|
||||
vite-plugin-html@^3.2.0:
|
||||
version "3.2.0"
|
||||
resolved "https://registry.yarnpkg.com/vite-plugin-html/-/vite-plugin-html-3.2.0.tgz#0d4df9900642a321a139f1c25c05195ba9d0ec79"
|
||||
integrity sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==
|
||||
dependencies:
|
||||
"@rollup/pluginutils" "^4.2.0"
|
||||
colorette "^2.0.16"
|
||||
connect-history-api-fallback "^1.6.0"
|
||||
consola "^2.15.3"
|
||||
dotenv "^16.0.0"
|
||||
dotenv-expand "^8.0.2"
|
||||
ejs "^3.1.6"
|
||||
fast-glob "^3.2.11"
|
||||
fs-extra "^10.0.1"
|
||||
html-minifier-terser "^6.1.0"
|
||||
node-html-parser "^5.3.3"
|
||||
pathe "^0.2.0"
|
||||
|
||||
vite@^4.4.5:
|
||||
version "4.5.0"
|
||||
resolved "https://registry.npmjs.org/vite/-/vite-4.5.0.tgz"
|
||||
|
||||
42
sweep.yaml
Normal file
42
sweep.yaml
Normal file
@ -0,0 +1,42 @@
|
||||
# Sweep AI turns bugs & feature requests into code changes (https://sweep.dev)
|
||||
# For details on our config file, check out our docs at https://docs.sweep.dev/usage/config
|
||||
|
||||
# This setting contains a list of rules that Sweep will check for. If any of these rules are broken in a new commit, Sweep will create an pull request to fix the broken rule.
|
||||
rules:
|
||||
- "All docstrings and comments should be up to date."
|
||||
['All new business logic should have corresponding unit tests.', 'Refactor large functions to be more modular.', 'Add docstrings to all functions and file headers.']
|
||||
|
||||
# This is the branch that Sweep will develop from and make pull requests to. Most people use 'main' or 'master' but some users also use 'dev' or 'staging'.
|
||||
branch: 'main'
|
||||
|
||||
# By default Sweep will read the logs and outputs from your existing Github Actions. To disable this, set this to false.
|
||||
gha_enabled: True
|
||||
|
||||
# This is the description of your project. It will be used by sweep when creating PRs. You can tell Sweep what's unique about your project, what frameworks you use, or anything else you want.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# description: sweepai/sweep is a python project. The main api endpoints are in sweepai/api.py. Write code that adheres to PEP8.
|
||||
description: ''
|
||||
|
||||
# This sets whether to create pull requests as drafts. If this is set to True, then all pull requests will be created as drafts and GitHub Actions will not be triggered.
|
||||
draft: False
|
||||
|
||||
# This is a list of directories that Sweep will not be able to edit.
|
||||
blocked_dirs: []
|
||||
|
||||
# This is a list of documentation links that Sweep will use to help it understand your code. You can add links to documentation for any packages you use here.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# docs:
|
||||
# - PyGitHub: ["https://pygithub.readthedocs.io/en/latest/", "We use pygithub to interact with the GitHub API"]
|
||||
docs: []
|
||||
|
||||
# Sandbox executes commands in a sandboxed environment to validate code changes after every edit to guarantee pristine code. For more details, see the [Sandbox](./sandbox) page.
|
||||
sandbox:
|
||||
install:
|
||||
- trunk init
|
||||
check:
|
||||
- trunk fmt {file_path} || return 0
|
||||
- trunk check --fix --print-failures {file_path}
|
||||
Loading…
Reference in New Issue
Block a user