feat(submissions): crop management
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { ref, onMounted } from "vue";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { submissionsApiCreateSubmission, submissionsApiListUserSubmissions } from "@/api";
|
||||
import { submissionsApiCreateSubmission, submissionsApiListUserSubmissions, submissionsApiCreateImageCrop, submissionsApiDeleteImageCrop } from "@/api";
|
||||
|
||||
interface Props {
|
||||
gamePath?: string;
|
||||
@@ -32,8 +32,13 @@ const userSubmissions = ref<any[]>([]);
|
||||
const isLoadingSubmissions = ref(false);
|
||||
const selectedSubmissionFile = ref<any>(null);
|
||||
const isSubmissionFileModalOpen = ref(false);
|
||||
const imageAspectRatio = ref<"portrait" | "landscape">("landscape");
|
||||
const copyFeedback = ref<string>("");
|
||||
const isDrawingCrop = ref(false);
|
||||
const cropStartX = ref(0);
|
||||
const cropStartY = ref(0);
|
||||
const currentCrop = ref<{ x1: number; y1: number; x2: number; y2: number } | null>(null);
|
||||
const cropLabel = ref("");
|
||||
const isCreatingCrop = ref(false);
|
||||
|
||||
const openFilePreview = async (file: File) => {
|
||||
selectedFile.value = file;
|
||||
@@ -65,12 +70,50 @@ const closeModal = () => {
|
||||
const openSubmissionFileModal = (file: any) => {
|
||||
selectedSubmissionFile.value = file;
|
||||
isSubmissionFileModalOpen.value = true;
|
||||
currentCrop.value = null;
|
||||
cropLabel.value = "";
|
||||
|
||||
// Check image dimensions for aspect ratio
|
||||
if (file?.content_type?.startsWith("image/")) {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
imageAspectRatio.value = img.height > img.width ? "portrait" : "landscape";
|
||||
// Initialize canvas for crop drawing
|
||||
setTimeout(() => {
|
||||
const canvas = document.getElementById("cropCanvas") as HTMLCanvasElement;
|
||||
if (canvas && userInfo.value.isStaff) {
|
||||
const container = canvas.parentElement;
|
||||
if (container) {
|
||||
// Set canvas resolution to image size for accurate crop coordinates
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
|
||||
// Calculate scaling to fit container while maintaining aspect ratio
|
||||
const containerWidth = container.clientWidth;
|
||||
const containerHeight = container.clientHeight;
|
||||
const imgAspect = img.width / img.height;
|
||||
const containerAspect = containerWidth / containerHeight;
|
||||
|
||||
let displayWidth, displayHeight;
|
||||
if (imgAspect > containerAspect) {
|
||||
displayWidth = containerWidth;
|
||||
displayHeight = containerWidth / imgAspect;
|
||||
} else {
|
||||
displayHeight = containerHeight;
|
||||
displayWidth = containerHeight * imgAspect;
|
||||
}
|
||||
|
||||
// Apply CSS scaling to display the image at correct size
|
||||
canvas.style.width = displayWidth + "px";
|
||||
canvas.style.height = displayHeight + "px";
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (ctx) {
|
||||
ctx.drawImage(img, 0, 0);
|
||||
redrawCropCanvas();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0);
|
||||
};
|
||||
img.src = file.file_url;
|
||||
}
|
||||
@@ -98,6 +141,150 @@ const copyOCRText = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const getCanvasCoordinates = (canvas: HTMLCanvasElement, clientX: number, clientY: number) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const scaleX = canvas.width / rect.width;
|
||||
const scaleY = canvas.height / rect.height;
|
||||
const x = (clientX - rect.left) * scaleX;
|
||||
const y = (clientY - rect.top) * scaleY;
|
||||
return { x, y };
|
||||
};
|
||||
|
||||
const startCropDraw = (e: MouseEvent) => {
|
||||
const canvas = e.currentTarget as HTMLCanvasElement;
|
||||
const coords = getCanvasCoordinates(canvas, e.clientX, e.clientY);
|
||||
cropStartX.value = coords.x;
|
||||
cropStartY.value = coords.y;
|
||||
isDrawingCrop.value = true;
|
||||
};
|
||||
|
||||
const drawCrop = (e: MouseEvent) => {
|
||||
if (!isDrawingCrop.value) return;
|
||||
|
||||
const canvas = e.currentTarget as HTMLCanvasElement;
|
||||
const coords = getCanvasCoordinates(canvas, e.clientX, e.clientY);
|
||||
|
||||
currentCrop.value = {
|
||||
x1: Math.min(cropStartX.value, coords.x),
|
||||
y1: Math.min(cropStartY.value, coords.y),
|
||||
x2: Math.max(cropStartX.value, coords.x),
|
||||
y2: Math.max(cropStartY.value, coords.y),
|
||||
};
|
||||
|
||||
redrawCropCanvas();
|
||||
};
|
||||
|
||||
const endCropDraw = () => {
|
||||
isDrawingCrop.value = false;
|
||||
};
|
||||
|
||||
const redrawCropCanvas = () => {
|
||||
const canvas = document.getElementById("cropCanvas") as HTMLCanvasElement;
|
||||
if (!canvas) return;
|
||||
|
||||
const ctx = canvas.getContext("2d");
|
||||
if (!ctx) return;
|
||||
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.drawImage(img, 0, 0);
|
||||
|
||||
if (currentCrop.value) {
|
||||
ctx.fillStyle = "rgba(59, 130, 246, 0.1)";
|
||||
ctx.fillRect(
|
||||
currentCrop.value.x1,
|
||||
currentCrop.value.y1,
|
||||
currentCrop.value.x2 - currentCrop.value.x1,
|
||||
currentCrop.value.y2 - currentCrop.value.y1
|
||||
);
|
||||
ctx.strokeStyle = "#3b82f6";
|
||||
ctx.lineWidth = 2;
|
||||
ctx.strokeRect(
|
||||
currentCrop.value.x1,
|
||||
currentCrop.value.y1,
|
||||
currentCrop.value.x2 - currentCrop.value.x1,
|
||||
currentCrop.value.y2 - currentCrop.value.y1
|
||||
);
|
||||
}
|
||||
|
||||
selectedSubmissionFile.value?.crops?.forEach((crop: any) => {
|
||||
ctx.fillStyle = "rgba(34, 197, 94, 0.1)";
|
||||
ctx.fillRect(
|
||||
crop.x1,
|
||||
crop.y1,
|
||||
crop.x2 - crop.x1,
|
||||
crop.y2 - crop.y1
|
||||
);
|
||||
ctx.strokeStyle = "#22c55e";
|
||||
ctx.lineWidth = 1;
|
||||
ctx.strokeRect(
|
||||
crop.x1,
|
||||
crop.y1,
|
||||
crop.x2 - crop.x1,
|
||||
crop.y2 - crop.y1
|
||||
);
|
||||
});
|
||||
};
|
||||
img.src = selectedSubmissionFile.value?.file_url;
|
||||
};
|
||||
|
||||
const submitCrop = async () => {
|
||||
if (!currentCrop.value || !selectedSubmissionFile.value) return;
|
||||
|
||||
isCreatingCrop.value = true;
|
||||
try {
|
||||
const { data: newCrop } = await submissionsApiCreateImageCrop({
|
||||
path: { submission_file_id: selectedSubmissionFile.value.id },
|
||||
body: {
|
||||
x1: Math.round(currentCrop.value.x1),
|
||||
y1: Math.round(currentCrop.value.y1),
|
||||
x2: Math.round(currentCrop.value.x2),
|
||||
y2: Math.round(currentCrop.value.y2),
|
||||
label: cropLabel.value || null,
|
||||
},
|
||||
});
|
||||
|
||||
// Add the new crop to the list immediately
|
||||
if (newCrop) {
|
||||
selectedSubmissionFile.value.crops = selectedSubmissionFile.value.crops || [];
|
||||
selectedSubmissionFile.value.crops.push(newCrop);
|
||||
}
|
||||
|
||||
currentCrop.value = null;
|
||||
cropLabel.value = "";
|
||||
redrawCropCanvas();
|
||||
} catch (error) {
|
||||
console.error("Error creating crop:", error);
|
||||
alert(`Error creating crop: ${error instanceof Error ? error.message : "Please try again."}`);
|
||||
} finally {
|
||||
isCreatingCrop.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteCrop = async (cropId: string) => {
|
||||
if (!confirm("Delete this crop?")) return;
|
||||
|
||||
try {
|
||||
await submissionsApiDeleteImageCrop({
|
||||
path: { crop_id: cropId },
|
||||
});
|
||||
selectedSubmissionFile.value.crops = selectedSubmissionFile.value.crops.filter(
|
||||
(c: any) => c.id !== cropId
|
||||
);
|
||||
redrawCropCanvas();
|
||||
} catch (error) {
|
||||
console.error("Error deleting crop:", error);
|
||||
alert(`Error deleting crop: ${error instanceof Error ? error.message : "Please try again."}`);
|
||||
}
|
||||
};
|
||||
|
||||
const cancelCrop = () => {
|
||||
currentCrop.value = null;
|
||||
cropLabel.value = "";
|
||||
redrawCropCanvas();
|
||||
};
|
||||
|
||||
const getGameHeaderImage = () => {
|
||||
if (props.gameImage) {
|
||||
return props.gameImage;
|
||||
@@ -533,22 +720,26 @@ onMounted(() => {
|
||||
|
||||
<!-- Submission File Modal -->
|
||||
<div :class="['modal', { 'modal-open': isSubmissionFileModalOpen }]">
|
||||
<div class="modal-box w-11/12 max-w-6xl max-h-[90vh] overflow-hidden">
|
||||
<div class="modal-box w-11/12 max-w-7xl max-h-[95vh] overflow-hidden flex flex-col">
|
||||
<h3 class="font-bold text-lg mb-4">
|
||||
<i :class="['mdi mr-2', getFileIcon({ type: selectedSubmissionFile?.content_type })]"></i>
|
||||
{{ selectedSubmissionFile?.original_filename }}
|
||||
</h3>
|
||||
|
||||
<!-- Responsive Layout: Portrait images side-by-side (left/right), landscape images and other files stacked (top/bottom) -->
|
||||
<div :class="['gap-4 h-[70vh]', imageAspectRatio === 'portrait' ? 'grid grid-cols-1 lg:grid-cols-2' : 'flex flex-col']">
|
||||
<!-- Layout: File Preview on top, OCR and Crops side by side below -->
|
||||
<div class="gap-4 h-[80vh] w-full overflow-hidden flex flex-col">
|
||||
<!-- File Display -->
|
||||
<div class="flex flex-col overflow-hidden" :class="imageAspectRatio === 'portrait' ? 'h-full' : 'flex-1'">
|
||||
<div class="flex flex-col overflow-hidden flex-[1.5]">
|
||||
<p class="text-sm text-base-content/70 mb-2">File Preview</p>
|
||||
<div class="flex-1 overflow-auto border border-base-300 rounded-lg">
|
||||
<!-- Image Preview -->
|
||||
<!-- Image Preview with Crop Drawing (Admin Only) -->
|
||||
<div v-if="selectedSubmissionFile?.content_type.startsWith('image/')"
|
||||
class="flex items-center justify-center h-full p-4">
|
||||
<img :src="selectedSubmissionFile.file_url" :alt="selectedSubmissionFile.original_filename"
|
||||
<canvas v-if="userInfo.isStaff" id="cropCanvas" @mousedown="startCropDraw" @mousemove="drawCrop"
|
||||
@mouseup="endCropDraw" @mouseleave="endCropDraw"
|
||||
class="border border-dashed border-blue-400 cursor-crosshair"
|
||||
style="max-height: 100%; max-width: 100%;"></canvas>
|
||||
<img v-else :src="selectedSubmissionFile.file_url" :alt="selectedSubmissionFile.original_filename"
|
||||
class="max-w-full max-h-full object-contain rounded" />
|
||||
</div>
|
||||
|
||||
@@ -573,60 +764,113 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2">
|
||||
<a v-if="selectedSubmissionFile?.file_url" :href="selectedSubmissionFile.file_url" download
|
||||
class="btn btn-sm btn-outline w-full">
|
||||
<i class="mdi mdi-download mr-2"></i>
|
||||
Download File
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- OCR Data -->
|
||||
<div class="flex flex-col overflow-hidden" :class="imageAspectRatio === 'portrait' ? 'h-full' : 'flex-1'">
|
||||
<p class="text-sm text-base-content/70 mb-2">OCR Data</p>
|
||||
<div class="flex-1 overflow-auto border border-base-300 rounded-lg p-4 bg-base-200">
|
||||
<div v-if="selectedSubmissionFile?.ocr_status === 'processed' && selectedSubmissionFile.ocr_raw_data">
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-base-content/70 mb-1">Status:</p>
|
||||
<span :class="getOCRStatusColor(selectedSubmissionFile.ocr_status)">{{
|
||||
selectedSubmissionFile.ocr_status }}</span>
|
||||
<!-- OCR Data and Crops Data Row -->
|
||||
<div class="gap-4 w-full overflow-hidden flex flex-row flex-1">
|
||||
<!-- OCR Data -->
|
||||
<div class="flex flex-col overflow-hidden flex-1">
|
||||
<p class="text-sm text-base-content/70 mb-2">OCR Data</p>
|
||||
<div class="flex-1 overflow-auto border border-base-300 rounded-lg p-4 bg-base-200">
|
||||
<div v-if="selectedSubmissionFile?.ocr_status === 'processed' && selectedSubmissionFile.ocr_raw_data">
|
||||
<div class="space-y-3">
|
||||
<div>
|
||||
<p class="text-xs font-semibold text-base-content/70 mb-1">Status:</p>
|
||||
<span :class="getOCRStatusColor(selectedSubmissionFile.ocr_status)">{{
|
||||
selectedSubmissionFile.ocr_status }}</span>
|
||||
</div>
|
||||
<div class="divider my-2"></div>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p class="text-xs font-semibold text-base-content/70">Extracted Text:</p>
|
||||
<button @click="copyOCRText" class="btn btn-xs btn-ghost gap-1">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
{{ copyFeedback || "Copy" }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="bg-base-100 p-3 rounded text-xs whitespace-pre-wrap break-words font-mono overflow-y-auto h-full">
|
||||
{{ selectedSubmissionFile.ocr_raw_data }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider my-2"></div>
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p class="text-xs font-semibold text-base-content/70">Extracted Text:</p>
|
||||
<button @click="copyOCRText" class="btn btn-xs btn-ghost gap-1">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
{{ copyFeedback || "Copy" }}
|
||||
</div>
|
||||
<div v-else-if="selectedSubmissionFile?.ocr_status === 'processing'"
|
||||
class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
<p class="text-base-content/70 mt-4">OCR is being processed...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="selectedSubmissionFile?.ocr_status === 'error'"
|
||||
class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<i class="mdi mdi-alert-circle text-4xl text-error mb-2"></i>
|
||||
<p class="text-base-content/70">OCR processing failed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<i class="mdi mdi-clock-outline text-4xl text-base-content/30 mb-2"></i>
|
||||
<p class="text-base-content/70">Waiting for OCR processing...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Crops Data -->
|
||||
<div v-if="userInfo.isStaff" class="flex flex-col overflow-hidden flex-1">
|
||||
<p class="text-sm text-base-content/70 mb-2">Crops Data</p>
|
||||
<div class="flex-1 overflow-auto border border-base-300 rounded-lg p-4 bg-base-100 flex flex-col gap-4">
|
||||
<!-- Crop Drawing Hints -->
|
||||
<div>
|
||||
<p class="text-xs text-base-content/50 text-center mb-2">
|
||||
<i class="mdi mdi-information-outline"></i>
|
||||
Drag on image to create crop (blue) or existing crops (green)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Crop Form -->
|
||||
<div v-if="currentCrop" class="p-2 rounded border border-blue-200">
|
||||
<div class="space-y-2">
|
||||
<div class="form-control">
|
||||
<label class="label">
|
||||
<span class="label-text text-sm">Crop Label (optional)</span>
|
||||
</label>
|
||||
<input v-model="cropLabel" type="text" placeholder="e.g., Top-left corner"
|
||||
class="input input-sm input-bordered" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<button @click="submitCrop" :disabled="isCreatingCrop" class="btn btn-sm btn-primary flex-1">
|
||||
<i v-if="!isCreatingCrop" class="mdi mdi-content-save"></i>
|
||||
{{ isCreatingCrop ? "Creating..." : "Save Crop" }}
|
||||
</button>
|
||||
<button @click="cancelCrop" class="btn btn-sm btn-ghost flex-1">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Crops List -->
|
||||
<div v-if="selectedSubmissionFile?.crops?.length" class="flex-1 overflow-y-auto">
|
||||
<p class="text-xs font-semibold text-base-content/70 mb-2">Crops ({{
|
||||
selectedSubmissionFile.crops.length }}):</p>
|
||||
<div class="space-y-1">
|
||||
<div v-for="crop in selectedSubmissionFile.crops" :key="crop.id"
|
||||
class="flex items-center justify-between p-1 rounded border border-green-200 text-xs">
|
||||
<div class="flex-1 min-w-0">
|
||||
<p class="font-medium truncate">{{ crop.label || `Crop (${crop.x1}, ${crop.y1})` }}</p>
|
||||
<p class="text-xs text-base-content/50">
|
||||
<span :class="getOCRStatusColor(crop.ocr_status)">{{ crop.ocr_status }}</span>
|
||||
</p>
|
||||
</div>
|
||||
<button @click="deleteCrop(crop.id)" class="btn btn-xs btn-ghost text-error flex-shrink-0">
|
||||
<i class="mdi mdi-delete"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="bg-base-100 p-3 rounded text-xs whitespace-pre-wrap break-words font-mono overflow-y-auto h-full">
|
||||
{{ selectedSubmissionFile.ocr_raw_data }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="selectedSubmissionFile?.ocr_status === 'processing'"
|
||||
class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<span class="loading loading-spinner loading-lg"></span>
|
||||
<p class="text-base-content/70 mt-4">OCR is being processed...</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="selectedSubmissionFile?.ocr_status === 'error'"
|
||||
class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<i class="mdi mdi-alert-circle text-4xl text-error mb-2"></i>
|
||||
<p class="text-base-content/70">OCR processing failed</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="flex items-center justify-center h-full">
|
||||
<div class="text-center">
|
||||
<i class="mdi mdi-clock-outline text-4xl text-base-content/30 mb-2"></i>
|
||||
<p class="text-base-content/70">Waiting for OCR processing...</p>
|
||||
<div v-else class="text-center text-xs text-base-content/50 py-4">
|
||||
No crops yet
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -634,6 +878,11 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="modal-action mt-4">
|
||||
<a v-if="selectedSubmissionFile?.file_url" :href="selectedSubmissionFile.file_url" download
|
||||
class="btn btn-outline">
|
||||
<i class="mdi mdi-download mr-2"></i>
|
||||
Download
|
||||
</a>
|
||||
<button @click="closeSubmissionFileModal" class="btn">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// This file is auto-generated by @hey-api/openapi-ts
|
||||
|
||||
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListGames, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateSubmission, submissionsApiGetSubmission, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions } from './sdk.gen';
|
||||
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, Input, LeaderboardEntryOut, LeaderboardOut, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResultsOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
|
||||
export { animationsApiPuzzleResults, animationsApiResults, animationsApiTopSubmissions, gamesApiListGames, noitaApiGetLeaderboard, noitaApiGetResults, noitaApiSubmitLogFile, type Options, opusMagnumApiCreateSubmission, opusMagnumApiDeleteSubmission, opusMagnumApiGetCollection, opusMagnumApiGetStats, opusMagnumApiGetSubmission, opusMagnumApiListPuzzles, opusMagnumApiListResponsesNeedingValidation, opusMagnumApiListSubmissions, opusMagnumApiValidateAuto, opusMagnumApiValidateResponse, opusMagnumApiValidateSubmission, polylanSubmitterApiClearCache, polylanSubmitterApiGetUserInfo, polylanSubmitterApiHealthCheck, submissionsApiCreateImageCrop, submissionsApiCreateSubmission, submissionsApiDeleteImageCrop, submissionsApiGetSubmission, submissionsApiListSubmissionsByGame, submissionsApiListUserSubmissions } from './sdk.gen';
|
||||
export type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponse, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponse, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponse, AnimationsApiTopSubmissionsResponses, ClientOptions, GameOut, GamesApiListGamesData, GamesApiListGamesResponse, GamesApiListGamesResponses, ImageCropIn, ImageCropOut, Input, LeaderboardEntryOut, LeaderboardOut, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponse, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponse, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileError, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponse, NoitaApiSubmitLogFileResponses, NoitaSubmissionOut, ObjectivResultOut, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponse, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponse, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponse, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponse, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponse, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponse, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponse, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponse, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponse, OpusMagnumApiValidateSubmissionResponses, PagedSubmissionOut, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponse, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, PuzzlePointsFactorOut, PuzzleResponseIn, PuzzleResponseOut, PuzzleResponseRankingOut, PuzzleResultsOut, PuzzleSubmissionsOut, PuzzleSubmissionWithRankOut, RankingSchema, ResultsOut, SteamCollectionItemOut, SteamCollectionOut, SubmissionFileOut, SubmissionIn, SubmissionOut, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponse, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponse, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponse, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponse, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponse, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponse, SubmissionsApiListUserSubmissionsResponses, TournamentPuzzleResultsOut, TournamentSubmissionsOut, UserDisplayOut, UserInfoOut, ValidationIn, WinnerFileOut, WinnerResponseOut } from './types.gen';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import { type Client, formDataBodySerializer, type Options as Options2, type TDataShape } from './client';
|
||||
import { client } from './client.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, GamesApiListGamesData, GamesApiListGamesResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponses } from './types.gen';
|
||||
import type { AnimationsApiPuzzleResultsData, AnimationsApiPuzzleResultsResponses, AnimationsApiResultsData, AnimationsApiResultsResponses, AnimationsApiTopSubmissionsData, AnimationsApiTopSubmissionsResponses, GamesApiListGamesData, GamesApiListGamesResponses, NoitaApiGetLeaderboardData, NoitaApiGetLeaderboardResponses, NoitaApiGetResultsData, NoitaApiGetResultsResponses, NoitaApiSubmitLogFileData, NoitaApiSubmitLogFileErrors, NoitaApiSubmitLogFileResponses, OpusMagnumApiCreateSubmissionData, OpusMagnumApiCreateSubmissionResponses, OpusMagnumApiDeleteSubmissionData, OpusMagnumApiDeleteSubmissionResponses, OpusMagnumApiGetCollectionData, OpusMagnumApiGetCollectionResponses, OpusMagnumApiGetStatsData, OpusMagnumApiGetStatsResponses, OpusMagnumApiGetSubmissionData, OpusMagnumApiGetSubmissionResponses, OpusMagnumApiListPuzzlesData, OpusMagnumApiListPuzzlesResponses, OpusMagnumApiListResponsesNeedingValidationData, OpusMagnumApiListResponsesNeedingValidationResponses, OpusMagnumApiListSubmissionsData, OpusMagnumApiListSubmissionsResponses, OpusMagnumApiValidateAutoData, OpusMagnumApiValidateAutoResponses, OpusMagnumApiValidateResponseData, OpusMagnumApiValidateResponseResponses, OpusMagnumApiValidateSubmissionData, OpusMagnumApiValidateSubmissionResponses, PolylanSubmitterApiClearCacheData, PolylanSubmitterApiClearCacheResponses, PolylanSubmitterApiGetUserInfoData, PolylanSubmitterApiGetUserInfoResponses, PolylanSubmitterApiHealthCheckData, PolylanSubmitterApiHealthCheckResponses, SubmissionsApiCreateImageCropData, SubmissionsApiCreateImageCropResponses, SubmissionsApiCreateSubmissionData, SubmissionsApiCreateSubmissionResponses, SubmissionsApiDeleteImageCropData, SubmissionsApiDeleteImageCropResponses, SubmissionsApiGetSubmissionData, SubmissionsApiGetSubmissionResponses, SubmissionsApiListSubmissionsByGameData, SubmissionsApiListSubmissionsByGameResponses, SubmissionsApiListUserSubmissionsData, SubmissionsApiListUserSubmissionsResponses } from './types.gen';
|
||||
|
||||
export type Options<TData extends TDataShape = TDataShape, ThrowOnError extends boolean = boolean, TResponse = unknown> = Options2<TData, ThrowOnError, TResponse> & {
|
||||
/**
|
||||
@@ -196,7 +196,7 @@ export const gamesApiListGames = <ThrowOnError extends boolean = false>(options?
|
||||
*/
|
||||
export const submissionsApiCreateSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiCreateSubmissionData, ThrowOnError>) => (options.client ?? client).post<SubmissionsApiCreateSubmissionResponses, unknown, ThrowOnError>({
|
||||
...formDataBodySerializer,
|
||||
url: '/api/submissions/create',
|
||||
url: '/api/submissions/submission/create',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': null,
|
||||
@@ -207,14 +207,31 @@ export const submissionsApiCreateSubmission = <ThrowOnError extends boolean = fa
|
||||
/**
|
||||
* List User Submissions
|
||||
*/
|
||||
export const submissionsApiListUserSubmissions = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListUserSubmissionsData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListUserSubmissionsResponses, unknown, ThrowOnError>({ url: '/api/submissions/user', ...options });
|
||||
export const submissionsApiListUserSubmissions = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListUserSubmissionsData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListUserSubmissionsResponses, unknown, ThrowOnError>({ url: '/api/submissions/submission/by/user', ...options });
|
||||
|
||||
/**
|
||||
* List Submissions By Game
|
||||
*/
|
||||
export const submissionsApiListSubmissionsByGame = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListSubmissionsByGameData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListSubmissionsByGameResponses, unknown, ThrowOnError>({ url: '/api/submissions/game', ...options });
|
||||
export const submissionsApiListSubmissionsByGame = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiListSubmissionsByGameData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiListSubmissionsByGameResponses, unknown, ThrowOnError>({ url: '/api/submissions/submission/by/game', ...options });
|
||||
|
||||
/**
|
||||
* Get Submission
|
||||
*/
|
||||
export const submissionsApiGetSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiGetSubmissionData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiGetSubmissionResponses, unknown, ThrowOnError>({ url: '/api/submissions/{submission_id}', ...options });
|
||||
export const submissionsApiGetSubmission = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiGetSubmissionData, ThrowOnError>) => (options.client ?? client).get<SubmissionsApiGetSubmissionResponses, unknown, ThrowOnError>({ url: '/api/submissions/submission/{submission_id}', ...options });
|
||||
|
||||
/**
|
||||
* Create Image Crop
|
||||
*/
|
||||
export const submissionsApiCreateImageCrop = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiCreateImageCropData, ThrowOnError>) => (options.client ?? client).post<SubmissionsApiCreateImageCropResponses, unknown, ThrowOnError>({
|
||||
url: '/api/submissions/crops/s/{submission_file_id}',
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Delete Image Crop
|
||||
*/
|
||||
export const submissionsApiDeleteImageCrop = <ThrowOnError extends boolean = false>(options: Options<SubmissionsApiDeleteImageCropData, ThrowOnError>) => (options.client ?? client).delete<SubmissionsApiDeleteImageCropResponses, unknown, ThrowOnError>({ url: '/api/submissions/crops/{crop_id}', ...options });
|
||||
|
||||
@@ -371,6 +371,10 @@ export type SubmissionFileOut = {
|
||||
* Ocr Raw Data
|
||||
*/
|
||||
ocr_raw_data: string;
|
||||
/**
|
||||
* Crops
|
||||
*/
|
||||
crops?: Array<ImageCropOut>;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -912,6 +916,74 @@ export type GameOut = {
|
||||
image?: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* ImageCropOut
|
||||
*/
|
||||
export type ImageCropOut = {
|
||||
/**
|
||||
* Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* X1
|
||||
*/
|
||||
x1: number;
|
||||
/**
|
||||
* Y1
|
||||
*/
|
||||
y1: number;
|
||||
/**
|
||||
* X2
|
||||
*/
|
||||
x2: number;
|
||||
/**
|
||||
* Y2
|
||||
*/
|
||||
y2: number;
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
label: string;
|
||||
/**
|
||||
* Ocr Status
|
||||
*/
|
||||
ocr_status: string;
|
||||
/**
|
||||
* Ocr Raw Data
|
||||
*/
|
||||
ocr_raw_data: string;
|
||||
/**
|
||||
* Created At
|
||||
*/
|
||||
created_at: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* ImageCropIn
|
||||
*/
|
||||
export type ImageCropIn = {
|
||||
/**
|
||||
* X1
|
||||
*/
|
||||
x1: number;
|
||||
/**
|
||||
* Y1
|
||||
*/
|
||||
y1: number;
|
||||
/**
|
||||
* X2
|
||||
*/
|
||||
x2: number;
|
||||
/**
|
||||
* Y2
|
||||
*/
|
||||
y2: number;
|
||||
/**
|
||||
* Label
|
||||
*/
|
||||
label?: string | null;
|
||||
};
|
||||
|
||||
export type PolylanSubmitterApiHealthCheckData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
@@ -1341,7 +1413,7 @@ export type SubmissionsApiCreateSubmissionData = {
|
||||
*/
|
||||
game_path: string;
|
||||
};
|
||||
url: '/api/submissions/create';
|
||||
url: '/api/submissions/submission/create';
|
||||
};
|
||||
|
||||
export type SubmissionsApiCreateSubmissionResponses = {
|
||||
@@ -1362,7 +1434,7 @@ export type SubmissionsApiListUserSubmissionsData = {
|
||||
*/
|
||||
game_path: string;
|
||||
};
|
||||
url: '/api/submissions/user';
|
||||
url: '/api/submissions/submission/by/user';
|
||||
};
|
||||
|
||||
export type SubmissionsApiListUserSubmissionsResponses = {
|
||||
@@ -1385,7 +1457,7 @@ export type SubmissionsApiListSubmissionsByGameData = {
|
||||
*/
|
||||
game_path: string;
|
||||
};
|
||||
url: '/api/submissions/game';
|
||||
url: '/api/submissions/submission/by/game';
|
||||
};
|
||||
|
||||
export type SubmissionsApiListSubmissionsByGameResponses = {
|
||||
@@ -1408,7 +1480,7 @@ export type SubmissionsApiGetSubmissionData = {
|
||||
submission_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/submissions/{submission_id}';
|
||||
url: '/api/submissions/submission/{submission_id}';
|
||||
};
|
||||
|
||||
export type SubmissionsApiGetSubmissionResponses = {
|
||||
@@ -1419,3 +1491,49 @@ export type SubmissionsApiGetSubmissionResponses = {
|
||||
};
|
||||
|
||||
export type SubmissionsApiGetSubmissionResponse = SubmissionsApiGetSubmissionResponses[keyof SubmissionsApiGetSubmissionResponses];
|
||||
|
||||
export type SubmissionsApiCreateImageCropData = {
|
||||
body: ImageCropIn;
|
||||
path: {
|
||||
/**
|
||||
* Submission File Id
|
||||
*/
|
||||
submission_file_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/submissions/crops/s/{submission_file_id}';
|
||||
};
|
||||
|
||||
export type SubmissionsApiCreateImageCropResponses = {
|
||||
/**
|
||||
* OK
|
||||
*/
|
||||
200: ImageCropOut;
|
||||
};
|
||||
|
||||
export type SubmissionsApiCreateImageCropResponse = SubmissionsApiCreateImageCropResponses[keyof SubmissionsApiCreateImageCropResponses];
|
||||
|
||||
export type SubmissionsApiDeleteImageCropData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Crop Id
|
||||
*/
|
||||
crop_id: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/api/submissions/crops/{crop_id}';
|
||||
};
|
||||
|
||||
export type SubmissionsApiDeleteImageCropResponses = {
|
||||
/**
|
||||
* Response
|
||||
*
|
||||
* OK
|
||||
*/
|
||||
200: {
|
||||
[key: string]: unknown;
|
||||
};
|
||||
};
|
||||
|
||||
export type SubmissionsApiDeleteImageCropResponse = SubmissionsApiDeleteImageCropResponses[keyof SubmissionsApiDeleteImageCropResponses];
|
||||
|
||||
Reference in New Issue
Block a user