Added rotation handling (beta!) Don't expand them if you don't wish to show them, report any performance issues and such to me

dependabot/npm_and_yarn/react-scripts-4.0.3
Rawrington 6 years ago
parent 38f3e88f72
commit 64bfab91f5
  1. 2
      package.json
  2. 49
      src/ACTWebsocket.js
  3. 8
      src/Action.js
  4. 199
      src/App.js
  5. 26
      src/css/Action.css
  6. 20
      src/css/App.css
  7. 21
      src/css/index.css

@ -1,6 +1,6 @@
{
"name": "skilldisplay",
"version": "0.2.0",
"version": "0.3.0",
"private": true,
"dependencies": {
"react": "^16.8.6",

@ -1,21 +1,30 @@
export default function listenActWebSocket(callback) {
const url = new URLSearchParams(window.location.search)
const wsUri = `${url.get('HOST_PORT')}BeforeLogLineRead` || undefined
const ws = new WebSocket(wsUri)
ws.onerror = () => listenActWebSocket()
ws.onmessage = function (e, m) { //PING
if (e.data === '.') return ws.send('.') //PONG
const obj = JSON.parse(e.data)
if(obj.msgtype === 'SendCharName')
{
return callback(obj.msg)
}
else if(obj.msgtype === 'Chat')
{
const code = obj.msg.substring(0, 2) //first 2 numbers POG
const handleCodes = new Set([
'00',
'01',
'02',
'21',
'22',
'33'
])
if(code === '21' || code === '22') return callback(obj.msg) //NetworkAbility or NetworkAoeAbility
}
}
}
export default function listenActWebSocket( callback ) {
const url = new URLSearchParams(window.location.search)
const wsUri = `${url.get("HOST_PORT")}BeforeLogLineRead` || undefined
const ws = new WebSocket(wsUri)
ws.onerror = () => ws.close()
ws.onclose = () => setTimeout(() => { listenActWebSocket( callback ) }, 1000)
ws.onmessage = function(e, m) {
if (e.data === ".") return ws.send(".") //PING
const obj = JSON.parse(e.data);
if (obj.msgtype === "SendCharName") {
return callback(obj.msg, null)
} else if (obj.msgtype === "Chat") {
const code = obj.msg.substring(0, 2) //first 2 numbers POG
if (handleCodes.has(code)) return callback(obj.msg, code) //NetworkAbility or NetworkAoeAbility
}
}
return ws
}

@ -26,14 +26,14 @@ const ogcdOverrides = new Set([
114 //bard MB
])
export default function Action({ action_id }) {
export default function Action({ actionId, additionalClasses }) {
const [apiData, setApiData] = React.useState()
React.useEffect(() => {
let current = true
void (async () => {
const data = await (
await fetch(`https://xivapi.com/Action/${action_id}`, { mode: 'cors' })
await fetch(`https://xivapi.com/Action/${actionId}`, { mode: 'cors' })
).json()
if (current) {
setApiData(data)
@ -43,7 +43,7 @@ export default function Action({ action_id }) {
return () => {
current = false
}
}, [action_id])
}, [actionId])
if (apiData === undefined || !apiData.Icon) {
return null
@ -51,7 +51,7 @@ export default function Action({ action_id }) {
return (
<img
className={(gcdOverrides.has(action_id) || (!ogcdOverrides.has(action_id) && apiData.ActionCategory.ID !== 4)) ? 'action-icon gcd' : 'action-icon ogcd'}
className={(gcdOverrides.has(actionId) || (!ogcdOverrides.has(actionId) && apiData.ActionCategory.ID !== 4)) ? `gcd ${additionalClasses}` : `ogcd ${additionalClasses}`}
src={`https://xivapi.com/${apiData.Icon}`}
alt={apiData.Name || ''}
/>

@ -2,73 +2,148 @@ import React from 'react'
import listenActWebSocket from './ACTWebsocket'
import './css/App.css'
import Action from './Action'
import RotationContainer from './Rotation'
import ReactDOM from 'react-dom'
class App extends React.Component {
state = {
me: 0,
actionlist: [],
actionindex: 1,
lastAddedTimestamp: '',
lastAddedAction: -1,
}
constructor(props) {
super(props)
listenActWebSocket(this.handleLogEvent.bind(this))
}
handleLogEvent(data) {
if(data.charID) {
this.setState({me: data.charID})
return
} //the ME data we need
const me = this.state.me
if(me === 0) return //we need data on the character first
let log = data.split('|')
if(parseInt(log[2],16) !== me) return //we only care about our actions
const action = parseInt(log[4],16)
if(action <= 8) return //things we don't care about i.e. sprint auto-attacks
if(this.state.lastAddedTimestamp === log[1] && this.state.lastAddedAction === action) return //no double aoe stuff
const index = this.state.actionindex
export default function App() {
// NOTE: unlike class state, useState doesn't do object merging; instead, it directly holds values
const [actionList, setActionList] = React.useState([])
const [encounterList, setEncounterList] = React.useState([])
React.useEffect(() => {
this.setState((state) => {
const actionindex = (state.actionindex >= 32)?1:state.actionindex+1
const lastAddedTimestamp = log[1]
const lastAddedAction = action
const actionlist = state.actionlist.concat({index,action});
// These values are only used internally by the handler,
// we don't need to notify React that they were updated,
// or keep their updates synchronized with actionList updates.
//
// This means we don't have to keep them in State (or Reducer)!
let selfId
let lastTimestamp = ''
let lastAction = -1
let currentZone = 'Unknown'
// we need keys to persist for each push, even if we shorten the array later,
// so we store the key with the action; can't just use array index due to CSS
let lastKey = 1
// listenActWebSocket should be changed to return the websocket,
// and this effect should return a function that disconnects the websocket
//
// like "return () => { ws.close() }"
let ws = listenActWebSocket((data, code) => {
const openNewEncounter = (timestamp) => {
setEncounterList(encounterList => {
if(encounterList[0] && encounterList[0].rotation && encounterList[0].rotation.length <= 0) {
encounterList.shift()
}
encounterList.unshift({
name: currentZone,
rotation: []
})
return encounterList.slice(0,3)
})
}
return {actionindex,lastAddedTimestamp,lastAddedAction,actionlist}
})
setTimeout(this.purgeAction.bind(this), 10000)
}
purgeAction() {
this.setState((state) => {
const actionlist = state.actionlist.slice(1)
if (data.charID) {
selfId = data.charID
openNewEncounter()
return
}
switch(code) {
case '00':
const [, , refCode, , message] = data.split('|')
if(refCode === '0038' && message === 'end') openNewEncounter()
return
case '01':
const [, , , zoneName] = data.split('|')
currentZone = zoneName
return
case '02':
const [, , logCharIdHex] = data.split('|')
selfId = parseInt(logCharIdHex, 16)
openNewEncounter()
return
case '33':
const [, , , controlCode] = data.split('|')
if(controlCode === '40000012' || controlCode === '40000010') openNewEncounter()
return
default:
break
}
return {actionlist}
//if it's not any of these it must be Network(AOE)Ability, the bulk of what we want to handle
if (selfId === undefined) return
const [, logTimestamp, logCharIdHex, , logActionIdHex] = data.split('|')
// microoptimization: since selfId updates way less often,
// save selfId as data.charID.toString(16), that way you don't need to
// parse logCharIdHex every time
if (parseInt(logCharIdHex, 16) !== selfId) return
// we do a mathematical comparison with action though so can't optimize this away
const action = parseInt(logActionIdHex, 16)
if (
action <= 8 ||
(logTimestamp === lastTimestamp && action === lastAction)
)
return
if((Date.now() - Date.parse(lastTimestamp)) > 120000) openNewEncounter()//last action > 120s ago
lastTimestamp = logTimestamp
lastAction = action
const key = (lastKey % 256) + 1
lastKey = key
ReactDOM.unstable_batchedUpdates(() => {
setActionList(actionList => actionList.concat({ action, key }))
setEncounterList(encounterList => {
if(!encounterList[0]) {
encounterList[0] = {
name: currentZone,
rotation: []
}
}
encounterList[0].rotation.push( action )
return encounterList
})
})
// This _probably_ should be done as a separate React.useEffect instead,
// which runs as an effect whenever the value of actionList changes.
// The problem there is, it would have to detect whether the list grew
// since the last time it was called, otherwise it'd react (heh) to its own
// updates.
//
// Easier to pair it with the previous set.
setTimeout(() => {
setActionList(actionList => actionList.slice(1))
}, 10000)
})
}
render() {
let actions = []
for (const action of this.state.actionlist) {
actions.push(<Action key={action.index} action_id={action.action} />)
}
return <div className="actions">{actions}</div>
}
return () => { ws.close() }
}, [])
return (
<div className='container'>
<div className='actions'>
{actionList.map(({ action, key }) => (
<Action key={key} actionId={action} additionalClasses='action-move' />
))}
</div>
{encounterList.map((encounter, i) => (
<RotationContainer key={i} encounterId={i} name={encounter.name} actionList={encounter.rotation} />
))}
</div>
)
}
export default App;

@ -1,25 +1,27 @@
.action-icon {
animation-duration: 10s;
animation-name: action-move;
animation-timing-function: linear;
animation-fill-mode: forwards;
position: absolute;
.action-move {
animation-duration: 10s;
animation-name: action-move;
animation-timing-function: linear;
animation-fill-mode: forwards;
position: absolute;
}
.gcd {
width: 3rem;
vertical-align: top;
}
.ogcd {
width: 2rem;
vertical-align: top;
}
@keyframes action-move {
from {
transform: translateX(calc(100vw - 3rem));
}
from {
transform: translateX(calc(100vw - 3rem));
}
to {
transform: translateX(-3rem);
}
to {
transform: translateX(-3rem);
}
}

@ -1,5 +1,5 @@
.actions {
margin-top: 1em;
padding-top: 1rem;
background: linear-gradient(180deg,
rgba(0,0,0,0) calc(25% - 1px),
rgba(255,255,255,0.5) calc(25%),
@ -11,13 +11,13 @@
rgba(255,255,255,0.5) calc(75%),
rgba(0,0,0,0) calc(75% + 1px)
);
height: 3em;
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
height: 3rem;
padding-bottom: 1rem;
background-color: rgba(20, 20, 20, 0.3);
display: inline-block;
}
.container {
display: flex;
flex-direction: column;
}

@ -1,16 +1,15 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 16px;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-size: 16px;
}
html {
margin: 0;
height: 100vh;
overflow: hidden;
background-color: rgba(20, 20, 20, 0.3);
margin: 0;
height: 100vh;
overflow: hidden;
}
Loading…
Cancel
Save