🚧 Last change
This commit is contained in:
+13
-10
@@ -6,15 +6,15 @@ from starlette.middleware.sessions import SessionMiddleware
|
||||
|
||||
from classquiz.config import settings
|
||||
from classquiz.db import database
|
||||
from classquiz.routers import users, quiz, utils, stats, storage, search, testing_routes
|
||||
from classquiz.routers import users, quiz, utils, stats, storage, search, testing_routes, editor
|
||||
from classquiz.socket_server import sio
|
||||
from classquiz.helpers import meilisearch_init
|
||||
|
||||
settings = settings()
|
||||
if settings.sentry_dsn:
|
||||
sentry_sdk.init(dsn=settings.sentry_dsn, integrations=[RedisIntegration()])
|
||||
# app = FastAPI(redoc_url="", docs_url="/api/docs")
|
||||
app = FastAPI(redoc_url="", docs_url="")
|
||||
app = FastAPI(redoc_url="", docs_url="/api/docs")
|
||||
# app = FastAPI(redoc_url="", docs_url="")
|
||||
app.state.database = database
|
||||
|
||||
|
||||
@@ -46,11 +46,14 @@ async def shutdown() -> None:
|
||||
|
||||
|
||||
app.add_middleware(SessionMiddleware, secret_key=settings.secret_key)
|
||||
app.include_router(users.router, tags=["users"], prefix="/api/v1/users")
|
||||
app.include_router(quiz.router, tags=["quiz"], prefix="/api/v1/quiz")
|
||||
app.include_router(utils.router, tags=["utils"], prefix="/api/v1/utils")
|
||||
app.include_router(stats.router, tags=["stats"], prefix="/api/v1/stats")
|
||||
app.include_router(storage.router, tags=["storage"], prefix="/api/v1/storage")
|
||||
app.include_router(search.router, tags=["search"], prefix="/api/v1/search")
|
||||
app.include_router(testing_routes.router, tags=["internal", "testing"], prefix="/api/v1/internal/testing")
|
||||
app.include_router(users.router, tags=["users"], prefix="/api/v1/users", include_in_schema=False)
|
||||
app.include_router(quiz.router, tags=["quiz"], prefix="/api/v1/quiz", include_in_schema=False)
|
||||
app.include_router(utils.router, tags=["utils"], prefix="/api/v1/utils", include_in_schema=True)
|
||||
app.include_router(stats.router, tags=["stats"], prefix="/api/v1/stats", include_in_schema=True)
|
||||
app.include_router(storage.router, tags=["storage"], prefix="/api/v1/storage", include_in_schema=True)
|
||||
app.include_router(search.router, tags=["search"], prefix="/api/v1/search", include_in_schema=True)
|
||||
app.include_router(
|
||||
testing_routes.router, tags=["internal", "testing"], prefix="/api/v1/internal/testing", include_in_schema=False
|
||||
)
|
||||
app.include_router(editor.router, tags=["editor"], prefix="/api/v1/editor", include_in_schema=True)
|
||||
app.mount("/", ASGIApp(sio))
|
||||
|
||||
+3
-2
@@ -1,6 +1,7 @@
|
||||
from functools import lru_cache
|
||||
|
||||
import redis.asyncio as redis_lib
|
||||
from redis import asyncio as redis_lib
|
||||
import redis as redis_base_lib
|
||||
from pydantic import BaseSettings, RedisDsn, PostgresDsn
|
||||
import meilisearch as MeiliSearch
|
||||
from typing import Optional
|
||||
@@ -53,7 +54,7 @@ def settings() -> Settings:
|
||||
return Settings()
|
||||
|
||||
|
||||
redis: redis_lib.client.Redis = redis_lib.Redis().from_url(settings().redis)
|
||||
redis: redis_base_lib.client.Redis = redis_lib.Redis().from_url(settings().redis)
|
||||
storage: Storage = Storage(
|
||||
backend=settings().storage_backend,
|
||||
deta_key=settings().deta_project_key,
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
import asyncio
|
||||
import html
|
||||
import uuid
|
||||
from typing import Optional
|
||||
|
||||
import bleach
|
||||
import pydantic
|
||||
from fastapi import APIRouter, File, Form, UploadFile, HTTPException, BackgroundTasks, Depends
|
||||
from pydantic import BaseModel
|
||||
|
||||
from classquiz.config import settings, redis, storage, meilisearch
|
||||
from classquiz.db.models import Quiz, QuizInput, User
|
||||
from classquiz.auth import get_current_user
|
||||
import os
|
||||
from datetime import datetime
|
||||
from uuid import UUID
|
||||
|
||||
from classquiz.helpers import get_meili_data
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
class InitEditorResponse(BaseModel):
|
||||
token: str
|
||||
|
||||
|
||||
class EditSessionData(BaseModel):
|
||||
quiz_id: UUID
|
||||
edit: bool
|
||||
user_id: UUID
|
||||
|
||||
|
||||
async def delete_images_for_edit_id(edit_id: str):
|
||||
await asyncio.sleep(30)
|
||||
res = await redis.lrange(f"edit_session:{edit_id}:images", 0, -1)
|
||||
if len(res) != 0:
|
||||
for image_id in res:
|
||||
await storage.delete(image_id)
|
||||
|
||||
|
||||
@router.post("/start", response_model=InitEditorResponse)
|
||||
async def init_editor(edit: bool, quiz_id: Optional[UUID] = None, user: User = Depends(get_current_user)):
|
||||
if edit and quiz_id is not None:
|
||||
if await Quiz.objects.get_or_none(id=quiz_id, user_id=user.id) is None:
|
||||
raise HTTPException(status_code=404, detail="Quiz not found")
|
||||
if not edit and quiz_id is not None:
|
||||
raise HTTPException(status_code=400, detail="You can't choose the id for your quiz")
|
||||
if edit and quiz_id is None:
|
||||
raise HTTPException(status_code=400, detail="Edit can't be true if quiz_id is None")
|
||||
if quiz_id is None:
|
||||
quiz_id = uuid.uuid4()
|
||||
edit_id = os.urandom(4).hex()
|
||||
await redis.sadd("edit_sessions", edit_id)
|
||||
await redis.set(
|
||||
f"edit_session:{edit_id}", EditSessionData(quiz_id=quiz_id, edit=edit, user_id=user.id).json(), ex=3600
|
||||
)
|
||||
return InitEditorResponse(token=edit_id)
|
||||
|
||||
|
||||
class UploadImageReturn(BaseModel):
|
||||
id: str
|
||||
|
||||
|
||||
@router.post("/image", response_model=UploadImageReturn)
|
||||
async def upload_image(edit_id: str, file: UploadFile = File()):
|
||||
session_data = await redis.get(f"edit_session:{edit_id}")
|
||||
if session_data is None:
|
||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
||||
session_data = EditSessionData.parse_raw(session_data)
|
||||
file_name = f"{session_data.quiz_id}--{uuid.uuid4()}"
|
||||
print("Uploading...")
|
||||
await storage.upload(file_name=file_name, file_data=await file.read())
|
||||
print("Finished Upload")
|
||||
await redis.lpush(f"edit_session:{edit_id}:images", file_name)
|
||||
return UploadImageReturn(id=file_name)
|
||||
|
||||
|
||||
@router.post("/finish")
|
||||
async def finish_edit(edit_id: str, quiz_input: QuizInput):
|
||||
session_data = await redis.get(f"edit_session:{edit_id}")
|
||||
if session_data is None:
|
||||
raise HTTPException(status_code=401, detail="Edit ID not found!")
|
||||
session_data = EditSessionData.parse_raw(session_data)
|
||||
quiz_input.title = html.unescape(bleach.clean(quiz_input.title, tags=[], strip=True))
|
||||
quiz_input.description = html.unescape(bleach.clean(quiz_input.description, tags=[], strip=True))
|
||||
if session_data.edit:
|
||||
quiz = await Quiz.objects.get_or_none(id=session_data.quiz_id, user_id=session_data.user_id)
|
||||
meilisearch.index(settings.meilisearch_index).update_documents([await get_meili_data(quiz)])
|
||||
if quiz.public and not quiz_input.public:
|
||||
meilisearch.index(settings.meilisearch_index).delete_document(str(quiz.id))
|
||||
if not quiz.public and quiz_input.public:
|
||||
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
|
||||
quiz.title = quiz_input.title
|
||||
quiz.public = quiz_input.public
|
||||
quiz.description = quiz_input.description
|
||||
quiz.updated_at = datetime.now()
|
||||
quiz.questions = quiz_input.dict()["questions"]
|
||||
return await quiz.save()
|
||||
else:
|
||||
quiz = Quiz(**quiz_input.dict(), user_id=session_data.user_id, id=session_data.quiz_id)
|
||||
await redis.delete("global_quiz_count")
|
||||
if quiz_input.public:
|
||||
meilisearch.index(settings.meilisearch_index).add_documents([await get_meili_data(quiz)])
|
||||
return await quiz.save()
|
||||
@@ -31,6 +31,13 @@
|
||||
"@types/yup": "^0.29.14",
|
||||
"@typescript-eslint/eslint-plugin": "^5.27.1",
|
||||
"@typescript-eslint/parser": "^5.27.1",
|
||||
"@uppy/compressor": "^0.3.0",
|
||||
"@uppy/core": "^2.3.1",
|
||||
"@uppy/dashboard": "^2.3.0",
|
||||
"@uppy/drop-target": "^1.1.3",
|
||||
"@uppy/image-editor": "^1.3.0",
|
||||
"@uppy/svelte": "^1.0.8",
|
||||
"@uppy/xhr-upload": "^2.1.2",
|
||||
"autoprefixer": "^10.4.7",
|
||||
"cookie": "^0.5.0",
|
||||
"cssnano": "^5.1.11",
|
||||
|
||||
Generated
+372
-3
@@ -18,6 +18,13 @@ specifiers:
|
||||
'@types/yup': ^0.29.14
|
||||
'@typescript-eslint/eslint-plugin': ^5.27.1
|
||||
'@typescript-eslint/parser': ^5.27.1
|
||||
'@uppy/compressor': ^0.3.0
|
||||
'@uppy/core': ^2.3.1
|
||||
'@uppy/dashboard': ^2.3.0
|
||||
'@uppy/drop-target': ^1.1.3
|
||||
'@uppy/image-editor': ^1.3.0
|
||||
'@uppy/svelte': ^1.0.8
|
||||
'@uppy/xhr-upload': ^2.1.2
|
||||
autoprefixer: ^10.4.7
|
||||
cookie: ^0.5.0
|
||||
cssnano: ^5.1.11
|
||||
@@ -73,6 +80,13 @@ devDependencies:
|
||||
'@types/yup': 0.29.14
|
||||
'@typescript-eslint/eslint-plugin': 5.27.1_aq7uryhocdbvbqum33pitcm3y4
|
||||
'@typescript-eslint/parser': 5.27.1_ud6rd4xtew5bv4yhvkvu24pzm4
|
||||
'@uppy/compressor': 0.3.0_@uppy+core@2.3.1
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/dashboard': 2.3.0_@uppy+core@2.3.1
|
||||
'@uppy/drop-target': 1.1.3_@uppy+core@2.3.1
|
||||
'@uppy/image-editor': 1.3.0_@uppy+core@2.3.1
|
||||
'@uppy/svelte': 1.0.8_w57owxejta2mqj4rr2tj54bhsi
|
||||
'@uppy/xhr-upload': 2.1.2_@uppy+core@2.3.1
|
||||
autoprefixer: 10.4.7_postcss@8.4.14
|
||||
cookie: 0.5.0
|
||||
cssnano: 5.1.11_postcss@8.4.14
|
||||
@@ -592,6 +606,20 @@ packages:
|
||||
tailwindcss: 3.1.2
|
||||
dev: true
|
||||
|
||||
/@transloadit/prettier-bytes/0.0.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-VeJbUb0wEKbcwaSlj5n+LscBl9IPgLPkHVGBkh00cztv6X4L/TJXK58LzFuBKX7/GAfiGhIwH67YTLTlzvIzBA==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/@transloadit/prettier-bytes/0.0.9:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-pCvdmea/F3Tn4hAtHqNXmjcixSaroJJ+L3STXlYJdir1g1m2mRQpWbN8a4SvgQtaw2930Ckhdx8qXdXBFMKbAA==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/@trysound/sax/0.2.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -822,6 +850,226 @@ packages:
|
||||
eslint-visitor-keys: 3.3.0
|
||||
dev: true
|
||||
|
||||
/@uppy/companion-client/2.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Y3E10NJLMfp/wjgthNhx3gJtT67fzFCPNPFwpNNRs5iJsW6PANhJ420eyMUFzfmEZ56ZzGYxr5pzJZx8YxHICQ==
|
||||
}
|
||||
dependencies:
|
||||
'@uppy/utils': 4.1.0
|
||||
namespace-emitter: 2.0.1
|
||||
dev: true
|
||||
|
||||
/@uppy/compressor/0.3.0_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-M05fz0TA5Oy0wW930j+eROlcPADNu20S0L2n4ej7bZlUzNLMixjE6UnwW5huSm2vzZV5tEPKvSJ0SnU/E+sM4Q==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@transloadit/prettier-bytes': 0.0.9
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
compressorjs: 1.1.1
|
||||
preact: 10.8.1
|
||||
promise-queue: 2.2.5
|
||||
dev: true
|
||||
|
||||
/@uppy/core/2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-KV04X7ueYbYX1p37/i3QsoQSw8IDP8Yb+Bh9KNN0X2Vcun6K2VnNjhVtPmPXtyjDZooK7lVIqhRX8TZWcSfgSQ==
|
||||
}
|
||||
dependencies:
|
||||
'@transloadit/prettier-bytes': 0.0.7
|
||||
'@uppy/store-default': 2.1.0
|
||||
'@uppy/utils': 4.1.0
|
||||
lodash.throttle: 4.1.1
|
||||
mime-match: 1.0.2
|
||||
namespace-emitter: 2.0.1
|
||||
nanoid: 3.3.4
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/dashboard/2.3.0_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-q7GaLrYt3Tb+x4R8rARfzBuqPtdf6lKnc5Rg/XW18VOIaQM/UZNuBr+EVGhwuDiQt/1QJz1t2lS2Mx246zJ/qA==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@transloadit/prettier-bytes': 0.0.7
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/informer': 2.1.0_@uppy+core@2.3.1
|
||||
'@uppy/provider-views': 2.1.1_@uppy+core@2.3.1
|
||||
'@uppy/status-bar': 2.2.1_@uppy+core@2.3.1
|
||||
'@uppy/thumbnail-generator': 2.2.0_@uppy+core@2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
classnames: 2.3.1
|
||||
is-shallow-equal: 1.0.1
|
||||
lodash.debounce: 4.0.8
|
||||
memoize-one: 5.2.1
|
||||
nanoid: 3.3.4
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/drag-drop/2.1.1_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-3cTXzmoMpCJvaM0H59acm/CNkS3y6jr03SYK7u6X/vd3/a6e3XyfaGc/jwKSsGBm+oMLftFKv/inhQLKOZR5CQ==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/drop-target/1.1.3_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Cd+L3mdVovS6G0Yv0Hah18Utz3czQ75YrGJ65MlHFzfJccOQJczvxn18FVssKyO6Zc0ce1y0mGKGjUClTM5dMA==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.1.9
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
dev: true
|
||||
|
||||
/@uppy/image-editor/1.3.0_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ZTF0A2e39K5ZDBFu6pSxKPL9Tx6Xslsu0k+1eW2bu2pzX0k4b4hVV6wciBZTr2UzitzdF7x83oIefiVQyBZ7/A==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
cropperjs: 1.5.7
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/informer/2.1.0_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-/nR7Dxh1RH1FvKRPQ/xZk1lS3F+lh93xaN6Z9utGg5u1hHREuaHfFTcEXhkeq1/hiKbVYsjNaNZ5aPe7+4flkg==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/progress-bar/2.1.1_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Kh/kM5rGUABxwwDRzR3pjDzkNRELES2T3nIBdstRIfWCxlHjIFpgqOkvvNRXKflm2hcYUzTRELgtfi09LuqE9A==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/provider-views/2.1.1_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-Emk18dBAUx73uhkYL/ryFqu9pQ5hVnW5P+b9J/bq3bxDaEtVfQvhFPfaa8/gHNb8t499ytHmRPJYUJlqfG3/5A==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
classnames: 2.3.1
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/status-bar/2.2.1_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-jnJ/DSEuWGPU6+m0i2yfGl5cPb6CJnxDtcL6/gXKOyjGeRfriWjeEsRHmEkdK+xLv/AJC//eEfSOCL3N737p0g==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@transloadit/prettier-bytes': 0.0.7
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
classnames: 2.3.1
|
||||
lodash.throttle: 4.1.1
|
||||
preact: 10.8.1
|
||||
dev: true
|
||||
|
||||
/@uppy/store-default/2.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-BkcR1wGw6Kwbvr8m1tKF9EDDWSTJoTGnVseBF/iW4bzR22assbtxZIE1iroo68UMqYEG4rv63SX4BUEtNvVjdA==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/@uppy/svelte/1.0.8_w57owxejta2mqj4rr2tj54bhsi:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-A/80S2LCRjszzGbdDe7e9YU77HAa2PiIMqAjmyuOY5ZMyoIsRjeT8G9CNjeDMGMJBsQLyysDWbF3AZqPyJukQA==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.2.0
|
||||
svelte: ^3.0.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/dashboard': 2.3.0_@uppy+core@2.3.1
|
||||
'@uppy/drag-drop': 2.1.1_@uppy+core@2.3.1
|
||||
'@uppy/progress-bar': 2.1.1_@uppy+core@2.3.1
|
||||
'@uppy/status-bar': 2.2.1_@uppy+core@2.3.1
|
||||
svelte: 3.48.0
|
||||
dev: true
|
||||
|
||||
/@uppy/thumbnail-generator/2.2.0_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-ySZoB0tK7sQ/PRSFvU5JLxcq/wVqJM/G95cVdJ2Wv1BzQ7+/wKBEPFgJuHDqgzpgUrHwmc+N0JYVXqfk297DZA==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.0
|
||||
dependencies:
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
exifr: 7.1.3
|
||||
dev: true
|
||||
|
||||
/@uppy/utils/4.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-C47DUl4uLzmQZdW+VmetIgGRurXuPsvb+/pyYqh9DJn0Phep8u7AOj/tlJA5CHv4pefNHsFjXpaWfSUG3HtW3A==
|
||||
}
|
||||
dependencies:
|
||||
lodash.throttle: 4.1.1
|
||||
dev: true
|
||||
|
||||
/@uppy/xhr-upload/2.1.2_@uppy+core@2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-VCsb7J5yHsof49nnUa+Y1n27UMtqHPttQmmoCa5hmjqa9R7ZISpBkXKOQmZo526eopKNuAKSAdkHWfCm8efJTA==
|
||||
}
|
||||
peerDependencies:
|
||||
'@uppy/core': ^2.3.1
|
||||
dependencies:
|
||||
'@uppy/companion-client': 2.2.1
|
||||
'@uppy/core': 2.3.1
|
||||
'@uppy/utils': 4.1.0
|
||||
nanoid: 3.3.4
|
||||
dev: true
|
||||
|
||||
/@vercel/nft/0.19.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -1061,6 +1309,13 @@ packages:
|
||||
file-uri-to-path: 1.0.0
|
||||
dev: true
|
||||
|
||||
/blueimp-canvas-to-blob/3.29.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-0pcSSGxC0QxT+yVkivxIqW0Y4VlO2XSDPofBAqoJ1qJxgH9eiUDLv50Rixij2cDuEfx4M6DpD9UGZpRhT5Q8qg==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/boolbase/1.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -1189,6 +1444,13 @@ packages:
|
||||
engines: { node: '>=10' }
|
||||
dev: true
|
||||
|
||||
/classnames/2.3.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-OlQdbZ7gLfGarSqxesMesDa5uz7KFbID8Kpq/SxIoNGDqY8lSYs0D+hhtBXhcdB3rcbXArFr7vlHheLk1voeNA==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/cluster-key-slot/1.1.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -1245,6 +1507,16 @@ packages:
|
||||
engines: { node: '>= 10' }
|
||||
dev: true
|
||||
|
||||
/compressorjs/1.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-SysRuUPfmUNoq+RviE0iMFVUmoX2q/x+7PkEPUmk6NGkd85hDrmvujx0Qtp8UCGA6KMe5kuodsylPQcNaLf60w==
|
||||
}
|
||||
dependencies:
|
||||
blueimp-canvas-to-blob: 3.29.0
|
||||
is-blob: 2.1.0
|
||||
dev: true
|
||||
|
||||
/concat-map/0.0.1:
|
||||
resolution: { integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= }
|
||||
dev: true
|
||||
@@ -1271,6 +1543,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/cropperjs/1.5.7:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-sGj+G/ofKh+f6A4BtXLJwtcKJgMUsXYVUubfTo9grERiDGXncttefmue/fyQFvn8wfdyoD1KhDRYLfjkJFl0yw==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/cross-spawn/7.0.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -2170,6 +2449,13 @@ packages:
|
||||
engines: { node: '>=0.10.0' }
|
||||
dev: true
|
||||
|
||||
/exifr/7.1.3:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/fast-deep-equal/3.1.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -2672,6 +2958,14 @@ packages:
|
||||
binary-extensions: 2.2.0
|
||||
dev: true
|
||||
|
||||
/is-blob/2.1.0:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-SZ/fTft5eUhQM6oF/ZaASFDEdbFVe89Imltn9uZr03wdKMcWNVYSMjQPFtg05QuNkt5l5c135ElvXEQG0rk4tw==
|
||||
}
|
||||
engines: { node: '>=6' }
|
||||
dev: true
|
||||
|
||||
/is-core-module/2.9.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2725,6 +3019,13 @@ packages:
|
||||
engines: { node: '>=0.12.0' }
|
||||
dev: true
|
||||
|
||||
/is-shallow-equal/1.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-lq5RvK+85Hs5J3p4oA4256M1FEffzmI533ikeDHvJd42nouRRx5wBzt36JuviiGe5dIPyHON/d0/Up+PBo6XkQ==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/isarray/1.0.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2819,6 +3120,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/lodash.debounce/4.0.8:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/lodash.defaults/4.2.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2854,6 +3162,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/lodash.throttle/4.1.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/lodash.uniq/4.5.0:
|
||||
resolution:
|
||||
{
|
||||
@@ -2953,6 +3268,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/memoize-one/5.2.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/merge2/1.4.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -2972,6 +3294,15 @@ packages:
|
||||
picomatch: 2.3.1
|
||||
dev: true
|
||||
|
||||
/mime-match/1.0.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-VXp/ugGDVh3eCLOBCiHZMYWQaTNUHv2IJrut+yXA6+JbLPXHglHwfS/5A5L0ll+jkCY7fIzRJcH6OIunF+c6Cg==
|
||||
}
|
||||
dependencies:
|
||||
wildcard: 1.1.2
|
||||
dev: true
|
||||
|
||||
/min-indent/1.0.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -3092,6 +3423,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/namespace-emitter/2.0.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-N/sMKHniSDJBjfrkbS/tpkPj4RAbvW3mr8UAzvlMHyun93XEm83IAvhWtJVHo+RHn/oO8Job5YN4b+wRjSVp5g==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/nanoclone/0.2.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -3938,6 +4276,13 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/preact/10.8.1:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-p5CKQ0MCEXTGKGOHiFaNE2V2nDq2hvDHykXvIlz+4lbfJ9umLZr8JS/fa1bXUwRcHXK+Ljk8zqmDhr25n0LtVg==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/prelude-ls/1.2.1:
|
||||
resolution:
|
||||
{
|
||||
@@ -3975,6 +4320,14 @@ packages:
|
||||
}
|
||||
dev: true
|
||||
|
||||
/promise-queue/2.2.5:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-p/iXrPSVfnqPft24ZdNNLECw/UrtLTpT3jpAAMzl/o5/rDsGCPo3/CQS2611flL6LkoEJ3oQZw7C8Q80ZISXRQ==
|
||||
}
|
||||
engines: { node: '>= 0.8.0' }
|
||||
dev: true
|
||||
|
||||
/property-expr/2.0.5:
|
||||
resolution:
|
||||
{
|
||||
@@ -4979,11 +5332,17 @@ packages:
|
||||
dev: true
|
||||
|
||||
/webidl-conversions/3.0.1:
|
||||
resolution: { integrity: sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/whatwg-url/5.0.0:
|
||||
resolution: { integrity: sha1-lmRU6HZUYuN2RNNib2dCzotwll0= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==
|
||||
}
|
||||
dependencies:
|
||||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
@@ -5009,6 +5368,13 @@ packages:
|
||||
string-width: 4.2.3
|
||||
dev: true
|
||||
|
||||
/wildcard/1.1.2:
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-DXukZJxpHA8LuotRwL0pP1+rS6CS7FF2qStDDE1C7DDg2rLud2PXRMuEDYIPhgEezwnlHNL4c+N6MfMTjCGTng==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/word-wrap/1.2.3:
|
||||
resolution:
|
||||
{
|
||||
@@ -5029,7 +5395,10 @@ packages:
|
||||
dev: true
|
||||
|
||||
/wrappy/1.0.2:
|
||||
resolution: { integrity: sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= }
|
||||
resolution:
|
||||
{
|
||||
integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
|
||||
}
|
||||
dev: true
|
||||
|
||||
/ws/8.2.3:
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
/** Dispatch event on click outside of node */
|
||||
export function clickOutside(node) {
|
||||
const handleClick = (event) => {
|
||||
if (node && !node.contains(event.target) && !event.defaultPrevented) {
|
||||
node.dispatchEvent(new CustomEvent('click_outside', node));
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', handleClick, true);
|
||||
|
||||
return {
|
||||
destroy() {
|
||||
document.removeEventListener('click', handleClick, true);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
import Sidebar from '$lib/editor/sidebar.svelte';
|
||||
import SettingsCard from '$lib/editor/settings-card.svelte';
|
||||
import QuizCard from '$lib/editor/card.svelte';
|
||||
import Spinner from './Spinner.svelte';
|
||||
|
||||
const { t } = getLocalization();
|
||||
|
||||
@@ -14,6 +15,7 @@
|
||||
|
||||
export let data: EditorData;
|
||||
export let submit_button_text = 'Create';
|
||||
export let quiz_id: string | null;
|
||||
let selected_question = -1;
|
||||
let imgur_links_valid = false;
|
||||
|
||||
@@ -63,48 +65,75 @@
|
||||
right: false,
|
||||
answer: ''
|
||||
};
|
||||
let edit_id;
|
||||
|
||||
const getEditID = async () => {
|
||||
let res;
|
||||
if (quiz_id === null) {
|
||||
res = await fetch(`/api/v1/editor/start?edit=false`, {
|
||||
method: 'POST'
|
||||
});
|
||||
} else {
|
||||
res = await fetch(`/api/v1/editor/start?edit=true&quiz_id=${quiz_id}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
}
|
||||
if (res.status === 200) {
|
||||
const json = await res.json();
|
||||
edit_id = json.token;
|
||||
console.log(edit_id, json);
|
||||
} else {
|
||||
alert('Error!');
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="grid grid-cols-6 h-screen w-screen">
|
||||
<div>
|
||||
<Sidebar bind:data bind:selected_question />
|
||||
</div>
|
||||
<div class="col-span-5 flex flex-col">
|
||||
<div class="h-10 w-full bg-white mb-10 flex align-middle justify-center rounded-br-lg">
|
||||
{#if schemaInvalid}
|
||||
<p class="text-center w-full text-red-600 h-full mt-0.5 font-semibold">
|
||||
{yupErrorMessage}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-center w-full text-black h-full align-bottom mt-0.5">{data.title}</p>
|
||||
{/if}
|
||||
<button
|
||||
class="pr-2 align-middle bg-purple-400 pl-2 ml-auto whitespace-nowrap disabled:opacity-60 rounded-br-lg"
|
||||
disabled={schemaInvalid}
|
||||
>
|
||||
<span>Save</span>
|
||||
<svg
|
||||
class="w-6 h-6 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
{#await getEditID()}
|
||||
<Spinner />
|
||||
{:then _}
|
||||
<div class="grid grid-cols-6 h-screen w-screen">
|
||||
<div>
|
||||
<Sidebar bind:data bind:selected_question />
|
||||
</div>
|
||||
<div class="col-span-5 flex flex-col">
|
||||
<div class="h-10 w-full bg-white mb-10 flex align-middle justify-center rounded-br-lg">
|
||||
{#if schemaInvalid}
|
||||
<p class="text-center w-full text-red-600 h-full mt-0.5 font-semibold">
|
||||
{yupErrorMessage}
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text-center w-full text-black h-full align-bottom mt-0.5">
|
||||
{data.title}
|
||||
</p>
|
||||
{/if}
|
||||
<button
|
||||
class="pr-2 align-middle bg-purple-400 pl-2 ml-auto whitespace-nowrap disabled:opacity-60 rounded-br-lg"
|
||||
disabled={schemaInvalid}
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-full h-full">
|
||||
{#if selected_question === -1}
|
||||
<SettingsCard bind:data />
|
||||
{:else}
|
||||
<QuizCard bind:data bind:selected_question />
|
||||
{/if}
|
||||
<span>Save</span>
|
||||
<svg
|
||||
class="w-6 h-6 inline-block"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M8 7H5a2 2 0 00-2 2v9a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-3m-1 4l-3 3m0 0l-3-3m3 3V4"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="w-full h-full">
|
||||
{#if selected_question === -1}
|
||||
<SettingsCard bind:data />
|
||||
{:else}
|
||||
<QuizCard bind:data bind:selected_question bind:edit_id />
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
import CheckerBg from '$lib/editor/checker-bg.svg';
|
||||
import { reach } from 'yup';
|
||||
import { dataSchema } from '$lib/yupSchemas';
|
||||
import Spinner from '../Spinner.svelte';
|
||||
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
export let edit_id: string;
|
||||
|
||||
let question = data.questions[selected_question];
|
||||
$: question = data.questions[selected_question];
|
||||
@@ -13,11 +15,14 @@
|
||||
right: false,
|
||||
answer: ''
|
||||
};
|
||||
console.log(question.image);
|
||||
let uppyOpen = false;
|
||||
$: console.log(uppyOpen);
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full pb-20 px-20">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 drop-shadow-2xl">
|
||||
<div class="h-fit bg-gray-300 rounded-t-lg">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 drop-shadow-2xl dark:bg-gray-700">
|
||||
<div class="h-fit bg-gray-300 rounded-t-lg dark:bg-gray-500">
|
||||
<div class="flex align-middle p-4 gap-3">
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
|
||||
@@ -36,15 +41,28 @@
|
||||
type="text"
|
||||
bind:value={question.question}
|
||||
placeholder="No title..."
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-2/3 text-lg font-semibold placeholder:italic placeholder:font-normal"
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-2/3 text-lg font-semibold placeholder:italic placeholder:font-normal dark:bg-gray-500"
|
||||
class:bg-yellow-500={!reach(dataSchema, 'questions[].question').isValidSync(
|
||||
question.question
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
|
||||
<img src={question.image} alt="not available" class="max-h-72 h-auto w-auto" />
|
||||
</div>
|
||||
{#if question.image != undefined && question.image !== ''}
|
||||
<div class="flex justify-center pt-10 w-full max-h-72 w-full">
|
||||
<img src={question.image} alt="not available" class="max-h-72 h-auto w-auto" />
|
||||
</div>
|
||||
{:else}
|
||||
{#await import('$lib/editor/uploader.svelte')}
|
||||
<Spinner />
|
||||
{:then c}
|
||||
<svelte:component
|
||||
this={c.default}
|
||||
bind:modalOpen={uppyOpen}
|
||||
bind:edit_id
|
||||
bind:data
|
||||
/>
|
||||
{/await}
|
||||
{/if}
|
||||
<div class="flex justify-center pt-10 w-full">
|
||||
<div class="grid grid-cols-2 gap-4 w-full px-10">
|
||||
{#each question.answers as answer, index}
|
||||
@@ -109,8 +127,7 @@
|
||||
{/each}
|
||||
{#if question.answers.length < 4}
|
||||
<button
|
||||
class="p-4 rounded-lg bg-transparent"
|
||||
style="background-image: url({CheckerBg})"
|
||||
class="p-4 rounded-lg bg-transparent border-gray-500 border-2"
|
||||
type="button"
|
||||
on:click={() => {
|
||||
question.answers = [...question.answers, { empty_answer }];
|
||||
@@ -125,3 +142,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#if uppyOpen}
|
||||
<span class="fixed w-screen h-screen bg-opacity-60 z-10">1</span>
|
||||
{/if}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
</script>
|
||||
|
||||
<div class="w-full h-full pb-20 px-20">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500">
|
||||
<div class="h-fit bg-gray-300 rounded-t-lg">
|
||||
<div class="rounded-lg bg-white w-full h-full border-gray-500 dark:bg-gray-700">
|
||||
<div class="h-fit bg-gray-300 rounded-t-lg dark:bg-gray-500">
|
||||
<div class="flex align-middle p-4 gap-3">
|
||||
<span
|
||||
class="inline-block bg-gray-600 w-4 h-4 rounded-full hover:bg-red-400 transition"
|
||||
@@ -19,19 +19,19 @@
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="dark:bg-gray-700">
|
||||
<div class="flex justify-center pt-10 w-full">
|
||||
<input
|
||||
type="text"
|
||||
bind:value={data.title}
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 text-lg font-semibold"
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 text-lg font-semibold dark:bg-gray-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="flex justify-center pt-10 w-full max-h-32">
|
||||
<textarea
|
||||
type="text"
|
||||
bind:value={data.description}
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none"
|
||||
class="p-3 rounded-lg border-gray-500 border text-center w-1/3 h-20 resize-none dark:bg-gray-500"
|
||||
/>
|
||||
</div>
|
||||
<div class="pt-10">
|
||||
|
||||
@@ -45,9 +45,10 @@
|
||||
|
||||
<div class="h-screen border-r-2 pt-6 px-6 overflow-scroll">
|
||||
<div
|
||||
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500"
|
||||
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600"
|
||||
bind:this={propertyCard}
|
||||
class:bg-green-300={selected_question === -1}
|
||||
class:dark:bg-green-500={selected_question === -1}
|
||||
on:click={() => setSelectedQuestion(-1)}
|
||||
>
|
||||
<div
|
||||
@@ -59,7 +60,7 @@
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
class="whitespace-nowrap truncate text-center w-full bg-transparent rounded font-semibold"
|
||||
class="whitespace-nowrap truncate text-center w-full bg-transparent rounded font-semibold dark:text-black"
|
||||
bind:value={data.title}
|
||||
/>
|
||||
</div>
|
||||
@@ -72,10 +73,10 @@
|
||||
>
|
||||
<textarea
|
||||
bind:value={data.description}
|
||||
class="bg-transparent resize-none w-full rounded text-sm"
|
||||
class="bg-transparent resize-none w-full rounded text-sm dark:text-black"
|
||||
/>
|
||||
</div>
|
||||
<div class="w-full flex justify-center">
|
||||
<div class="w-full flex justify-center dark:text-black">
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
@@ -121,8 +122,13 @@
|
||||
</div>
|
||||
{#each data.questions as question, index}
|
||||
<div
|
||||
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500"
|
||||
class="bg-white shadow rounded-lg h-40 p-2 mb-6 hover:cursor-pointer drop-shadow-2xl border border-gray-500 dark:bg-gray-600"
|
||||
class:bg-green-300={index === selected_question}
|
||||
class:dark:bg-green-500={index === selected_question}
|
||||
on:contextmenu|preventDefault={() => {
|
||||
data.questions.splice(index, 1);
|
||||
data.questions = data.questions;
|
||||
}}
|
||||
on:click={() => {
|
||||
setSelectedQuestion(index);
|
||||
}}
|
||||
@@ -133,7 +139,7 @@
|
||||
class="m-1 border border-gray-500 rounded-lg p-0.5"
|
||||
>
|
||||
<h1
|
||||
class="whitespace-nowrap truncate text-center rounded-lg"
|
||||
class="whitespace-nowrap truncate text-center rounded-lg dark:text-black"
|
||||
class:bg-yellow-500={!reach(dataSchema, 'questions[].question').isValidSync(
|
||||
question.question
|
||||
)}
|
||||
@@ -184,7 +190,7 @@
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
class="h-full flex justify-center w-full"
|
||||
class="h-full flex justify-center w-full dark:text-black"
|
||||
on:click={() => {
|
||||
data.questions = [...data.questions, { ...empty_question }];
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
<script lang="ts">
|
||||
import { Dashboard as SvelteDashboard } from '@uppy/svelte';
|
||||
import Uppy from '@uppy/core';
|
||||
import DropTarget from '@uppy/drop-target';
|
||||
import XHRUpload from '@uppy/xhr-upload';
|
||||
import ImageEditor from '@uppy/image-editor';
|
||||
import Dashboard from '@uppy/dashboard';
|
||||
import Compressor from '@uppy/compressor';
|
||||
|
||||
// CSS imports
|
||||
import '@uppy/core/dist/style.css';
|
||||
import '@uppy/dashboard/dist/style.css';
|
||||
import '@uppy/drop-target/dist/style.css';
|
||||
// import '@uppy/file-input/dist/style.css'
|
||||
import '@uppy/image-editor/dist/style.css';
|
||||
import type { EditorData } from '../quiz_types';
|
||||
|
||||
export let modalOpen = false;
|
||||
export let edit_id: string;
|
||||
export let data: EditorData;
|
||||
export let selected_question: number;
|
||||
|
||||
const uppy = new Uppy()
|
||||
.use(DropTarget, {
|
||||
target: document.body
|
||||
})
|
||||
.use(Dashboard)
|
||||
.use(ImageEditor, {
|
||||
target: Dashboard,
|
||||
quality: 0.8
|
||||
})
|
||||
.use(Compressor, {
|
||||
quality: 0.6
|
||||
})
|
||||
.use(XHRUpload, {
|
||||
endpoint: `/api/v1/editor/image?edit_id=${edit_id}`
|
||||
});
|
||||
const props = { inline: true };
|
||||
let image_id;
|
||||
uppy.on('upload-success', (file, response) => {
|
||||
image_id = response.body.id;
|
||||
});
|
||||
uppy.on('complete', (res) => {
|
||||
data.questions[
|
||||
selected_question
|
||||
].image = `https://${window.location.hostname}/api/v1/storage/download/${image_id}`;
|
||||
modalOpen = false;
|
||||
});
|
||||
console.log(edit_id);
|
||||
</script>
|
||||
|
||||
{#if modalOpen}
|
||||
<div class="w-full h-full absolute top-0 left-0 bg-opacity-60 z-20 flex justify-center">
|
||||
<div>
|
||||
<button
|
||||
type="button"
|
||||
on:click={() => {
|
||||
modalOpen = false;
|
||||
}}>Close</button
|
||||
>
|
||||
<div>
|
||||
<SvelteDashboard {uppy} width="100%" {props} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<button
|
||||
on:click={() => {
|
||||
modalOpen = true;
|
||||
}}>Add Image</button
|
||||
>
|
||||
{/if}
|
||||
@@ -50,6 +50,7 @@
|
||||
|
||||
let data: Data;
|
||||
let confirm_to_leave = true;
|
||||
let quiz_id = null;
|
||||
onMount(() => {
|
||||
const from_localstorage = localStorage.getItem('create_game');
|
||||
if (from_localstorage === null) {
|
||||
@@ -100,7 +101,7 @@
|
||||
|
||||
{#if data !== undefined}
|
||||
<form on:submit|preventDefault={submit} class="grid grid-cols-1 gap-2">
|
||||
<Editor bind:data />
|
||||
<Editor bind:data bind:quiz_id />
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@
|
||||
{:then _}
|
||||
{#if data !== undefined}
|
||||
<form on:submit|preventDefault={submit} class="grid grid-cols-1 gap-2">
|
||||
<Editor bind:data submit_button_text={$t('words.save')} />
|
||||
<Editor bind:data submit_button_text={$t('words.save')} bind:quiz_id />
|
||||
</form>
|
||||
{/if}
|
||||
{:catch err}
|
||||
|
||||
Reference in New Issue
Block a user