diff --git a/apps/mobile/.env.example b/apps/mobile/.env.example
index 1d7b842e..71e668bf 100644
--- a/apps/mobile/.env.example
+++ b/apps/mobile/.env.example
@@ -8,3 +8,7 @@ EXPO_NODE_ENV="development"
EXPO_PUBLIC_INDEXER_BACKEND_URL=""
EXPO_PUBLIC_PIXEL_URL="http://localhost:3000/pixel"
+REACT_APP_USERNAME_STORE_CONTRACT_ADDRESS=
+REACT_APP_CANVAS_NFT_CONTRACT_ADDRESS=
+REACT_APP_USERNAME_STORE_CONTRACT_ADDRESS=
+REACT_APP_NODE_ENV=
diff --git a/apps/mobile/src/modules/PixelPeace/index.tsx b/apps/mobile/src/modules/PixelPeace/index.tsx
index 7034ea90..343c1f0e 100644
--- a/apps/mobile/src/modules/PixelPeace/index.tsx
+++ b/apps/mobile/src/modules/PixelPeace/index.tsx
@@ -21,7 +21,8 @@ export const PixelPeace: React.FC = () => {
{Platform.OS == "web" && process.env.EXPO_PUBLIC_PIXEL_URL &&
<>
diff --git a/apps/website/src/app/components/NavbarPixel.tsx b/apps/website/src/app/components/NavbarPixel.tsx
new file mode 100644
index 00000000..385f5271
--- /dev/null
+++ b/apps/website/src/app/components/NavbarPixel.tsx
@@ -0,0 +1,46 @@
+'use client';
+
+import Link from 'next/link';
+import React, {useState} from 'react';
+import {createPortal} from 'react-dom';
+
+import {MobileNavBar} from './MobileNavBar';
+import {NavigationLinks} from './NavigationLinks';
+
+export function NavbarPixel() {
+ const [toggleNav, setToggleNav] = useState(false);
+ return (
+
+
+

+
+
AFK
+
+
+ {/*
*/}
+ {/*
*/}
+
+
+ {toggleNav &&
+ createPortal(
, document.body)}
+
+ );
+}
diff --git a/apps/website/src/app/pixel/page.tsx b/apps/website/src/app/pixel/page.tsx
index d0308dbc..56c8d2f7 100644
--- a/apps/website/src/app/pixel/page.tsx
+++ b/apps/website/src/app/pixel/page.tsx
@@ -1,17 +1,16 @@
'use client';
import {AppRender} from 'pixel_ui';
-
-import {Footer} from '../components/Footer';
-import {Navbar} from '../components/Navbar';
+import { NavbarPixel } from '../components/NavbarPixel';
export default function Pixel() {
return (
-
+
{/*
*/}
{typeof window !== 'undefined' &&
}
-
+ {/* {typeof window !== 'undefined' &&
} */}
+ {/*
*/}
);
}
diff --git a/onchain/solidity_contracts/src/launchpad/LaunchpadPumpDualVM.sol b/onchain/solidity_contracts/src/launchpad/LaunchpadPumpDualVM.sol
index 27750c74..df5c1e48 100644
--- a/onchain/solidity_contracts/src/launchpad/LaunchpadPumpDualVM.sol
+++ b/onchain/solidity_contracts/src/launchpad/LaunchpadPumpDualVM.sol
@@ -1,6 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
+import {CairoLib} from "kakarot-lib/CairoLib.sol";
+
+using CairoLib for uint256;
+
contract LaunchpadPumpDualVM {
/// @dev The address of the cairo contract to call
uint256 immutable starknetLaunchpad;
@@ -69,20 +73,57 @@ contract LaunchpadPumpDualVM {
function getLaunchPump(uint256 tokenAddress) public {
uint256[] memory tokenAddressCalldata = new uint256[](1);
- tokenAddressCalldata[0] = uint256(uint160(from));
+ tokenAddressCalldata[0] = uint256(uint160(tokenAddress));
uint256 tokenStarknetAddress =
- abi.decode(kakarot.staticcallCairo("compute_starknet_address", tokenAddressCalldata), (uint256));
+ abi.decode(starknetLaunchpad.staticcallCairo("compute_starknet_address", tokenAddressCalldata), (uint256));
// call launch that sent struct
// todo how do it?
}
- function createToken() public {
+
+ /** */
+ function createToken(address recipient,
+ bytes calldata symbol,
+ bytes calldata name,
+ uint256 initialSupply,
+ bytes calldata contractAddressSalt
+ ) public {
+
+ uint256[] memory recipientAddressCalldata = new uint256[](1);
+ recipientAddressCalldata[0] = uint256(uint160(recipient));
+ uint256 recipientStarknetAddress =
+ abi.decode(starknetLaunchpad.staticcallCairo("compute_starknet_address", recipientAddressCalldata), (uint256));
+
+ uint128 amountLow = uint128(initialSupply);
+ uint128 amountHigh = uint128(initialSupply >> 128);
+
+ uint256[] memory createTokenCallData = new uint256[](6);
+ createTokenCallData[0] = recipientStarknetAddress;
+ // Decode the first 32 bytes (a uint256 is 32 bytes)
+ uint256 symbolResult = abi.decode(symbol, (uint256));
+ uint256 nameResult = abi.decode(name, (uint256));
+ uint256 contractAddressSaltResult = abi.decode(contractAddressSalt, (uint256));
+
+ createTokenCallData[1] = uint(symbolResult);
+ createTokenCallData[2] = uint(nameResult);
+ createTokenCallData[3] = uint256(amountLow);
+ createTokenCallData[4] = uint256(amountHigh);
+ createTokenCallData[5] = uint256(contractAddressSaltResult);
+
+ starknetLaunchpad.callCairo(FUNCTION_SELECTOR_CREATE_TOKEN, createTokenCallData);
}
- function createAndLaunchToken() public {
+ function createAndLaunchToken(
+ address recipient,
+ bytes calldata symbol,
+ bytes calldata name,
+ uint256 initialSupply,
+ bytes calldata contractAddressSalt
+ ) public {
+
}
diff --git a/onchain/solidity_contracts/src/nostr/Namespace.sol b/onchain/solidity_contracts/src/nostr/Namespace.sol
index f856e0c1..11ff1a90 100644
--- a/onchain/solidity_contracts/src/nostr/Namespace.sol
+++ b/onchain/solidity_contracts/src/nostr/Namespace.sol
@@ -1,9 +1,14 @@
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0;
+
+import {CairoLib} from "kakarot-lib/CairoLib.sol";
+
+using CairoLib for uint256;
+
contract Namespace {
- /// @dev The address of the starknet token to call
+ /// @dev The address of the starknet token to call
uint256 immutable namespaceAddress;
constructor(uint256 _namespaceAddress) {
@@ -18,12 +23,12 @@ contract Namespace {
kakarotCallData[0] = uint256(uint160(userAddress));
uint256 userStarknetAddress =
- abi.decode(kakarot.staticcallCairo("compute_starknet_address", kakarotCallData), (uint256));
+ abi.decode(namespaceAddress.staticcallCairo("compute_starknet_address", kakarotCallData), (uint256));
uint256[] memory addressOfCallData = new uint256[](1);
addressOfCallData[0] = userStarknetAddress;
- bytes memory returnData = namespaceAddress.staticcallCairo("get_nostr_by_sn_default", balanceOfCallData);
- return abi.decode(returnData, (uint256));
+ bytes memory returnData = namespaceAddress.staticcallCairo("get_nostr_by_sn_default", addressOfCallData);
+ // return abi.decode(returnData, (uint256));
}
@@ -34,12 +39,12 @@ contract Namespace {
kakarotCallData[0] = uint256(uint160(nostrAddress));
uint256 userStarknetAddress =
- abi.decode(kakarot.staticcallCairo("compute_starknet_address", kakarotCallData), (uint256));
+ abi.decode(namespaceAddress.staticcallCairo("compute_starknet_address", kakarotCallData), (uint256));
uint256[] memory addressOfCallData = new uint256[](1);
addressOfCallData[0] = userStarknetAddress;
- bytes memory returnData = namespaceAddress.staticcallCairo("get_sn_by_nostr_default", balanceOfCallData);
- return abi.decode(returnData, (uint256));
+ bytes memory returnData = namespaceAddress.staticcallCairo("get_sn_by_nostr_default", addressOfCallData);
+ // return abi.decode(returnData, (uint256));
}
function linkNostrAddress() public {
diff --git a/packages/pixel_ui/index.js b/packages/pixel_ui/index.js
index d70cdca6..d1ec4477 100644
--- a/packages/pixel_ui/index.js
+++ b/packages/pixel_ui/index.js
@@ -1,2 +1,3 @@
-export * from "./src/App"
+// export * from "./src/App"
+export * from "./src/App.tsx"
// export * from "./src"
\ No newline at end of file
diff --git a/packages/pixel_ui/src/App.js b/packages/pixel_ui/src/App.js
index ba194aef..86be9f1d 100644
--- a/packages/pixel_ui/src/App.js
+++ b/packages/pixel_ui/src/App.js
@@ -23,7 +23,6 @@ import NotificationPanel from './tabs/NotificationPanel.js';
import ModalPanel from './ui/ModalPanel.js';
import Hamburger from './resources/icons/Hamburger.png';
import useMediaQuery from './hooks/useMediaQuery';
-// import { useMediaQuery } from 'react-responsive';
function App() {
// Window management
diff --git a/packages/pixel_ui/src/App.tsx b/packages/pixel_ui/src/App.tsx
new file mode 100644
index 00000000..658424e8
--- /dev/null
+++ b/packages/pixel_ui/src/App.tsx
@@ -0,0 +1,838 @@
+import React, { useState, useEffect, useRef, useCallback } from 'react';
+import useWebSocket, { ReadyState } from 'react-use-websocket';
+import {
+ useAccount,
+ useContract,
+ useNetwork,
+ useConnect
+} from '@starknet-react/core';
+import './App.css';
+import CanvasContainer from './canvas/CanvasContainer.js';
+import PixelSelector from './footer/PixelSelector.js';
+import TabsFooter from './footer/TabsFooter.js';
+import TabPanel from './tabs/TabPanel.js';
+import { usePreventZoom, useLockScroll } from './utils/Window.js';
+import { backendUrl, wsUrl, devnetMode } from './utils/Consts.js';
+import logo from './resources/logo.png';
+import canvasConfig from './configs/canvas.config.json';
+import { fetchWrapper, getTodaysStartTime } from './services/apiService.js';
+import art_peace_abi from './contracts/art_peace.abi.json';
+import username_store_abi from './contracts/username_store.abi.json';
+import canvas_nft_abi from './contracts/canvas_nft.abi.json';
+import NotificationPanel from './tabs/NotificationPanel.js';
+import ModalPanel from './ui/ModalPanel.js';
+import Hamburger from './resources/icons/Hamburger.png';
+import useMediaQuery from './hooks/useMediaQuery.js';
+// import { useMediaQuery } from 'react-responsive';
+
+interface IApp {
+ contractAddress?: string;
+ canvasAddress?: string;
+ nftAddress?: string;
+ factoryAddress?: string;
+}
+
+function App({ contractAddress, canvasAddress, nftAddress, factoryAddress }: IApp) {
+ // Window management
+ usePreventZoom();
+ const tabs = ['Canvas', 'Factions', 'Quests', 'Vote', 'NFTs', 'Account'];
+ const [activeTab, setActiveTab] = useState(tabs[0]);
+ useLockScroll(activeTab === 'Canvas');
+
+ const isDesktopOrLaptop = useMediaQuery({
+ query: '(min-width: 1224px)'
+ });
+ const isBigScreen = useMediaQuery({ query: '(min-width: 1824px)' });
+ const isTabletOrMobile = useMediaQuery({ query: '(max-width: 1224px)' });
+ const isPortrait = useMediaQuery({ query: '(orientation: portrait)' });
+ const isRetina = useMediaQuery({ query: '(min-resolution: 2dppx)' });
+ const isMobile = useMediaQuery({ query: '(max-width: 768px)' });
+ const isFooterSplit = useMediaQuery({ query: '(max-width: 52rem)' });
+ // TODO: height checks ?
+ // TODO: Animate logo exit on mobile
+
+ const [footerExpanded, setFooterExpanded] = useState(false);
+ const [modal, setModal] = useState(null);
+
+ const getDeviceTypeInfo = () => {
+ return {
+ isDesktopOrLaptop: isDesktopOrLaptop,
+ isBigScreen: isBigScreen,
+ isTabletOrMobile: isTabletOrMobile,
+ isPortrait: isPortrait,
+ isRetina: isRetina,
+ isMobile: isMobile
+ };
+ };
+
+ // Starknet wallet
+ const { account, address } = useAccount();
+ const { chain } = useNetwork();
+ const [queryAddress, setQueryAddress] = useState('0');
+ const [connected, setConnected] = useState(false); // TODO: change to only devnet
+ useEffect(() => {
+ if (devnetMode) {
+ if (connected) {
+ setQueryAddress(
+ '0328ced46664355fc4b885ae7011af202313056a7e3d44827fb24c9d3206aaa0'
+ );
+ } else {
+ setQueryAddress('0');
+ }
+ } else {
+ if (!address) {
+ setQueryAddress('0');
+ } else {
+ setQueryAddress(address.slice(2).toLowerCase().padStart(64, '0'));
+ }
+ }
+ }, [address, connected]);
+
+ // Contracts
+ // TODO: Pull addrs from api?
+ const { contract: artPeaceContract } = useContract({
+ address: process.env.REACT_APP_STARKNET_CONTRACT_ADDRESS,
+ abi: art_peace_abi
+ });
+ const { contract: usernameContract } = useContract({
+ address: process.env.REACT_APP_USERNAME_STORE_CONTRACT_ADDRESS,
+ abi: username_store_abi
+ });
+ const { contract: canvasNftContract } = useContract({
+ address: process.env.REACT_APP_CANVAS_NFT_CONTRACT_ADDRESS,
+ abi: canvas_nft_abi
+ });
+
+ const [currentDay, setCurrentDay] = useState(0);
+ const [isLastDay, setIsLastDay] = useState(false);
+ const [gameEnded, setGameEnded] = useState(false);
+ const [host, setHost] = useState('');
+ const [endTimestamp, setEndTimestamp] = useState(0);
+ useEffect(() => {
+ const fetchGameData = async () => {
+ let response = await fetchWrapper('get-game-data');
+ if (!response.data) {
+ return;
+ }
+ setCurrentDay(response.data.day);
+ if (devnetMode) {
+ const days = 4;
+ if (response.data.day >= days) {
+ setGameEnded(true);
+ } else if (response.data.day === days - 1) {
+ setIsLastDay(true);
+ }
+ } else {
+ let now = new Date();
+ const result = await getTodaysStartTime();
+ let dayEnd = new Date(result.data);
+ dayEnd.setHours(dayEnd.getHours() + 24);
+ // Now in seconds
+ let nowInSeconds = Math.floor(now.getTime() / 1000);
+ let dayEndInSeconds = Math.floor(dayEnd.getTime() / 1000);
+ if (nowInSeconds >= response.data.endTime) {
+ setGameEnded(true);
+ } else if (dayEndInSeconds >= response.data.endTime) {
+ setIsLastDay(true);
+ }
+ }
+ setHost(response.data.host);
+ setEndTimestamp(response.data.endTime);
+ };
+ fetchGameData();
+ }, []);
+
+ // Websocket
+ const { sendJsonMessage, lastJsonMessage, readyState } = useWebSocket(wsUrl, {
+ share: false,
+ shouldReconnect: (_e) => true,
+ reconnectAttempts: 10,
+ reconnectInterval: (attempt) => Math.min(10000, Math.pow(2, attempt) * 1000)
+ });
+ const [latestMintedTokenId, setLatestMintedTokenId] = useState(null);
+
+ useEffect(() => {
+ if (readyState === ReadyState.OPEN) {
+ sendJsonMessage({
+ event: 'subscribe',
+ data: {
+ channel: 'general'
+ }
+ });
+ }
+ }, [readyState]);
+
+ // Colors
+ const staticColors = canvasConfig.colors;
+ const [colors, setColors] = useState([]);
+
+ const [notificationMessage, setNotificationMessage] = useState('');
+
+ const fetchColors = async () => {
+ try {
+ let getColorsEndpoint = backendUrl + '/get-colors';
+ let response = await fetch(getColorsEndpoint);
+ let colors = await response.json();
+ if (colors.error) {
+ setColors(staticColors);
+ console.error(colors.error);
+ return;
+ }
+ if (colors.data) {
+ setColors(colors.data);
+ }
+ } catch (error) {
+ setColors(staticColors);
+ console.error(error);
+ }
+ };
+ useEffect(() => {
+ fetchColors();
+ }, []);
+
+ useEffect(() => {
+ const processMessage = async (message) => {
+ if (message) {
+ // Check the message type and handle accordingly
+ if (message.messageType === 'colorPixel') {
+ if (message.color >= colors.length) {
+ // Get new colors from backend
+ await fetchColors();
+ }
+ colorPixel(message.position, message.color);
+ } else if (
+ message.messageType === 'nftMinted' &&
+ activeTab === 'NFTs'
+ ) {
+ if (message.minter === queryAddress) {
+ setLatestMintedTokenId(message.token_id);
+ }
+ }
+ }
+ };
+
+ processMessage(lastJsonMessage);
+ }, [lastJsonMessage]);
+
+ // Canvas
+ const width = canvasConfig.canvas.width;
+ const height = canvasConfig.canvas.height;
+
+ const canvasRef = useRef(null);
+ const extraPixelsCanvasRef = useRef(null);
+
+ const colorPixel = (position, color) => {
+ const canvas = canvasRef.current;
+ const context = canvas.getContext('2d');
+ const x = position % width;
+ const y = Math.floor(position / width);
+ const colorIdx = color;
+ const colorHex = `#${colors[colorIdx]}FF`;
+ context.fillStyle = colorHex;
+ context.fillRect(x, y, 1, 1);
+ };
+
+ // Pixel selection data
+ const [selectedColorId, setSelectedColorId] = useState(-1);
+ const [pixelSelectedMode, setPixelSelectedMode] = useState(false);
+ const [selectedPositionX, setSelectedPositionX] = useState(null);
+ const [selectedPositionY, setSelectedPositionY] = useState(null);
+ const [pixelPlacedBy, setPixelPlacedBy] = useState('');
+
+ const [lastPlacedTime, setLastPlacedTime] = useState(0);
+ const [basePixelUp, setBasePixelUp] = useState(false);
+ const [chainFactionPixelsData, setChainFactionPixelsData] = useState([]);
+ const [chainFactionPixels, setChainFactionPixels] = useState([]);
+ const [factionPixelsData, setFactionPixelsData] = useState([]);
+ const [factionPixels, setFactionPixels] = useState([]);
+ const [extraPixels, setExtraPixels] = useState(0);
+ const [availablePixels, setAvailablePixels] = useState(0);
+ const [availablePixelsUsed, setAvailablePixelsUsed] = useState(0);
+ const [extraPixelsData, setExtraPixelsData] = useState([]);
+
+ const [selectorMode, setSelectorMode] = useState(false);
+
+ const [isEraserMode, setIsEraserMode] = React.useState(false);
+ const [isExtraDeleteMode, setIsExtraDeleteMode] = React.useState(false);
+
+ useEffect(() => {
+ const getLastPlacedPixel = `get-last-placed-time?address=${queryAddress}`;
+ async function fetchGetLastPlacedPixel() {
+ const response = await fetchWrapper(getLastPlacedPixel);
+ if (!response.data) {
+ return;
+ }
+ const time = new Date(response.data);
+ setLastPlacedTime(time?.getTime());
+ }
+
+ fetchGetLastPlacedPixel();
+ }, [queryAddress]);
+
+ const updateInterval = 1000; // 1 second
+ // TODO: make this a config
+ const timeBetweenPlacements = 120000; // 2 minutes
+ const [basePixelTimer, setBasePixelTimer] = useState('XX:XX');
+ useEffect(() => {
+ const updateBasePixelTimer = () => {
+ let timeSinceLastPlacement = Date.now() - lastPlacedTime;
+ let basePixelAvailable = timeSinceLastPlacement > timeBetweenPlacements;
+ if (basePixelAvailable) {
+ setBasePixelUp(true);
+ setBasePixelTimer('00:00');
+ clearInterval(interval);
+ } else {
+ let secondsTillPlacement = Math.floor(
+ (timeBetweenPlacements - timeSinceLastPlacement) / 1000
+ );
+ setBasePixelTimer(
+ `${Math.floor(secondsTillPlacement / 60)}:${secondsTillPlacement % 60 < 10 ? '0' : ''}${secondsTillPlacement % 60}`
+ );
+ setBasePixelUp(false);
+ }
+ };
+ const interval = setInterval(() => {
+ updateBasePixelTimer();
+ }, updateInterval);
+ updateBasePixelTimer();
+ return () => clearInterval(interval);
+ }, [lastPlacedTime]);
+
+ const [chainFactionPixelTimers, setChainFactionPixelTimers] = useState([]);
+ useEffect(() => {
+ const updateChainFactionPixelTimers = () => {
+ let newChainFactionPixelTimers = [];
+ let newChainFactionPixels = [];
+ for (let i = 0; i < chainFactionPixelsData.length; i++) {
+ let memberPixels = chainFactionPixelsData[i].memberPixels;
+ if (memberPixels !== 0) {
+ newChainFactionPixelTimers.push('00:00');
+ newChainFactionPixels.push(memberPixels);
+ continue;
+ }
+ let lastPlacedTime = new Date(chainFactionPixelsData[i].lastPlacedTime);
+ let timeSinceLastPlacement = Date.now() - lastPlacedTime?.getTime();
+ let chainFactionPixelAvailable =
+ timeSinceLastPlacement > timeBetweenPlacements;
+ if (chainFactionPixelAvailable) {
+ newChainFactionPixelTimers.push('00:00');
+ newChainFactionPixels.push(chainFactionPixelsData[i].allocation);
+ } else {
+ let secondsTillPlacement = Math.floor(
+ (timeBetweenPlacements - timeSinceLastPlacement) / 1000
+ );
+ newChainFactionPixelTimers.push(
+ `${Math.floor(secondsTillPlacement / 60)}:${secondsTillPlacement % 60 < 10 ? '0' : ''}${secondsTillPlacement % 60}`
+ );
+ newChainFactionPixels.push(0);
+ }
+ }
+ setChainFactionPixelTimers(newChainFactionPixelTimers);
+ setChainFactionPixels(newChainFactionPixels);
+ };
+ const interval = setInterval(() => {
+ updateChainFactionPixelTimers();
+ }, updateInterval);
+ updateChainFactionPixelTimers();
+ return () => clearInterval(interval);
+ }, [chainFactionPixelsData]);
+
+ const [factionPixelTimers, setFactionPixelTimers] = useState([]);
+ useEffect(() => {
+ const updateFactionPixelTimers = () => {
+ let newFactionPixelTimers = [];
+ let newFactionPixels = [];
+ for (let i = 0; i < factionPixelsData.length; i++) {
+ let memberPixels = factionPixelsData[i].memberPixels;
+ if (memberPixels !== 0) {
+ newFactionPixelTimers.push('00:00');
+ newFactionPixels.push(memberPixels);
+ continue;
+ }
+ let lastPlacedTime = new Date(factionPixelsData[i].lastPlacedTime);
+ let timeSinceLastPlacement = Date.now() - lastPlacedTime?.getTime();
+ let factionPixelAvailable =
+ timeSinceLastPlacement > timeBetweenPlacements;
+ if (factionPixelAvailable) {
+ newFactionPixelTimers.push('00:00');
+ newFactionPixels.push(factionPixelsData[i].allocation);
+ } else {
+ let secondsTillPlacement = Math.floor(
+ (timeBetweenPlacements - timeSinceLastPlacement) / 1000
+ );
+ newFactionPixelTimers.push(
+ `${Math.floor(secondsTillPlacement / 60)}:${secondsTillPlacement % 60 < 10 ? '0' : ''}${secondsTillPlacement % 60}`
+ );
+ newFactionPixels.push(0);
+ }
+ }
+ setFactionPixelTimers(newFactionPixelTimers);
+ setFactionPixels(newFactionPixels);
+ };
+ const interval = setInterval(() => {
+ updateFactionPixelTimers();
+ }, updateInterval);
+ updateFactionPixelTimers();
+ return () => clearInterval(interval);
+ }, [factionPixelsData]);
+
+ useEffect(() => {
+ let totalChainFactionPixels = 0;
+ for (let i = 0; i < chainFactionPixels.length; i++) {
+ totalChainFactionPixels += chainFactionPixels[i];
+ }
+ let totalFactionPixels = 0;
+ for (let i = 0; i < factionPixels.length; i++) {
+ totalFactionPixels += factionPixels[i];
+ }
+ setAvailablePixels(
+ (basePixelUp ? 1 : 0) +
+ totalChainFactionPixels +
+ totalFactionPixels +
+ extraPixels
+ );
+ }, [basePixelUp, chainFactionPixels, factionPixels, extraPixels]);
+
+ useEffect(() => {
+ async function fetchExtraPixelsEndpoint() {
+ let extraPixelsResponse = await fetchWrapper(
+ `get-extra-pixels?address=${queryAddress}`
+ );
+ if (!extraPixelsResponse.data) {
+ setExtraPixels(0);
+ return;
+ }
+ setExtraPixels(extraPixelsResponse.data);
+ }
+ fetchExtraPixelsEndpoint();
+
+ async function fetchChainFactionPixelsEndpoint() {
+ let chainFactionPixelsResponse = await fetchWrapper(
+ `get-chain-faction-pixels?address=${queryAddress}`
+ );
+ if (!chainFactionPixelsResponse.data) {
+ setChainFactionPixelsData([]);
+ return;
+ }
+ setChainFactionPixelsData(chainFactionPixelsResponse.data);
+ }
+ fetchChainFactionPixelsEndpoint();
+
+ async function fetchFactionPixelsEndpoint() {
+ let factionPixelsResponse = await fetchWrapper(
+ `get-faction-pixels?address=${queryAddress}`
+ );
+ if (!factionPixelsResponse.data) {
+ setFactionPixelsData([]);
+ return;
+ }
+ setFactionPixelsData(factionPixelsResponse.data);
+ }
+ fetchFactionPixelsEndpoint();
+ }, [queryAddress]);
+
+ const clearPixelSelection = () => {
+ setSelectedColorId(-1);
+ setSelectedPositionX(null);
+ setSelectedPositionY(null);
+ setPixelSelectedMode(false);
+ setPixelPlacedBy('');
+ };
+
+ const setPixelSelection = (x, y) => {
+ setSelectedPositionX(x);
+ setSelectedPositionY(y);
+ setPixelSelectedMode(true);
+ // TODO: move http fetch for pixel data here?
+ };
+
+ const clearExtraPixels = useCallback(() => {
+ setAvailablePixelsUsed(0);
+ setExtraPixelsData([]);
+
+ const canvas = extraPixelsCanvasRef.current;
+ const context = canvas.getContext('2d');
+ context.clearRect(0, 0, width, height);
+ }, [width, height]);
+
+ const clearExtraPixel = useCallback(
+ (index) => {
+ setAvailablePixelsUsed(availablePixelsUsed - 1);
+ setExtraPixelsData(extraPixelsData.filter((_, i) => i !== index));
+ const canvas = extraPixelsCanvasRef.current;
+ const context = canvas.getContext('2d');
+ const pixel = extraPixelsData[index];
+ const x = pixel.x;
+ const y = pixel.y;
+ context.clearRect(x, y, 1, 1);
+ },
+ [extraPixelsData, availablePixelsUsed]
+ );
+
+ const addExtraPixel = useCallback(
+ (x, y) => {
+ // Overwrite pixel if already placed
+ const existingPixelIndex = extraPixelsData.findIndex(
+ (pixel) => pixel.x === x && pixel.y === y
+ );
+ if (existingPixelIndex !== -1) {
+ let newExtraPixelsData = [...extraPixelsData];
+ newExtraPixelsData[existingPixelIndex].colorId = selectedColorId;
+ setExtraPixelsData(newExtraPixelsData);
+ } else {
+ setAvailablePixelsUsed(availablePixelsUsed + 1);
+ setExtraPixelsData([
+ ...extraPixelsData,
+ { x: x, y: y, colorId: selectedColorId }
+ ]);
+ }
+ },
+ [extraPixelsData, availablePixelsUsed, selectedColorId]
+ );
+
+ // Factions
+ const [chainFaction, setChainFaction] = useState(null);
+ const [userFactions, setUserFactions] = useState([]);
+ useEffect(() => {
+ async function fetchChainFaction() {
+ let chainFactionResponse = await fetchWrapper(
+ `get-my-chain-factions?address=${queryAddress}`
+ );
+ if (!chainFactionResponse.data) {
+ return;
+ }
+ if (chainFactionResponse.data.length === 0) {
+ return;
+ }
+ setChainFaction(chainFactionResponse.data[0]);
+ }
+ async function fetchUserFactions() {
+ let userFactionsResponse = await fetchWrapper(
+ `get-my-factions?address=${queryAddress}`
+ );
+ if (!userFactionsResponse.data) {
+ return;
+ }
+ setUserFactions(userFactionsResponse.data);
+ }
+ fetchChainFaction();
+ fetchUserFactions();
+ }, [queryAddress]);
+
+ // Templates
+ const [templateOverlayMode, setTemplateOverlayMode] = useState(false);
+ const [overlayTemplate, setOverlayTemplate] = useState(null);
+
+ const [templateFaction, setTemplateFaction] = useState(null);
+ const [templateImage, setTemplateImage] = useState(null);
+ const [templateColorIds, setTemplateColorIds] = useState([]);
+ const [templateCreationMode, setTemplateCreationMode] = useState(false);
+ const [templateCreationSelected, setTemplateCreationSelected] =
+ useState(false);
+ const [templatePosition, setTemplatePosition] = useState(0);
+
+ // NFTs
+ const [nftMintingMode, setNftMintingMode] = useState(false);
+ const [nftSelectionStarted, setNftSelectionStarted] = useState(false);
+ const [nftSelected, setNftSelected] = useState(false);
+ const [nftPosition, setNftPosition] = useState(null);
+ const [nftWidth, setNftWidth] = useState(null);
+ const [nftHeight, setNftHeight] = useState(null);
+
+ // Account
+ const { connect, connectors } = useConnect();
+ const connectWallet = async (connector) => {
+ if (devnetMode) {
+ setConnected(true);
+ return;
+ }
+ connect({ connector });
+ };
+ useEffect(() => {
+ if (devnetMode) return;
+ if (!connectors) return;
+ if (connectors.length === 0) return;
+
+ const connectIfReady = async () => {
+ for (let i = 0; i < connectors.length; i++) {
+ let ready = await connectors[i].ready();
+ if (ready) {
+ connectWallet(connectors[i]);
+ break;
+ }
+ }
+ };
+ connectIfReady();
+ }, [connectors]);
+
+ // Tabs
+ const [showExtraPixelsPanel, setShowExtraPixelsPanel] = useState(false);
+
+ useEffect(() => {
+ // TODO: If selecting into other tab, ask to stop selecting?
+ if (activeTab !== tabs[0] && showExtraPixelsPanel) {
+ clearExtraPixels();
+ setSelectedColorId(-1);
+ setShowExtraPixelsPanel(false);
+ return;
+ }
+
+ if (selectedColorId !== -1) {
+ if (availablePixels > (basePixelUp ? 1 : 0)) {
+ setActiveTab(tabs[0]);
+ setShowExtraPixelsPanel(true);
+ return;
+ } else {
+ setShowExtraPixelsPanel(false);
+ return;
+ }
+ } else {
+ if (availablePixelsUsed > 0) {
+ setActiveTab(tabs[0]);
+ setShowExtraPixelsPanel(true);
+ return;
+ } else {
+ setShowExtraPixelsPanel(false);
+ return;
+ }
+ }
+ }, [
+ activeTab,
+ selectedColorId,
+ availablePixels,
+ availablePixelsUsed,
+ basePixelUp
+ ]);
+
+ return (
+
+
+
+ {modal &&
}
+
+ {(!isMobile || activeTab === tabs[0]) && (
+

+ )}
+
+
+
+
+
+ {!gameEnded && (
+
+ )}
+ {isFooterSplit && !footerExpanded && (
+
{
+ setActiveTab(tabs[0]);
+ setFooterExpanded(!footerExpanded);
+ }}
+ >
+

+
+ )}
+ {isFooterSplit && footerExpanded && (
+
+ )}
+
+ {!isFooterSplit && (
+
+ )}
+
+
+
+ );
+}
+
+export default App;
diff --git a/packages/pixel_ui/src/configs/backend.config.json b/packages/pixel_ui/src/configs/backend.config.json
index 69a04867..53521fac 100644
--- a/packages/pixel_ui/src/configs/backend.config.json
+++ b/packages/pixel_ui/src/configs/backend.config.json
@@ -1,6 +1,7 @@
{
"host_local": "localhost",
- "host": "https://backend-pixel.onrender.com/",
+ "host_p": "https://backend-pixel.onrender.com/",
+ "host": "http://localhost:8081",
"port": 8082,
"scripts": {
"place_pixel_devnet": "../tests/integration/local/place_pixel.sh",
diff --git a/packages/pixel_ui/src/utils/Consts.js b/packages/pixel_ui/src/utils/Consts.js
index ea4c5b22..4777602f 100644
--- a/packages/pixel_ui/src/utils/Consts.js
+++ b/packages/pixel_ui/src/utils/Consts.js
@@ -1,15 +1,16 @@
import backendConfig from '../configs/backend.config.json';
/** TODO fix url */
-export const backendUrl = 'https://' + backendConfig.host;
+// TODO used REACT_APP_NODE_ENV
+
+// export const backendUrl = 'https://' + backendConfig.host;
+export const backendUrl = backendConfig.host;
// export const backendUrl = 'https://' + backendConfig.host + ':' + backendConfig.port;
// export const backendUrl = backendConfig.production
// ? 'https://' + backendConfig.host
// : 'http://' + backendConfig.host + ':' + backendConfig.port;
-
-
export const wsUrl = backendConfig.production
? 'wss://' + backendConfig.host + '/ws'
: 'ws://' + backendConfig.host + ':' + backendConfig.consumer_port + '/ws';
@@ -22,8 +23,8 @@ export const templateUrl = backendConfig.production
? 'https://' + backendConfig.host
: 'http://' + backendConfig.host + ':' + backendConfig.port;
+// TODO used REACT_APP_NODE_ENV
export const devnetMode = backendConfig.production === false;
-
export const convertUrl = (url) => {
if (!url) {
return url;
diff --git a/packages/pixel_ui/tsconfig.json b/packages/pixel_ui/tsconfig.json
new file mode 100644
index 00000000..f27b54c2
--- /dev/null
+++ b/packages/pixel_ui/tsconfig.json
@@ -0,0 +1,24 @@
+{
+ "compilerOptions": {
+ "outDir": "./dist",
+ "rootDir": "./src",
+ "composite": true,
+ "target": "ES6",
+ "module": "ES6",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "allowJs": true,
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "noFallthroughCasesInSwitch": true,
+ "skipLibCheck": true,
+ "declaration": true,
+ "sourceMap": true,
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"],
+
+}
\ No newline at end of file