Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Responsive frontend and Template image initial setup #21

Merged
merged 4 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ However, it might be worth running only certain components for development/testi

- [Diagrams](./docs/diagrams/)
- [r/place technical document](https://www.redditinc.com/blog/how-we-built-rplace)
- [Telegram](https://t.me/+rd1pvEo8T2w2ZDRh)
- [OnlyDust](https://app.onlydust.com/p/artpeace)

## Contributors ✨

Expand Down
41 changes: 41 additions & 0 deletions frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"get-starknet": "^3.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-responsive": "^10.0.0",
"react-scripts": "5.0.1",
"react-use-websocket": "^4.8.1",
"starknet": "^5.24.3"
Expand Down
19 changes: 18 additions & 1 deletion frontend/src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
z-index: 100;
right: 0;
top: 0;
width: max(22rem, 25%);
width: max(25rem, 30%);
margin: 0.5rem;
padding: 0;

Expand All @@ -26,6 +26,14 @@
font-size: 1.2rem;
}

.App__panel--tablet {
width: max(28rem, 40%);
}

.App__panel--portrait {
width: max(28rem, 60%);
}

.App__footer {
position: fixed;
z-index: 101;
Expand All @@ -40,3 +48,12 @@

pointer-events: none;
}

.App__logo--mobile {
position: fixed;
z-index: 99;
left: 1rem;
top: 1rem;
width: min(8rem, 15%);
image-rendering: pixelated;
}
36 changes: 31 additions & 5 deletions frontend/src/App.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,38 @@
import React, { useState } from 'react';
import { useMediaQuery } from 'react-responsive'
import './App.css';
import Canvas from './canvas/Canvas.js';
import PixelSelector from './canvas/PixelSelector.js';
import SelectedPixelPanel from './canvas/SelectedPixelPanel.js';
import TabsFooter from './tabs/TabsFooter.js';
import TabPanel from './tabs/TabPanel.js';
import { usePreventZoom } from './utils/Window.js';
import logo from './resources/logo.png';

function App() {
// Window management
usePreventZoom();

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)' })
// TODO: Consider using in sizing stuff
const isRetina = useMediaQuery({ query: '(min-resolution: 2dppx)' })
// TODO: height checks ?

const getDeviceTypeInfo = () => {
return {
isDesktopOrLaptop: isDesktopOrLaptop,
isBigScreen: isBigScreen,
isTabletOrMobile: isTabletOrMobile,
isPortrait: isPortrait,
isRetina: isRetina
}
}

// Pixel selection data
const [selectedColorId, setSelectedColorId] = useState(-1);
const [pixelSelectedMode, setPixelSelectedMode] = useState(false);
Expand All @@ -35,15 +58,18 @@ function App() {
return (
<div className="App">
<Canvas selectedColorId={selectedColorId} setSelectedColorId={setSelectedColorId} pixelSelectedMode={pixelSelectedMode} selectedPositionX={selectedPositionX} selectedPositionY={selectedPositionY} setPixelSelection={setPixelSelection} clearPixelSelection={clearPixelSelection} />
<div className="App__panel">
{ pixelSelectedMode && (
{ !isDesktopOrLaptop && (
<img src={logo} alt="logo" className="App__logo--mobile" />
)}
<div className={"App__panel " + (isTabletOrMobile ? "App__panel--tablet " : " ") + (isPortrait ? "App__panel--portrait " : " ")}>
{ (!isPortrait ? pixelSelectedMode : pixelSelectedMode && activeTab === tabs[0]) && (
<SelectedPixelPanel selectedPositionX={selectedPositionX} selectedPositionY={selectedPositionY} clearPixelSelection={clearPixelSelection} />
)}
<TabPanel activeTab={activeTab} setActiveTab={setActiveTab} />
<TabPanel activeTab={activeTab} setActiveTab={setActiveTab} getDeviceTypeInfo={getDeviceTypeInfo} />
</div>
<div className="App__footer">
<PixelSelector selectedColorId={selectedColorId} setSelectedColorId={setSelectedColorId} />
<TabsFooter tabs={tabs} activeTab={activeTab} setActiveTab={setActiveTab} />
<PixelSelector selectedColorId={selectedColorId} setSelectedColorId={setSelectedColorId} getDeviceTypeInfo={getDeviceTypeInfo} />
<TabsFooter tabs={tabs} activeTab={activeTab} setActiveTab={setActiveTab} getDeviceTypeInfo={getDeviceTypeInfo} />
</div>
</div>
);
Expand Down
17 changes: 7 additions & 10 deletions frontend/src/canvas/Canvas.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const Canvas = props => {
setDragStartY(e.clientY)
}

const handlePointerUp = (e) => {
const handlePointerUp = () => {
setIsDragging(false)
setDragStartX(0)
setDragStartY(0)
Expand All @@ -90,12 +90,12 @@ const Canvas = props => {
const [setup, setSetup] = useState(false)
const [pixelPlacedBy, setPixelPlacedBy] = useState("")

const draw = (ctx, imageData) => {
const draw = useCallback((ctx, imageData) => {
ctx.canvas.width = width
ctx.canvas.height = height
ctx.putImageData(imageData, 0, 0)
// TODO: Use image-rendering for supported browsers?
}
}, [width, height])

useEffect(() => {
if (setup) {
Expand All @@ -122,7 +122,7 @@ const Canvas = props => {
let value = (byte >> (oneByteBitOffset - bitOffset)) & 0b11111
dataArray.push(value)
} else {
let byte = colorData[bytePos] << 8 | colorData[bytePos + 1]
let byte = (colorData[bytePos] << 8) | colorData[bytePos + 1]
let value = (byte >> (twoByteBitOffset - bitOffset)) & 0b11111
dataArray.push(value)
}
Expand Down Expand Up @@ -152,7 +152,7 @@ const Canvas = props => {
})
}
// TODO: Return a cleanup function to close the websocket / ...
}, [draw, readyState])
}, [readyState, sendJsonMessage, setup, colors, width, height, backendUrl, draw])

useEffect(() => {
if (lastJsonMessage) {
Expand All @@ -166,7 +166,7 @@ const Canvas = props => {
context.fillStyle = color
context.fillRect(x, y, 1, 1)
}
}, [lastJsonMessage])
}, [lastJsonMessage, colors, width])

const pixelSelect = useCallback((clientX, clientY) => {
const canvas = canvasRef.current
Expand All @@ -189,17 +189,14 @@ const Canvas = props => {
}).then(response => {
return response.text()
}).then(data => {
// TODO: not working
// TODO: Cache pixel info & clear cache on update from websocket
// TODO: Dont query if hover select ( until 1s after hover? )
setPixelPlacedBy(data)
}).catch(error => {
console.error(error)
//TODO: Handle error
});

// TODO: Create a border around the selected pixel
}, [props.setSelectedPositionX, props.setSelectedPositionY, props.setPixelSelectedMode, setPixelPlacedBy, width, height, props.selectedColorId, props.pixelSelectedMode, props.selectedPositionX, props.selectedPositionY])
}, [props, width, height, backendUrl])

const pixelClicked = (e) => {
pixelSelect(e.clientX, e.clientY)
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/canvas/PixelSelector.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
pointer-events: fill;
}

.PixelSelector--portrait {
font-size: min(1.4rem, 3vw);
margin: 1rem;
}

.PixelSelector__button {
padding: 0 1rem;
border-radius: 1rem;
Expand All @@ -14,6 +19,7 @@
align-items: center;
justify-content: center;

/* TODO: style this better? */
background-image: linear-gradient(to bottom right, rgba(50, 50, 50, 0.25), rgba(50, 50, 50, 0.5));
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/canvas/PixelSelector.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ const PixelSelector = (props) => {
}, [getTimeTillNextPlacement]);

return (
<div className="PixelSelector">
<div className={"PixelSelector " + (props.getDeviceTypeInfo().isPortrait ? " PixelSelector--portrait" : "")}>
{
selectorMode &&
<div className="PixelSelector__selector">
Expand Down
2 changes: 0 additions & 2 deletions frontend/src/canvas/TemplateOverlay.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ const TemplateOverlay = props => {
const width = props.templateWidth
const height = templateImage.length / width
const scaler = 8
const scalerFactor = 1 / scaler
const translater = (scaler - 1) / 2

const draw = (ctx, imageData) => {
ctx.canvas.width = width * scaler
Expand Down
1 change: 1 addition & 0 deletions frontend/src/tabs/ExpandableTab.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import React, { useState } from 'react';
import './ExpandableTab.css';

const ExpandableTab = props => {
// TODO: Close pixel selection when expanded
const [expanded, setExpanded] = useState(false);

const MainSection = props.mainSection;
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/tabs/NFTs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import './NFTs.css';
import ExpandableTab from './ExpandableTab.js';

Expand Down
15 changes: 13 additions & 2 deletions frontend/src/tabs/TabsFooter.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

/* TODO: css -> scss? */
.TabsFooter__logo {
width: min(8rem, 10vw);
width: 10rem;

pointer-events: fill;
image-rendering: pixelated;
Expand All @@ -25,9 +25,20 @@
}

.TabsFooter__tab {
font-size: min(1.6rem, 2vw);
font-size: min(1.6rem, 1.6vw);
text-shadow: 0 0 1rem #FFFFFF;
pointer-events: all;
padding: 2rem 0;
}

.TabsFooter__tab--portrait {
font-size: min(2rem, 2.2vw);
text-shadow: 0 0 1rem #FFFFFF;
pointer-events: all;
padding: 1.6rem 0.4rem;
border-radius: 0.5rem;
background: linear-gradient(to bottom right, rgba(255, 255, 255, 0.4), rgba(255, 255, 255, 0.6));
box-shadow: 0 0 1rem 0.1rem rgba(0, 0, 0, 0.2);
}

.TabsFooter__main {
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/tabs/TabsFooter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@ const TabsFooter = props => {
return (
<div className="TabsFooter">
<div key={props.tabs[0]} onClick={() => props.setActiveTab(props.tabs[0])} className="TabsFooter__main">
<img src={logo} alt="logo" className="Tabs__logo" />
<div className={"TabsFooter__tab " + (props.activeTab === props.tabs[0] ? 'TabsFooter__tab--active' : '')}>Canvas</div>
{ props.getDeviceTypeInfo().isDesktopOrLaptop &&
<img src={logo} alt="logo" className="TabsFooter__logo" />
}
<div className={"TabsFooter__tab " + (props.activeTab === props.tabs[0] ? 'TabsFooter__tab--active ' : ' ') + (props.getDeviceTypeInfo().isPortrait ? 'TabsFooter__tab--portrait' : ' ')}>Canvas</div>
</div>

{props.tabs.slice(1, props.tabs.length).map((type) => (
<div
key={type}
onClick={() => props.setActiveTab(type)}
className={"TabsFooter__tab " + (props.activeTab === type ? 'TabsFooter__tab--active' : '')}
className={"TabsFooter__tab " + (props.activeTab === type ? 'TabsFooter__tab--active ' : ' ') + (props.getDeviceTypeInfo().isPortrait ? 'TabsFooter__tab--portrait' : '')}
>
{type}
</div>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/tabs/Templates.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useState } from 'react'
import React from 'react'
import './Templates.css';
import ExpandableTab from './ExpandableTab.js';

Expand Down
2 changes: 1 addition & 1 deletion frontend/src/tabs/Voting.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const Voting = props => {
<div style={{gridArea: "color"}}>Color</div>
<div style={{gridArea: "votes"}}>Count</div>
</div>
<div style={{height: '55vh', overflow: 'scroll'}}>
<div style={{height: '52vh', overflow: 'scroll'}}>
{colors.map((color, index) => (
<div key={index} className="Voting__colors__item">
<div className="Voting__colors__item__vote" onClick={() => {
Expand Down
Loading