Compare commits

..
Author SHA1 Message Date
Michael Chung e4bbafeae6 Used Aspect Ratio to fix.
Prompt box handles both web and desktop.
2025-04-21 16:58:02 -04:00
bflatastic e39a7f0360 bug fix: asset modal flickers when items are drag and dropped too fast 2025-04-22 01:47:03 +07:00
bflatastic 5ea90154b1 bug fix: selecting object in scene crashes (Headless UI Transition component bug) 2025-04-22 01:29:51 +07:00
bflatastic f627da6d8f updated readme: ui component import 2025-04-22 01:19:30 +07:00
ArEnScandMichael Chung 682f11c79e Tauri and login for open ai (#540)
Co-authored-by: Michael Chung <michaelchung@michaels-mbp-2.home>
2025-04-21 14:15:14 -04:00
BFlat 0bdbb5214b settings modal (#539)
new shared components:
- button
- close button
- input
- label
- modal
- select
- settings modal
2025-04-22 01:00:58 +07:00
Brandon Thomas 94f2d2ec68 fix tauri build configs 2025-04-21 13:13:23 -04:00
Michael Chung 384b841528 Add a scale factor to the capture canvas
just use a hack to get it released
2025-04-21 12:55:02 -04:00
Michael Chung 62f97317ff Add component information into README 2025-04-21 09:23:30 -04:00
19 changed files with 309 additions and 223 deletions
@@ -1,37 +0,0 @@
{
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
"productName": "ArtCraft 2D",
"version": "0.1.0",
"identifier": "ai.artcraft.app",
"build": {
"frontendDist": "../../../frontend/apps/editor2d/dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "nx run editor2d:dev",
"beforeBuildCommand": "nx run editor2d:build"
},
"app": {
"windows": [
{
"title": "ArtCraft 2D",
"width": 2400,
"height": 1300,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
@@ -1,37 +0,0 @@
{
"$schema": "https://schema.tauri.app/config/2.0.0-rc",
"productName": "ArtCraft 3D",
"version": "0.1.0",
"identifier": "ai.artcraft.app",
"build": {
"frontendDist": "../../../frontend/apps/editor3d/dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "nx run editor3d:dev",
"beforeBuildCommand": "nx run editor3d:build"
},
"app": {
"windows": [
{
"title": "ArtCraft 3D",
"width": 2400,
"height": 1300,
"resizable": true,
"fullscreen": false
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}
@@ -0,0 +1,5 @@
# For some reason, we can't pass json files to the tauri dev command
[build]
frontendDist = "../../../frontend/apps/editor3d/dist"
beforeDevCommand = "nx run editor3d:dev"
devUrl = "http://localhost:5173"
@@ -4,10 +4,10 @@
"version": "0.1.0",
"identifier": "ai.artcraft.app",
"build": {
"frontendDist": "../../../frontend/apps/editor3d/dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "nx run editor3d:dev",
"beforeBuildCommand": "nx run editor3d:build"
"frontendDist": "../../../frontend/apps/editor2d/dist",
"devUrl": "http://localhost:5741",
"beforeDevCommand": "nx run editor2d:dev",
"beforeBuildCommand": "nx run editor2d:build"
},
"app": {
"windows": [
+15 -4
View File
@@ -2,8 +2,7 @@
This is an `nx` monorepo that can contain multiple apps and shared libraries.
All commands to run these projects are performed from _this_ directory.
All commands to run these projects are performed from _this_ directory.
## Install dependencies
@@ -35,6 +34,10 @@ The names for @frontend and @storyteller come from the package.json file in the
import { Login } from "@frontend/login";
import { api } from "@storyteller/api";
For shared UI components, import from @storyteller/ui-[componentname].
import { Button } from "@storyteller/ui-button";
import { Modal } from "@storyteller/ui-modal";
building:
npx nx build editor3d
npx nx build editor2d
@@ -42,6 +45,14 @@ npx nx build editor2d
dev:
npx nx dev editor3d
npx nx dev editor2d
# component build
1.
npx nx g @nx/react:library libs/components/toaster --import-path=@storyteller/ui-toaster --bundler=vite
npm install
2.import it first in code
3.nx sync
4.nx build
```
@@ -62,6 +62,7 @@ export class EditEngine {
this.editMode = EditModes.SELECT;
this.boardCanvasRef = boardCanvasRef;
this.stage = new Konva.Stage({
container: this.boardCanvasRef,
width: window.innerWidth,
@@ -238,13 +239,19 @@ export class EditEngine {
}
private onBoardCanvasResize() {
this.realTimeDrawEngine.updateCaptureCanvas(undefined, undefined);
this.realTimeDrawEngine.updateCaptureCanvas(0, 0);
console.log("onBoardCanvasResize Event called");
console.log("Board Canvas values below");
console.log(this.boardCanvasRef.offsetWidth);
console.log(this.boardCanvasRef.offsetHeight);
this.matteBox.updateSize({
boardCanvasSize: {
width: this.boardCanvasRef.offsetWidth,
height: this.boardCanvasRef.offsetHeight,
},
});
this.uiLayer.draw();
this.stage.width(this.boardCanvasRef.offsetWidth);
this.stage.height(this.boardCanvasRef.offsetHeight);
+14 -1
View File
@@ -510,7 +510,20 @@ export class Engine {
}
private onBoardCanvasResize() {
this.realTimeDrawEngine.updateCaptureCanvas(undefined, undefined);
console.log("onBoardCanvasResize Event called");
// Print window dimensions for debugging
console.log("Window dimensions:", {
innerWidth: window.innerWidth,
innerHeight: window.innerHeight,
outerWidth: window.outerWidth,
outerHeight: window.outerHeight,
});
this.realTimeDrawEngine.updateCaptureCanvas(
window.innerWidth,
window.innerHeight,
);
this.matteBox.updateSize({
boardCanvasSize: {
width: this.boardCanvasRef.offsetWidth,
@@ -516,36 +516,74 @@ export class RealTimeDrawEngine {
width: number | undefined,
height: number | undefined,
) {
console.log("updateCaptureCanvas");
if (!this.captureCanvas) {
return;
}
if (width) {
this.width = width;
}
if (height) {
this.height = height;
}
// Ensures that all the nodes stag in the same place should
// there be a window resize.
// recompute the position
// to ensure that the position of this stays
const scaleFactor = 0.9;
// Store old values for calculating delta
const oldPositionX = this.positionX;
const oldPositionY = this.positionY;
// recompute the position
const oldWidth = this.width;
const oldHeight = this.height;
// Original aspect ratio
const originalAspectRatio = oldWidth / oldHeight;
// Calculate new dimensions based on window size
let newWidth: number;
let newHeight: number;
if (width === undefined) {
newWidth = window.innerWidth * scaleFactor;
} else {
newWidth = width * scaleFactor;
}
if (height === undefined) {
newHeight = window.innerHeight * scaleFactor;
} else {
newHeight = height * scaleFactor;
}
// Preserve aspect ratio
const newAspectRatio = newWidth / newHeight;
if (newAspectRatio > originalAspectRatio) {
// Width is constraining factor
newWidth = newHeight * originalAspectRatio;
} else {
// Height is constraining factor
newHeight = newWidth / originalAspectRatio;
}
this.width = newWidth;
this.height = newHeight;
console.log("New dimensions:", this.width, this.height);
// Calculate new position to center the canvas in the window
this.positionX = (window.innerWidth - this.width) / 2;
this.positionY = (window.innerHeight - this.height) / 2;
console.log("New centered position:", this.positionX, this.positionY);
// Update the canvas position and size
this.captureCanvas.setPosition({
x: this.positionX / 2,
y: this.positionY / 2,
x: this.positionX,
y: this.positionY,
});
this.captureCanvas.size({ width: this.width, height: this.height });
// this is the change in positions
const deltaX = this.positionX - oldPositionX;
const deltaY = this.positionY - oldPositionY;
// Calculate scale factors to maintain proportions
const scaleX = this.width / oldWidth;
const scaleY = this.height / oldHeight;
// Use a uniform scale factor to preserve aspect ratio
const uniformScale = Math.min(scaleX, scaleY);
// Update all children except the capture canvas
var children = this.mediaLayerRef.getChildren();
for (let i = 0; i < children.length; i++) {
let node = children[i];
@@ -554,11 +592,27 @@ export class RealTimeDrawEngine {
if (node.name() === "CaptureCanvas") {
continue;
}
const pos = node.getPosition();
const oldScale = node.scale();
// Apply both position shift and scaling to maintain relative positioning
node.setPosition({
x: pos.x + deltaX,
y: pos.y + deltaY,
x: (pos.x - oldPositionX) * uniformScale + this.positionX,
y: (pos.y - oldPositionY) * uniformScale + this.positionY,
});
// Update scale proportionally if the node has a scale property
if (
oldScale &&
typeof oldScale.x === "number" &&
typeof oldScale.y === "number"
) {
node.scale({
x: oldScale.x * uniformScale,
y: oldScale.y * uniformScale,
});
}
}
this.mediaLayerRef.batchDraw();
@@ -1,6 +1,7 @@
import { useParams } from "react-router-dom";
import { withProtectionRoute } from "~/components/hoc";
import { useRenderCounter } from "~/hooks/useRenderCounter";
import { Resource, invoke } from '@tauri-apps/api/core';
// Components of the page
import { ToolbarUserProfile } from "~/components/features";
@@ -26,6 +27,11 @@ import {
} from "@fortawesome/pro-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export const isTauri = (): boolean => {
console.log(window.__TAURI_INTERNALS__);
return window.__TAURI_INTERNALS__;
};
export const Main = withProtectionRoute(() => {
// This is a hook that will log the number of times the component has rerendered
// Let's make sure we only log once
@@ -40,7 +46,7 @@ export const Main = withProtectionRoute(() => {
const realtimeEngineRef = useRef<EngineType | null>(null);
const generationEngineRef = useRef<GenerationEngine | null>(null);
const [firstTimeDialogOpen, setFirstTimeDialogOpen] = useState(true);
const [signinDialogOpen, setSigninDialogOpen] = useState(false);
const appModeValue = appMode.value;
let childView;
@@ -97,7 +103,7 @@ export const Main = withProtectionRoute(() => {
}}
className="max-w-5xl"
>
<div className="flex flex-col items-center justify-center gap-6">
<div className="flex flex-col items-center justify-center">
<div className="flex flex-col items-center gap-3">
<h1 className="text-3xl font-bold">
<FontAwesomeIcon
@@ -123,7 +129,22 @@ export const Main = withProtectionRoute(() => {
</video>
</div>
<Button
className="px-4 py-3 font-semibold"
className="font-semibold"
icon={faArrowRight}
iconFlip={true}
onClick={async () => {
if (isTauri()) {
await invoke('open_login_command');
setFirstTimeDialogOpen(false);
} else {
console.error('Tauri is not available in this environment');
}
}}
>
Signin to OpenAI to get Started
</Button>
{/* <Button
className="font-semibold"
icon={faArrowRight}
iconFlip={true}
onClick={() => {
@@ -131,7 +152,7 @@ export const Main = withProtectionRoute(() => {
}}
>
Start creating now
</Button>
</Button> */}
</div>
</BaseDialog>
</>
@@ -177,7 +177,100 @@ export const PromptBox = () => {
const handleChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => {
setPrompt(e.target.value);
};
const handleTauriEnqueue = async () => {
const api = new Api();
let image = getCanvasRenderBitmap();
if (image === undefined) {
return;
}
const base64Bitmap = await EncodeImageBitmapToBase64(image);
const byteString = atob(base64Bitmap);
const mimeString = "image/png";
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const uuid = crypto.randomUUID(); // Generate a new UUID
const file = new File([ab], `${uuid}.png`, { type: mimeString });
const snapshotMediaToken = await api.uploadSceneSnapshot({
screenshot: file,
});
if (snapshotMediaToken.data === undefined) {
toast.error("Error: Unable to upload scene snapshot Please try again.");
return;
}
console.log("useSystemPrompt", useSystemPrompt);
console.log("Snapshot media token:", snapshotMediaToken.data);
const generateResponse = await invoke("sora_image_generation_command", {
snapshot_media_token: snapshotMediaToken.data,
disable_system_prompt: !useSystemPrompt,
prompt: prompt,
maybe_additional_images: referenceImages.map((image) => image.mediaToken),
maybe_number_of_samples: 1,
});
toast.success("Please wait while we process your image.");
};
const handleWebEnqueue = async () => {
const api = new Api();
let image = getCanvasRenderBitmap();
if (image === undefined) {
toast.error(
"Error: Unable to generate image. Please check the input and try again.",
);
return;
}
const base64Bitmap = await EncodeImageBitmapToBase64(image);
const byteString = atob(base64Bitmap);
const mimeString = "image/png";
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const uuid = crypto.randomUUID(); // Generate a new UUID
const file = new File([ab], `${uuid}.png`, { type: mimeString });
const snapshotMediaToken = await api.uploadSceneSnapshot({
screenshot: file,
});
if (snapshotMediaToken.data === undefined) {
toast.error("Error: Unable to upload scene snapshot Please try again.");
return;
}
console.log("useSystemPrompt", useSystemPrompt);
console.log("Snapshot media token:", snapshotMediaToken.data);
const response = await api.enqueueImageGeneration({
disableSystemPrompt: !useSystemPrompt,
prompt: prompt,
snapshotMediaToken: snapshotMediaToken.data,
additionalImages: referenceImages.map((image) => image.mediaToken),
});
if (response.success === true) {
toast.success("Please wait while we process your image.");
if (response.data) {
addJobToken(response.data);
}
return;
} else {
toast.error("Failed to enqueue image generation. Please try again.");
}
};
const handleEnqueue = async () => {
if (!prompt.trim()) return;
@@ -191,69 +284,10 @@ export const PromptBox = () => {
referenceImages,
);
if (isDesktopApp) {
let image = getCanvasRenderBitmap();
if (image === undefined) {
return;
}
const base64Bitmap = await EncodeImageBitmapToBase64(image);
const generateResponse = await invoke("image_generation_command", {
image: base64Bitmap,
prompt: prompt,
});
await new Promise((resolve) => setTimeout(resolve, 2000));
}
if (!isDesktopApp) {
const api = new Api();
let image = getCanvasRenderBitmap();
if (image === undefined) {
toast.error(
"Error: Unable to generate image. Please check the input and try again.",
);
return;
}
const base64Bitmap = await EncodeImageBitmapToBase64(image);
const byteString = atob(base64Bitmap);
const mimeString = "image/png";
const ab = new ArrayBuffer(byteString.length);
const ia = new Uint8Array(ab);
for (let i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
const uuid = crypto.randomUUID(); // Generate a new UUID
const file = new File([ab], `${uuid}.png`, { type: mimeString });
const snapshotMediaToken = await api.uploadSceneSnapshot({
screenshot: file,
});
if (snapshotMediaToken.data === undefined) {
toast.error(
"Error: Unable to upload scene snapshot Please try again.",
);
return;
}
console.log("useSystemPrompt", useSystemPrompt);
console.log("Snapshot media token:", snapshotMediaToken.data);
const response = await api.enqueueImageGeneration({
disableSystemPrompt: !useSystemPrompt,
prompt: prompt,
snapshotMediaToken: snapshotMediaToken.data,
additionalImages: referenceImages.map((image) => image.mediaToken),
});
if (response.success === true) {
toast.success("Please wait while we process your image.");
if (response.data) {
addJobToken(response.data);
}
return;
} else {
toast.error("Failed to enqueue image generation. Please try again.");
}
if (isDesktopApp == true) {
handleTauriEnqueue();
} else {
handleWebEnqueue();
}
} catch (error) {
console.error("Error during image generation:", error);
@@ -59,14 +59,7 @@ class DndAsset {
this.overElement = null;
overTimeline.value = false;
this.notDropText = "";
if (reopenAfterDragSignal.value) {
assetModalVisibleDuringDrag.value = true;
} else {
assetModalVisibleDuringDrag.value = true;
setTimeout(() => {
assetModalVisibleDuringDrag.value = false;
}, 0);
}
assetModalVisibleDuringDrag.value = reopenAfterDragSignal.value;
}
}
@@ -120,7 +120,7 @@ export const PageEditor = () => {
onClick={handleOverlayClick}
>
<div
className="absolute bottom-0 mb-2 ml-2 flex origin-bottom-left flex-col gap-2"
className="absolute bottom-0 mb-4 ml-4 flex origin-bottom-left flex-col gap-2"
style={{ transform: `scale(${getScale()})` }}
>
<Outliner />
@@ -1,4 +1,4 @@
import { TransitionDialogue } from "~/components/reusable/TransitionDialogue";
import { Modal } from "@storyteller/ui-modal";
import { UploadModal3D } from "~/components/reusable/UploadModal3D";
import {
faPlus,
@@ -77,19 +77,21 @@ export const AssetModal = () => {
const [activeLibraryTab, setActiveLibraryTab] = useState("library");
const [activeAssetTab, setActiveAssetTab] = useState("all");
const [searchTerm, setSearchTerm] = useState("");
const [reopenAfterAdd, setReopenAfterAdd] = useState(false);
const [isUploadModalOpen, setIsUploadModalOpen] = useState(false);
const searchInputRef = useRef<HTMLInputElement>(null);
const handleReopenChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const newValue = e.target.checked;
// Force the update to be asynchronous
setTimeout(() => {
reopenAfterDragSignal.value = newValue;
}, 0);
};
const handleClose = () => {
assetModalVisible.value = false;
};
// Update the signal when the preference changes
useEffect(() => {
reopenAfterDragSignal.value = reopenAfterAdd;
}, [reopenAfterAdd]);
useEffect(() => {
if (assetModalVisible.value) {
// Check for stored category and tab from upload
@@ -391,7 +393,7 @@ export const AssetModal = () => {
};
const handleUploadSuccess = () => {
if (reopenAfterAdd) {
if (reopenAfterDragSignal.value) {
// Small delay to allow the modal to close and reopen
setTimeout(() => {
handleClose();
@@ -405,7 +407,7 @@ export const AssetModal = () => {
return (
<>
<TransitionDialogue
<Modal
isOpen={assetModalVisible.value && assetModalVisibleDuringDrag.value}
onClose={handleClose}
className="h-[640px] max-w-4xl"
@@ -448,19 +450,34 @@ export const AssetModal = () => {
))}
</div>
<div className="mt-auto flex items-center gap-2 pt-3">
<input
type="checkbox"
id="reopen-after-add"
checked={reopenAfterAdd}
onChange={(e) => setReopenAfterAdd(e.target.checked)}
className="h-4 w-4 rounded-lg border-gray-300 bg-gray-700 text-brand-primary focus:ring-brand-primary"
/>
<label
htmlFor="reopen-after-add"
className="text-sm text-white/70"
<div
className="flex cursor-pointer items-center"
onClick={(e) => {
// Prevent double-firing when clicking the checkbox itself
if (e.target instanceof HTMLInputElement) return;
const checkbox = document.getElementById(
"reopen-after-add",
) as HTMLInputElement;
if (checkbox) {
checkbox.checked = !checkbox.checked;
reopenAfterDragSignal.value = checkbox.checked;
}
}}
>
Reopen after adding
</label>
<input
type="checkbox"
id="reopen-after-add"
checked={reopenAfterDragSignal.value}
onChange={handleReopenChange}
className="h-4 w-4 cursor-pointer rounded-lg border-gray-300 bg-gray-700 text-brand-primary focus:ring-brand-primary"
/>
<label
htmlFor="reopen-after-add"
className="ml-2 cursor-pointer select-none text-sm text-white/70"
>
Reopen after adding
</label>
</div>
</div>
</div>
<div className="col-span-9 p-3 pb-0 ps-0 pt-2">
@@ -520,7 +537,7 @@ export const AssetModal = () => {
</div>
</div>
</div>
</TransitionDialogue>
</Modal>
<UploadModal3D
onClose={() => setIsUploadModalOpen(false)}
onSuccess={handleUploadSuccess}
@@ -155,7 +155,7 @@ export const ControlPanelSceneObject = () => {
updatedScaleValues.z = (parseFloat(localScale.z) + scale).toString();
handleScaleChange(updatedScaleValues);
}
};
const handleScaleChange = (xyz: Record<string, string>) => {
if (isInvalid(xyz)) {
@@ -180,9 +180,10 @@ export const ControlPanelSceneObject = () => {
return (
<Transition
as="div"
show={isShowing.value}
className={twMerge(
"glass absolute bottom-0 right-0 mb-2 mr-2 flex h-fit w-56 origin-bottom-right flex-col gap-2 rounded-lg border border-ui-panel-border p-3.5 text-white shadow-lg",
"glass absolute bottom-0 right-0 mb-4 mr-4 flex h-fit w-56 origin-bottom-right flex-col gap-2 rounded-lg border border-ui-panel-border p-3.5 text-white shadow-lg",
)}
enter="transition-opacity duration-150"
enterFrom="opacity-0"
@@ -208,6 +209,7 @@ export const ControlPanelSceneObject = () => {
</div>
<Transition
as="div"
show={!isCollapsed}
enter="transition-all duration-200 ease-in-out"
enterFrom="opacity-0 max-h-0"
@@ -179,6 +179,7 @@ export const Outliner = () => {
return (
<Transition
as="div"
show={outlinerIsShowing.value}
className={twMerge(
"glass flex max-h-[34vh] w-[240px] origin-bottom-left flex-col overflow-hidden rounded-lg shadow-lg",
@@ -2,11 +2,7 @@ import React from "react";
import { useSignals } from "@preact/signals-react/runtime";
import { poseMode, showPoseControls } from "../signals/selectedMode";
import { Button, Tooltip } from "~/components";
import {
faCheck,
faCheckDouble,
faPersonRunning,
} from "@fortawesome/pro-solid-svg-icons";
import { faCheck, faPersonRunning } from "@fortawesome/pro-solid-svg-icons";
declare global {
interface Window {
@@ -5,11 +5,7 @@ import { Badge } from "~/components";
import { AssetType } from "~/enums";
import { useState } from "react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { faHandPointer } from "@fortawesome/pro-regular-svg-icons";
import {
faArrowPointer,
faUpDownLeftRight,
} from "@fortawesome/pro-solid-svg-icons";
import { faUpDownLeftRight } from "@fortawesome/pro-solid-svg-icons";
interface Props {
debug?: string;
@@ -44,10 +40,7 @@ export const ItemElement = ({ item }: Props) => {
const [imageError, setImageError] = useState(false);
return (
<div
className="group relative w-full cursor-pointer select-none overflow-hidden transition-all duration-200"
onPointerDown={(event) => DndAsset.onPointerDown(event, item)}
>
<div className="group relative w-full select-none overflow-hidden transition-all duration-200">
{item.media_type && (
<Badge
label={
@@ -61,7 +54,11 @@ export const ItemElement = ({ item }: Props) => {
/>
)}
<div className="pointer-events-none relative aspect-[16/12] w-full select-none overflow-hidden rounded-xl border-[3px] border-white/5 bg-brand-secondary-600 object-cover object-center transition-all group-hover:border-brand-primary">
<div
className="pointer-events-none relative aspect-[16/12] w-full select-none overflow-hidden rounded-xl border-[3px] border-white/5 bg-brand-secondary-600 object-cover object-center transition-all group-hover:border-brand-primary"
onPointerDown={(event) => DndAsset.onPointerDown(event, item)}
style={{ cursor: "grab", pointerEvents: "auto" }}
>
{item.thumbnail && !imageError && (
<img
crossOrigin="anonymous"
+10 -2
View File
@@ -5,12 +5,20 @@ set -euxo pipefail
# Tauri doesn't let you configure the frontend project directory statically, though they do provide an
# environment variable to pass it to the CLI. Without doing this, the tauri cli randomly walks the
# filesystem and finds the wrong frontend code.
# Kill any process running on port 5741
if lsof -i tcp:5741 &>/dev/null; then
lsof -i tcp:5741 -t | xargs kill -9
echo "Killed process running on port 5741"
else
echo "No process running on port 5741"
fi
# TODO(bt,2025-02-13): This is not the correct way to get the root dir
brew install pkg-config pixman cairo pango libpng jpeg giflib librsvg
root_dir=$(pwd)
frontend_path="${root_dir}/frontend"
pushd "${frontend_path}"
#nvm use stable
npm install
popd
@@ -20,5 +28,5 @@ export TAURI_APP_PATH="${root_dir}/crates/desktop/tauri-artcraft"
export CONFIG_PATH="${TAURI_APP_PATH}/tauri.artcraft_2d.conf.toml"
#cargo tauri dev --no-watch -- --config "${CONFIG_PATH}"
# TODO: --no-watch
cargo tauri dev --config "${CONFIG_PATH}"
+3 -2
View File
@@ -18,6 +18,7 @@ popd
export TAURI_FRONTEND_PATH="${frontend_path}"
export TAURI_APP_PATH="${root_dir}/crates/desktop/tauri-artcraft"
export CONFIG_PATH="${TAURI_APP_PATH}/tauri.artcraft_2d.conf.json"
export CONFIG_PATH="${TAURI_APP_PATH}/tauri.artcraft_3d.conf.toml"
cargo tauri dev --config "${TAURI_APP_PATH}/tauri.artcraft_2d.conf.json" -- --no-watch
# TODO: --no-watch
cargo tauri dev --config "${CONFIG_PATH}"