Compare commits

...

10 Commits

  1. 15
      Dockerfile
  2. 0
      README.md
  3. 0
      items.csv
  4. 0
      package-lock.json
  5. 0
      package.json
  6. 0
      public/favicon.ico
  7. 0
      public/index.html
  8. 0
      public/logo192.png
  9. 0
      public/logo512.png
  10. 0
      public/manifest.json
  11. BIN
      public/progress1.mp3
  12. BIN
      public/progress2.mp3
  13. 0
      public/robots.txt
  14. 0
      src/App.css
  15. 165
      src/App.js
  16. 0
      src/App.test.js
  17. 0
      src/index.css
  18. 0
      src/index.js
  19. 0
      src/logo512.png
  20. 0
      src/reportWebVitals.js
  21. 0
      src/setupTests.js

@ -0,0 +1,15 @@
# pull official base image
FROM node:13.12.0-alpine
RUN mkdir -p /app
WORKDIR /app
EXPOSE 3001
ENV PATH /app/node_modules/.bin:$PATH
COPY . /app
RUN npm install -y --silent
RUN npm rebuild node-sass
CMD ["npm", "start"]

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 55 KiB

Before

Width:  |  Height:  |  Size: 48 KiB

After

Width:  |  Height:  |  Size: 48 KiB

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Binary file not shown.

@ -13,6 +13,7 @@ import ProgressBar from 'react-bootstrap/ProgressBar';
import Accordion from 'react-bootstrap/Accordion'; import Accordion from 'react-bootstrap/Accordion';
import ToastContainer from 'react-bootstrap/ToastContainer' import ToastContainer from 'react-bootstrap/ToastContainer'
import Toast from 'react-bootstrap/Toast' import Toast from 'react-bootstrap/Toast'
import Nav from 'react-bootstrap/Nav'
import { FaCheckCircle } from 'react-icons/fa'; import { FaCheckCircle } from 'react-icons/fa';
@ -20,15 +21,21 @@ import { FaCheckCircle } from 'react-icons/fa';
const parse = require('csv-parse/lib/sync') const parse = require('csv-parse/lib/sync')
const axios = require('axios'); const axios = require('axios');
const dataSplitters = [0,135,250,388] const dataSplitters = [0,128,225,363]
const BACKEND_URL = "https://projectdivar.com:4505" const BACKEND_URL = "https://projectdivar.com:4505"
const NOTIFICATIONTIMEOUT = 300 //In seconds
const progress1 = new Audio(process.env.PUBLIC_URL+"/progress1.mp3")
const progress2 = new Audio(process.env.PUBLIC_URL+"/progress2.mp3")
function ItemGroup(p) { function ItemGroup(p) {
const { data } = p const { data } = p
const { contributor } = p const { contributor } = p
const { setData1,setData2,setData3,setData4,setLastModified,lastModified } = p const { setData1,setData2,setData3,setData4,setLastModified,lastModified } = p
const [displayData,setDisplayData] = useState([]) const [displayData,setDisplayData] = useState([])
const [lockout,setLockout] = useState(false)
useEffect(()=>{ useEffect(()=>{
setDisplayData([...data].sort((a,b)=>{ setDisplayData([...data].sort((a,b)=>{
@ -37,15 +44,6 @@ function ItemGroup(p) {
if (b.required!==b.obtained&&a.required!==a.obtained) {return a.id-b.id} if (b.required!==b.obtained&&a.required!==a.obtained) {return a.id-b.id}
})) }))
},[data]) },[data])
function findIndex(name,arr) {
for (var i=0;i<arr.length;i++) {
if (arr[i].name===name) {
console.log("Found "+name+" at position "+i)
return i
}
}
}
useEffect(()=>{ useEffect(()=>{
displayData.forEach((item)=>{ displayData.forEach((item)=>{
@ -57,8 +55,15 @@ function ItemGroup(p) {
function updateItem(item,target,contributor) { function updateItem(item,target,contributor) {
var correctedVal=Math.min(item.required,target.value); var correctedVal=Math.min(item.required,target.value);
if (correctedVal===item.obtained) {return;} if (correctedVal==item.obtained) {return;}
axios.post(BACKEND_URL+"/updateItem",{obtained:correctedVal,id:item.id,last_modified:new Date(),item_name:item.name,username:contributor,required:item.required,operation:correctedVal==item.required?"FINISH":correctedVal>item.obtained?"INCREASE":"SET",previous_amt:item.obtained}); setLockout(true)
axios.post(BACKEND_URL+"/updateItem",{obtained:correctedVal,id:item.id,last_modified:new Date(),item_name:item.name,username:contributor,required:item.required,operation:correctedVal==item.required?"FINISH":correctedVal>item.obtained?"INCREASE":"SET",previous_amt:item.obtained})
.then((data)=>{
setLockout(false)
})
.catch((err)=>{
})
} }
return <Accordion.Item className="bg-dark" eventKey={p.akey}> return <Accordion.Item className="bg-dark" eventKey={p.akey}>
@ -69,7 +74,11 @@ function ItemGroup(p) {
<img src={"https://xivapi.com"+item.icon}/> {item.name} <img src={"https://xivapi.com"+item.icon}/> {item.name}
</Col> </Col>
<Col> <Col>
<input id={"field_"+item.id} style={{width:"5em"}} defaultValue={item.obtained} className="mt-1 bg-secondary" onChange={(f)=>{ <input disabled={lockout} id={"field_"+item.id} style={{width:"5em"}} defaultValue={item.obtained} className="mt-1 bg-secondary"
onKeyDown={(k)=>{
if (k.key==='Enter') {updateItem(item,document.getElementById("field_"+item.id),contributor)}
}}
onChange={(f)=>{
if (f.currentTarget.value>=item.required) {f.currentTarget.blur()} if (f.currentTarget.value>=item.required) {f.currentTarget.blur()}
}} onBlur={(f)=>{updateItem(item,f.currentTarget,contributor)}} type="number" min="0" max={item.required}/> / {item.required} {item.required!==item.obtained&&<FaCheckCircle style={{color:"green"}} onClick={(f)=>{ }} onBlur={(f)=>{updateItem(item,f.currentTarget,contributor)}} type="number" min="0" max={item.required}/> / {item.required} {item.required!==item.obtained&&<FaCheckCircle style={{color:"green"}} onClick={(f)=>{
updateItem(item,{value:item.required},contributor) updateItem(item,{value:item.required},contributor)
@ -83,6 +92,51 @@ function ItemGroup(p) {
</Accordion.Item> </Accordion.Item>
} }
function Notification(p) {
const [show,setShow] = useState(true)
const { not } = p
return <Toast key={not.id} show={show} autohide delay={NOTIFICATIONTIMEOUT*1000} onClose={()=>{setShow(false)}} bg={not.operation==="FINISH"?"success":not.operation==="INCREASE"?"primary":"warning"}>
<Toast.Header closeButton={true}>
<span className="me-auto">
<strong>{not.username}</strong>
{not.operation==="FINISH"?" has finished collecting "+not.required+"/"+not.required+" "+not.item_name+"!":
not.operation==="INCREASE"?" has collected "+not.obtained+"/"+not.required+" "+not.item_name+" (+"+(not.obtained-not.previous_amt)+")"
:" has set "+not.item_name+" to "+not.obtained+"/"+not.required}</span>
</Toast.Header>
</Toast>
}
function DarkInput(p){
const {props}=p;
return <input type="text" className="bg-dark text-white" {...p}></input>
}
function SorterApp(){
const [item1,setItem1] = useState("")
const [item2,setItem2] = useState("")
const [item3,setItem3] = useState("")
const [item4,setItem4] = useState("")
const [item5,setItem5] = useState("")
const [item6,setItem6] = useState("")
return <>
<Row className="text-white"><Col>Item 1:<DarkInput value={item1} onChange={(f)=>{setItem1(f.currentTarget.value);}}></DarkInput></Col><Col><input type="text"></input></Col></Row>
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row>
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row>
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row>
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row>
<Row><Col><input type="text"></input></Col><Col><input type="text"></input></Col></Row>
</>
}
function ListApp(){
return <>
<h1>List App!</h1>
</>
}
function App() { function App() {
const [data,setData] = useState([]) const [data,setData] = useState([])
@ -98,6 +152,14 @@ function App() {
const [lastModified,setLastModified] = useState(new Date()) const [lastModified,setLastModified] = useState(new Date())
const [contributor,setContributor] = useState("") const [contributor,setContributor] = useState("")
const [notifications,setNotifications] = useState([])
const [closedNotifications,setClosedNotifications] = useState([])
const [nav,setNav] = useState("main")
function LZ(digits,numb) {
return "0".repeat(digits-String(numb).length)+numb
}
useEffect(()=>{ useEffect(()=>{
const interval = setInterval(()=>{ const interval = setInterval(()=>{
@ -122,6 +184,49 @@ function App() {
},1000) },1000)
return ()=>clearInterval(interval) return ()=>clearInterval(interval)
},[lastModified]) },[lastModified])
useEffect(()=>{
var notificationLastUpdate = new Date(new Date()-(NOTIFICATIONTIMEOUT*1000))
var largestNotificationID = -1
var dataCheck=true
const interval = setInterval(()=>{
if (dataCheck) {
dataCheck=false
notificationLastUpdate = new Date(new Date()-(NOTIFICATIONTIMEOUT*1000))
axios.get(BACKEND_URL+"/getNotifications?date="+encodeURIComponent(LZ(4,notificationLastUpdate.getUTCFullYear())+"-"+LZ(2,notificationLastUpdate.getUTCMonth()+1)+"-"+LZ(2,notificationLastUpdate.getUTCDate())+" "+LZ(2,notificationLastUpdate.getUTCHours())+":"+LZ(2,notificationLastUpdate.getUTCMinutes())+":"+LZ(2,notificationLastUpdate.getUTCSeconds())+"."+LZ(3,notificationLastUpdate.getUTCMilliseconds())+"+00"))
.then((data)=>{
if (data.data.length>0) {
console.log("New notification array: "+JSON.stringify(data.data))
setNotifications(data.data)
var completion=false
var largestID = -1
for (var dat of data.data) {
if (dat.id>largestNotificationID && dat.operation==="FINISH") {
completion=true
largestID=Math.max(dat.id,largestID)
break
} else
if (dat.id>largestNotificationID)
{
largestID=Math.max(dat.id,largestID)
}
}
if (largestID!==-1) {
largestNotificationID = largestID
if (completion) {progress2.play()} else {progress1.play()}
}
}
})
.catch((err)=>{
console.log(err.message)
})
.then(()=>{
dataCheck=true
})
}
},1000)
return ()=>clearInterval(interval)
},[])
const disabled=true const disabled=true
@ -205,21 +310,27 @@ function App() {
<Navbar.Brand href="#home"> <Navbar.Brand href="#home">
<img src={process.env.PUBLIC_URL+"/favicon.ico"} width="30" height="30" className="d-inline-block align-top" alt="BUN logo"/> BUN Collab App <img src={process.env.PUBLIC_URL+"/favicon.ico"} width="30" height="30" className="d-inline-block align-top" alt="BUN logo"/> BUN Collab App
</Navbar.Brand> </Navbar.Brand>
{contributor.length>0&&<span className="text-light font-weight-light font-italic">Signed in as {contributor}</span>} {contributor.length>0&&<>
<Nav.Link style={(nav==="main")?{"color":"lime","text-decoration":"underline"}:{}} onClick={()=>{setNav("main")}}>Main</Nav.Link>
<Nav.Link style={(nav==="sort")?{"color":"lime","text-decoration":"underline"}:{}} onClick={()=>{setNav("sort")}}>Sort Check</Nav.Link>
<Nav.Link style={(nav==="list")?{"color":"lime","text-decoration":"underline"}:{}} onClick={()=>{setNav("list")}}>Grocery List</Nav.Link>
<span className="text-light font-weight-light font-italic">Signed in as {contributor}</span>
</>}
</Container> </Container>
</Navbar> </Navbar>
<Container> <Container>
{contributor.length===0?<> {contributor.length===0?<>
<input placeHolder="Bun" onKeyDown={(k)=>{if (k.key==='Enter') {setContributor(document.getElementById("username").value)}}} id="username"/> <input placeHolder="e.g. Bun" onKeyDown={(k)=>{if (k.key==='Enter') {setContributor(document.getElementById("username").value)}}} id="username"/>
<button type="Submit" onClick={(f)=>{setContributor(document.getElementById("username").value)}}>Submit</button> <button type="Submit" onClick={(f)=>{setContributor(document.getElementById("username").value)}}>Submit</button>
</>: </>:
data.length>0? nav==="main"?
data.length>0?
<> <>
<Accordion className="bg-dark" defaultActiveKey="0"> <Accordion className="bg-dark" defaultActiveKey="0">
<ItemGroup name="Gathering Items" contributor={contributor} akey="0" data={data} lastModified={lastModified} setLastModified={setLastModified} setData={setData} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/> <ItemGroup name="Gathering Items" contributor={contributor} akey="0" data={data} lastModified={lastModified} setLastModified={setLastModified} setData={setData} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
<ItemGroup name="Other Items" contributor={contributor} akey="1" data={data2} lastModified={lastModified} setLastModified={setLastModified} setData={setData2} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/> <ItemGroup name="Other Items" contributor={contributor} akey="1" data={data2} lastModified={lastModified} setLastModified={setLastModified} setData={setData2} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
<ItemGroup name="Pre-crafting" contributor={contributor} akey="2" data={data3} lastModified={lastModified} setLastModified={setLastModified} setData={setData3} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/> <ItemGroup name="Pre-crafting" contributor={contributor} akey="2" data={data3} lastModified={lastModified} setLastModified={setLastModified} setData={setData3} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
<ItemGroup name="Crafting Items" contributor={contributor} akey="3" data={data4} lastModified={lastModified} setLastModified={setLastModified} setData={setData4} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/> <ItemGroup name="Crafting Items" contributor={contributor} akey="3" data={data4} lastModified={lastModified} setLastModified={setLastModified} setData={setData4} setData1={setData} setData2={setData2} setData3={setData3} setData4={setData4}/>
</Accordion> </Accordion>
</>: </>:
!disabled&& !disabled&&
@ -242,16 +353,16 @@ function App() {
} }
</Col> </Col>
</Row> </Row>
:
nav==="sort"?<SorterApp></SorterApp>:
nav==="list"?<ListApp></ListApp>:
<></>
} }
<div style={{pointerEvents:"none",position:"fixed",top:"0px",left:"0px",width:"100%",height:"100%"}}> <div style={{pointerEvents:"none",position:"fixed",top:"0px",left:"0px",width:"100%",height:"100%"}}>
<ToastContainer position="bottom-end"> <ToastContainer position="bottom-end">
<Toast autohide delay={10000} onClose={()=>{console.log("closing")}} bg="primary"> {notifications.map((not)=>{
<Toast.Header closeButton={true}> return <Notification key={not.id} not={not}/>
<strong className="me-auto">Testing</strong> })}
<small>11 mins ago</small>
</Toast.Header>
</Toast>
</ToastContainer> </ToastContainer>
</div> </div>
</Container> </Container>

Before

Width:  |  Height:  |  Size: 182 KiB

After

Width:  |  Height:  |  Size: 182 KiB

Loading…
Cancel
Save