AO code common to all AO projects (ao-server, ao-svelt, ao-3, ao-cli), included on both client and server
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1550 lines
44 KiB

3 years ago
// Mutations are state builders.
// The current state is the result of all the events in the system fed through the mutation functions.
// `server/state.js` for server; `modules/*` for vuex.
// const Vue = require('vue')
import { createHash } from './crypto.js'
import {
blankCard,
blankPinboard,
getTask,
getTaskBy,
atomicCardPlay,
unpinTasksOutOfBounds,
discardTaskFromZone,
taskExists,
seeTask,
clearPassesTo,
changeGiftCount,
grabTask,
dropTask,
addParent,
removeParentIfNotParent,
filterFromSubpiles,
clearSeenExcept,
addSubTask,
putTaskInTask,
addPriority,
stashTask,
unstashTask,
addPotential,
checkPotential,
clearPotential,
updateLastUsed,
safeMerge,
registerDuplicateTaskId,
POTENTIALS_TO_EXECUTE,
} from './cards.js'
function aoMuts(aos, ev) {
switch (ev.type) {
//case 'ao-linked':
// aos.forEach((ao, i) => {
// if (ao.address === ev.address) {
// ao.links.push(ev.taskId)
// }
// })
// break
case 'ao-inbound-connected':
let inAddressConnect = aos.some(a => {
if (a.address === ev.address) {
a.inboundSecret = ev.secret
a.lastContact = Date.now()
return true
}
})
if (!inAddressConnect) {
let newEv = {
address: ev.address,
outboundSecret: false,
inboundSecret: ev.secret,
lastContact: Date.now(),
}
aos.push(newEv)
}
break
case 'ao-outbound-connected':
let outAddressConnect = aos.some(a => {
if (a.address === ev.address) {
a.outboundSecret = ev.secret
a.lastContact = Date.now()
return true
}
})
if (!outAddressConnect) {
let newEv = {
address: ev.address.trim(),
outboundSecret: ev.secret,
inboundSecret: false,
lastContact: Date.now(),
}
aos.push(newEv)
}
break
case 'ao-disconnected':
aos.forEach((ao, i) => {
if (ao.address.trim() === ev.address.trim()) {
aos.splice(i, 1)
}
})
break
}
}
function cashMuts(cash, ev) {
switch (ev.type) {
case 'ao-named':
cash.alias = ev.alias
break
case 'spot-updated':
cash.spot = ev.spot
break
case 'currency-switched':
cash.currency = ev.currency
break
case 'rent-set':
cash.rent = parseFloat(ev.amount)
break
case 'cap-set':
cash.cap = ev.amount
break
case 'funds-set':
cash.outputs = ev.outputs
cash.channels = ev.channels
break
case 'quorum-set':
cash.quorum = ev.quorum
break
case 'task-boosted':
if (ev.txid) cash.usedTxIds.push(ev.txid)
break
case 'task-boosted-lightning':
cash.pay_index = ev.pay_index
break
case 'get-node-info':
cash.info = ev.info
break
}
}
function membersMuts(members, ev) {
switch (ev.type) {
case 'ao-outbound-connected':
break
case 'ao-disconnected':
break
case 'member-created':
updateLastUsed(ev, ev.timestamp)
ev.muted = true
ev.p0wned = true
members.push(ev)
break
case 'member-activated':
members.forEach(member => {
if (member.memberId === ev.memberId && !member.banned) {
if (member.active < 0) {
member.active = -1 * member.active
} else {
member.active++
}
}
})
break
case 'task-boosted':
members.forEach(member => {
if (member.memberId === ev.taskId) {
if (member.active < 0) {
member.active = -1 * member.active
} else {
member.active++
}
}
})
break
case 'task-boosted-lightning':
members.forEach(member => {
if (member.memberId === ev.taskId) {
if (member.active < 0) {
member.active = -1 * member.active
} else {
member.active++
}
}
})
break
case 'task-visited':
members.forEach(member => {
if (member.memberId === ev.memberId) {
updateLastUsed(member, ev.timestamp)
}
})
break
case 'member-deactivated':
members.forEach(member => {
if (member.memberId === ev.memberId) {
if (member.active >= 0) {
member.active = -1 * member.active - 1
}
}
})
break
case 'member-secret-reset':
members.forEach(member => {
if (member.memberId === ev.kohaiId) {
const newSig = {
memberId: ev.senpaiId,
timestamp: ev.timestamp,
opinion: ev.type,
}
addPotential(member, newSig)
if (checkPotential(member, 'member-secret-reset')) {
member.p0wned = true
member.secret = createHash(member.name)
clearPotential(member, 'member-secret-reset')
}
}
})
break
case 'member-promoted':
let toIndex
let fromIndex
if (
members.some((member, i) => {
if (member.memberId === ev.senpaiId) {
toIndex = i
return true
}
}) &&
members.some((member, i) => {
if (member.memberId === ev.kohaiId) {
fromIndex = i
return true
}
})
) {
members.splice(toIndex, 0, members.splice(fromIndex, 1)[0])
}
case 'member-banned':
members.forEach(member => {
if (member.memberId === ev.kohaiId) {
const newSig = {
memberId: ev.senpaiId,
timestamp: ev.timestamp,
opinion: ev.type,
}
addPotential(member, newSig)
if (checkPotential(member, 'member-banned')) {
member.banned = true
if (member.active >= 0) {
member.active = -1 * member.active - 1
}
}
}
})
break
case 'member-unbanned':
members.forEach(member => {
if (
member.memberId === ev.kohaiId &&
member.hasOwnProperty('potentials') &&
member.potentials.length >= 1
) {
const beforeBans = member.potentials.filter(
p => p.opinion === 'member-banned'
).length
member.potentials = member.potentials.filter(
p => !(p.opinion === 'member-banned' && p.memberId === ev.senpaiId)
)
const afterBans = member.potentials.filter(
p => p.opinion === 'member-banned'
).length
if (
beforeBans >= POTENTIALS_TO_EXECUTE &&
afterBans < POTENTIALS_TO_EXECUTE
) {
member.banned = false
}
}
})
break
case 'member-purged':
for (let i = members.length - 1; i >= 0; i--) {
const member = members[i]
if (member.memberId === ev.memberId) {
const newSig = {
memberId: ev.blame,
timestamp: ev.timestamp,
opinion: ev.type,
}
//addPotential(member, newSig)
//if (testPotential(member, 'member-purged')) {
members.splice(i, 1)
//}
}
}
break
case 'resource-used':
members.forEach(member => {
if (member.memberId === ev.memberId) {
updateLastUsed(member, ev.timestamp)
}
})
break
case 'member-field-updated':
members.forEach(member => {
if (member.memberId === ev.memberId) {
member[ev.field] = ev.newfield
if (ev.field === 'secret') {
member.p0wned = false
}
}
})
break
case 'member-ticker-set':
members.forEach(member => {
if (member.memberId === ev.memberId) {
if (!member.tickers) {
member.tickers = []
}
if (
!ev.fromCoin ||
ev.fromCoin.trim().length < 1 ||
!ev.toCoin ||
ev.toCoin.trim().length < 1
) {
member.tickers.splice(ev.index, 1)
} else {
member.tickers[ev.index] = {
from: ev.fromCoin.trim().toLowerCase(),
to: ev.toCoin.trim().toLowerCase(),
}
}
}
})
break
case 'doge-barked':
members.forEach(member => {
// this should only bump up for mutual doges
if (member.memberId === ev.memberId) {
updateLastUsed(member, ev.timestamp)
// then bark
}
})
break
}
}
function resourcesMuts(resources, ev) {
switch (ev.type) {
case 'resource-created':
let resourceIds = resources.map(r => r.resourceId)
if (resourceIds.indexOf(ev.resourceId) === -1) {
resources.push(ev)
} else {
console.log(
'BAD data duplicate resource rejected in mutation, dup resource task likely created'
)
}
break
case 'resource-used':
resources.forEach(resource => {
if (resource.resourceId == ev.resourceId) {
resource.stock -= parseInt(ev.amount)
}
})
break
case 'resource-purged':
resources.forEach((r, i) => {
if (r.resourceId === ev.resourceId) {
resources.splice(i, 1)
}
})
break
case 'resource-stocked':
resources.forEach(resource => {
if (resource.resourceId == ev.resourceId) {
resource.stock += parseInt(ev.amount)
}
})
break
case 'channel-created':
resources.forEach((r, i) => {
if (r.resourceId == ev.resourceId) {
r.pubkey = ev.pubkey
}
})
break
}
}
function memesMuts(memes, ev) {
switch (ev.type) {
case 'meme-added':
const fileHash = ev.data
if (
!memes.some(file => {
return file.hash === ev.hash
})
) {
memes.push({
memeId: ev.taskId,
filename: ev.filename,
hash: ev.hash,
filetype: ev.filetype,
})
// console.log('added meme file: ', ev.filename)
} else {
// console.log('meme file already in state: ', ev.filename)
}
break
case 'task-removed':
for (let i = memes.length - 1; i >= 0; i--) {
const meme = memes[i]
if (meme.memeId === ev.taskId) {
memes.splice(i, 1)
}
}
break
}
}
function sessionsMuts(sessions, ev) {
switch (ev.type) {
case 'session-created':
let idHasSession = sessions.some(session => {
// replace that sessions creds,
let match = false
if (session.ownerId === ev.ownerId) {
match = true
Object.assign(session, session, ev)
}
return match // true terminates the some loop & idHasSession->true too
})
if (idHasSession) {
// edited in session
} else {
// id didn't previously have session
sessions.push(ev)
}
break
case 'session-killed':
for (let i = sessions.length - 1; i >= 0; i--) {
const s = sessions[i]
if (s.session == ev.session) {
sessions.splice(i, 1)
}
}
break
case 'ao-outbound-connected':
sessions.push({
ownerId: ev.address,
token: ev.secret,
session: ev.address,
})
break
}
}
let missingTaskIds = []
function tasksMuts(tasks, ev) {
let theTask
let inTask
let memberTask
// Most tasks have a taskId and memberId, and many have an inId, so pull these out in a standard way
const memberTaskId = ev.memberId || ev.blame
if (
memberTaskId &&
memberTaskId !== 'cleanup' &&
typeof memberTaskId === 'string' &&
!memberTaskId.includes('.onion')
) {
memberTask = getTask(tasks, memberTaskId)
if (
!memberTask &&
ev.type !== 'member-created' &&
ev.type !== 'member-purged'
) {
if (!missingTaskIds.includes(memberTaskId)) {
missingTaskIds.push(memberTaskId)
console.log(
ev.type + ': first missing member task for memberId',
memberTaskId,
'(' + missingTaskIds.length + ')'
)
}
return
}
}
const theTaskId =
ev.taskId ||
ev.subTask ||
ev.resourceId ||
ev?.from?.taskId ||
ev?.to?.taskId
if (theTaskId) {
theTask = getTask(tasks, theTaskId)
if (
!theTask &&
ev.type !== 'task-created' &&
ev.type !== 'grid-created' &&
ev.type !== 'resource-created' &&
ev.type !== 'meme-added'
) {
if (!missingTaskIds.includes(theTaskId)) {
missingTaskIds.push(theTaskId)
//console.log(ev.type + ': first missing task event for taskId', theTaskId, '(' + missingTaskIds.length + ') ev:', ev)
}
return // continuing may crash, but returning may cause further inconsistencies going forward
}
}
if (ev.inId) {
inTask = getTask(tasks, ev.inId)
if (!inTask && ev.type !== 'task-created') {
if (!missingTaskIds.includes(ev.inId)) {
missingTaskIds.push(ev.inId)
//console.log(ev.type + ': first missing task event for inId', ev.inId, '(' + missingTaskIds.length + ') ev:', ev)
}
return // continuing may crash, but returning may cause further inconsistencies going forward
}
}
switch (ev.type) {
case 'highlighted':
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
let didUpdateInline = false
task.highlights.forEach((h, i) => {
if (h.memberId === ev.memberId) {
didUpdateInline = true
if (h.valence === ev.valence) {
task.highlights.splice(i, 1)
} else {
h.valence = ev.valence
}
}
})
if (!didUpdateInline) {
task.highlights.push({
memberId: ev.memberId,
valence: ev.valence,
})
}
}
})
break
case 'ao-outbound-connected':
tasks.push(blankCard(ev.address, ev.address, 'purple', ev.timestamp))
break
case 'ao-disconnected':
break
case 'resource-created':
tasks.push(blankCard(ev.resourceId, ev.resourceId, 'red', ev.timestamp))
break
case 'member-created':
tasks.push(blankCard(ev.memberId, ev.memberId, 'blue', ev.timestamp))
break
case 'member-purged':
// This is terribly redundant since the same potential builds up on the member.
// Maybe the potentials system should be abstracted out to the spec or validation layer;
// Attempts to call limited functions instead produce an action-potential event.
// The original idea was potentials would only build up on members, not tasks.
let purgedMemberCard = false
for (let i = tasks.length - 1; i >= 0; i--) {
const task = tasks[i]
if (task.taskId === ev.memberId) {
let newSig = {
memberId: ev.blame,
timestamp: ev.timestamp,
opinion: ev.type,
}
addPotential(task, newSig)
if (checkPotential(task, 'member-purged')) {
tasks.splice(i, 1)
purgedMemberCard = true
}
}
}
if (purgedMemberCard) {
tasks.forEach((t, j) => {
t.subTasks = t.subTasks.filter(st => st !== ev.memberId)
t.priorities = t.priorities.filter(st => st !== ev.memberId)
t.completed.filter(st => st !== ev.memberId)
t.claimed = t.claimed.filter(st => st !== ev.memberId)
t.deck = t.deck.filter(st => st !== ev.memberId)
clearPassesTo(tasks, t, ev.memberId, true)
if (t.pins && t.pins.length >= 1) {
t.pins.forEach((pin, i) => {
const cell = pin.taskId
if (cell === ev.memberId) {
tasks[j].pins.spice(i, 1)
}
})
}
})
}
break
case 'meme-added':
// console.log('meme-added taskId is', ev.taskId)
if (!tasks.some(t => t.taskId === ev.taskId)) {
// console.log('adding meme', ev.taskId)
tasks.push(blankCard(ev.taskId, ev.filename, 'yellow', ev.timestamp))
}
break
case 'task-created':
const foundExistingTask = getTaskBy(tasks, ev.name, 'name')
if (foundExistingTask?.taskId) {
registerDuplicateTaskId(foundExistingTask.taskId, ev.taskId)
break
}
tasks.push(
blankCard(
ev.taskId,
ev.name,
ev.color,
ev.timestamp,
ev.deck,
ev.inId ? [ev.inId] : []
)
)
if (inTask) {
if (ev.prioritized) {
addPriority(inTask, ev.taskId)
} else {
addSubTask(inTask, ev.taskId)
}
addParent(inTask, ev.inId)
clearSeenExcept(inTask, ev.deck.length >= 1 ? [ev.deck[0]] : undefined) // The very font of novelty
}
break
case 'address-updated':
tasks.forEach(t => {
if (t.taskId === ev.taskId) {
t.address = ev.address
}
})
break
case 'task-passed':
let pass = [ev.fromMemberId, ev.toMemberId]
if (
!theTask.passed.some(p => {
if (p[0] === pass[0] && p[1] === pass[1]) {
return true
}
})
) {
theTask.passed.push(pass)
const recipient = getTask(tasks, ev.toMemberId)
if (recipient) {
changeGiftCount(recipient, 1)
}
}
break
case 'task-grabbed':
grabTask(tasks, theTask, ev.memberId)
break
case 'task-seen':
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
if (!task.seen) {
task.seen = []
}
if (
!task.seen.some(t => {
return t.memberId === ev.memberId
})
) {
task.seen.push({ memberId: ev.memberId, timestamp: Date.now() })
}
}
})
break
case 'task-started':
const tsFound = theTask
if (tsFound) {
if (!tsFound.timelog) {
tsFound.timelog = []
}
// console.log('task-started pre timelog is', tsFound.timelog)
tsFound.timelog.push({
memberId: ev.memberId,
taskId: ev.taskId,
inId: ev.inId,
start: ev.timestamp,
stop: null,
})
}
// console.log('task-started post timelog is', tsFound.timelog)
break
case 'task-stopped':
// console.log('task-stopped 1')
const tstFound = theTask
// console.log('task-stopped 2')
if (tstFound) {
// console.log('task-stopped 3')
// console.log('task-stopped pre timelog is', tstFound.timelog)
if (!tstFound.timelog) {
return
}
// console.log('task-stopped 4')
for (var i = tstFound.timelog.length - 1; i >= 0; i--) {
// console.log('task-stopped 5')
if (tstFound.timelog[i].memberId === ev.memberId) {
// console.log('task-stopped 6')
if (
tstFound.timelog[i].stop &&
tstFound.timelog[i].stop > tstFound.timelog[i].start
) {
// console.log(
// 'task-stopped 7 stop is',
// tstFound.timelog[i].stop,
// ' and start is',
// tstFound.timelog[i].start
// )
console.log(
'Stop time already set for most recent start time, triggering this event should not be possible in the GUI'
)
} else {
// console.log('task-stopped 8')
tstFound.timelog[i].stop = ev.timestamp
// tstFound.timelog.push({
// memberId: 'test',
// taskId: 'testId',
// inId: null,
// start: null,
// stop: null,
// })
}
}
// console.log('task-stopped post timelog is', tstFound.timelog)
break
}
}
break
case 'task-time-clocked':
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
let found = task.time.find(t => {
return t.memberId === ev.memberId
})
if (!found) {
task.time.push({
memberId: ev.memberId,
timelog: [ev.seconds],
date: [ev.date],
})
} else {
if (!found.timelog) {
found.timelog = []
}
if (!found.date) {
found.date = []
if (found.timelog.length > found.date.length) {
let count = found.timelog.length - found.date.length
while (count > 0) {
found.date.push(null)
count--
}
}
}
found.timelog.push(ev.seconds)
found.date.push(ev.date)
}
}
})
break
case 'task-signed':
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
clearPassesTo(tasks, task, ev.memberId)
if (task.deck.indexOf(ev.memberId) === -1) {
task.deck.push(ev.memberId)
}
let newSig = {
memberId: ev.memberId,
timestamp: ev.timestamp,
opinion: ev.opinion,
}
if (!task.signed) {
task.signed = []
}
task.signed.push(newSig)
if (task.guild && task.guild.length >= 1) {
if (
ev.opinion === 1 &&
(!task.hasOwnProperty('memberships') ||
!Array.isArray(task.memberships))
) {
if (!task.memberships) {
task.memberships = []
}
task.memberships.push({ memberId: ev.memberId, level: 2 })
} else if (
ev.opinion === 0 &&
task.hasOwnProperty('memberships') &&
Array.isArray(task.memberships)
) {
task.memberships.filter(memb => memb.memberId !== ev.memberId)
}
}
}
})
break
case 'task-membership':
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
if (task.guild && task.guild.length >= 1) {
// The member must have signed the task affirmatively
if (!task.signed || !task.signed.length || task.signed.length < 1) {
return
}
let mostRecentOpinion
for (let i = task.signed.length - 1; i--; i >= 0) {
const signature = task.signed[i]
if (signature.memberId === ev.memberId) {
mostRecentOpinion = signature.opinion
break
}
}
if (mostRecentOpinion < 1) {
return
}
if (
!task.memberships ||
!task.memberships.length ||
task.memberships.length < 1
) {
return
}
const promoterLevel = task.memberships.find(
membership => membership.memberId === ev.blame
)?.level
if (!promoterLevel || promoterLevel < 1) {
return
}
const promotedLevel =
task.memberships.find(
membership => membership.memberId === ev.memberId
)?.level || 0
let maxLevel = 0
task.memberships.forEach(membership => {
maxLevel = Math.max(maxLevel, membership.level)
})
// The promoter must be a member at least one level higher
// Or, the highest-level member of a group can promote themselves
const canPromote =
(promoterLevel > promotedLevel &&
promoterLevel >= ev.level + 1) ||
(ev.memberId === ev.blame &&
promoterLevel === maxLevel &&
maxLevel >= 1)
if (!canPromote) {
return
}
task.memberships = task.memberships.filter(
memb => memb.memberId !== ev.memberId
)
if (ev.level !== 0) {
task.memberships.push({
memberId: ev.memberId,
level: ev.level,
})
}
}
}
})
break
case 'task-stashed':
// I think the spec is only run on event creation, not load from database,
// so make sure the task exists before linking to it from another card
const toStash = theTask
if (toStash) {
grabTask(tasks, toStash, ev.blame)
addParent(toStash, ev.inId)
tasks.forEach(task => {
if (task.taskId === ev.inId) {
stashTask(task, ev.taskId, ev.level)
}
})
}
break
case 'task-unstashed':
// I think the spec is only run on event creation, not load from database,
// so make sure the task exists before linking to it from another card
const toUnstash = theTask
const unstashParentCard = inTask
if (toUnstash && unstashParentCard) {
grabTask(tasks, toUnstash, ev.blame)
tasks.forEach(task => {
if (task.taskId === ev.inId) {
unstashTask(task, ev.taskId, ev.level)
removeParentIfNotParent(task, unstashParentCard)
}
})
}
break
case 'pile-grabbed':
if (!ev.memberId) {
break
}
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
clearPassesTo(tasks, task, ev.memberId)
let crawler = [ev.taskId]
let history = []
let newCards = []
do {
newCards = []
crawler.forEach(t => {
if (history.indexOf(t) >= 0) return
let subTask = tasks.filter(pst => pst.taskId === t)
if (subTask.length < 1) {
// console.log(
// 'missing subtask, this is messy. parent task name: ',
// task.name
// )
return
}
if (subTask.length > 1) {
console.log('duplicate task found, this is very bad')
}
subTask = subTask[0]
if (
subTask === undefined ||
subTask.subTasks === undefined ||
subTask.priorities === undefined ||
subTask.completed === undefined
) {
console.log('invalid task data found, this is very bad')
return
}
history.push(t)
if (
subTask.deck.indexOf(ev.memberId) === -1 &&
ev.taskId !== ev.memberId
) {
clearPassesTo(tasks, subTask, ev.memberId)
subTask.deck.push(ev.memberId)
}
newCards = newCards
.concat(subTask.subTasks)
.concat(subTask.priorities)
.concat(subTask.completed)
})
crawler = newCards
} while (crawler.length > 0)
}
})
break
case 'task-dropped':
theTask.deck = theTask.deck.filter(d => d !== ev.memberId)
clearPassesTo(tasks, theTask, ev.memberId)
break
case 'pile-dropped':
if (!ev.memberId) {
break
}
tasks.forEach(task => {
if (task.taskId === ev.taskId) {
clearPassesTo(tasks, task, ev.memberId)
let crawler = [ev.taskId]
let history = []
let newCards = []
do {
newCards = []
crawler.forEach(t => {
if (history.indexOf(t) >= 0) return
let subTask = tasks.filter(pst => pst.taskId === t)
if (subTask.length < 1) {
console.log('missing subtask, this is messy')
return
}
if (subTask.length > 1) {
console.log('duplicate task found, this is very bad')
}
subTask = subTask[0]
if (
subTask === undefined ||
subTask.subTasks === undefined ||
subTask.priorities === undefined ||
subTask.completed === undefined
) {
console.log('invalid task data found, this is very bad')
return
}
history.push(t)
if (
subTask.deck.indexOf(ev.memberId) >= 0 &&
ev.taskId !== ev.memberId
) {
clearPassesTo(tasks, subTask, ev.memberId)
dropTask(subTask, ev.memberId)
}
newCards = newCards
.concat(subTask.subTasks)
.concat(subTask.priorities)
.concat(subTask.completed)
})
crawler = newCards
} while (crawler.length > 0)
}
})
break
case 'task-removed':
for (let i = tasks.length - 1; i >= 0; i--) {
const task = tasks[i]
if (task.taskId === ev.taskId) {
tasks.splice(i, 1)
}
}
tasks.forEach((t, i) => {
t.subTasks = t.subTasks.filter(st => st !== ev.taskId)
t.priorities = t.priorities.filter(st => st !== ev.taskId)
t.completed = t.completed.filter(st => st !== ev.taskId)
t.pins = t.pins.filter(pin => pin.taskId !== ev.taskId)
})
break
case 'tasks-removed':
for (let i = tasks.length - 1; i >= 0; i--) {
const task = tasks[i]
if (ev.taskIds.includes(task.taskId)) {
tasks.splice(i, 1)
}
}
tasks.forEach((t, i) => {
t.subTasks = t.subTasks.filter(st => !ev.taskIds.includes(st))
t.priorities = t.priorities.filter(st => !ev.taskIds.includes(st))
t.completed = t.completed.filter(st => !ev.taskIds.includes(st))
t.pins =
t.pins && Array.isArray(t.pins)
? t.pins.filter(pin => !ev.taskIds.includes(pin.taskId))
: []
})
break
case 'pile-prioritized':
inTask.priorities = inTask.priorities.concat(inTask.subTasks)
inTask.subTasks = []
break
case 'task-refocused':
atomicCardPlay(
tasks,
{ taskId: ev.taskId, inId: ev.inId, zone: 'priorities' },
{ taskId: ev.taskId, inId: ev.inId, zone: 'subTasks' },
ev.blame
)
break
case 'pile-refocused':
tasks.forEach(task => {
if (task.taskId === ev.inId) {
task.priorities.forEach(stId => {
tasks.forEach(st => {
if (st.taskId === stId) {
if (st.claimed && st.claimed.length >= 1) {
task.completed.push(stId)
} else {
task.subTasks.push(stId)
}
}
})
task.priorities = []
if (task.allocations && Array.isArray(task.allocations)) {
task.allocations.forEach(allocation => {
task.boost += allocation.amount
})
task.allocations = []
}
})
}
})
break
case 'task-played':
atomicCardPlay(tasks, ev.from, ev.to, ev.memberId)
break
case 'task-sub-tasked':
atomicCardPlay(
tasks,
{ taskId: ev.subTask },
{ taskId: ev.subTask, inId: ev.taskId, zone: 'subTasks' },
ev.memberId
)
break
case 'task-de-sub-tasked':
atomicCardPlay(
tasks,
{ taskId: ev.subTask, inId: ev.taskId, zone: 'subTasks' },
{ taskId: ev.subTask, zone: 'discard' },
ev.memberId
)
break
case 'task-prioritized':
atomicCardPlay(
tasks,
{ taskId: ev.taskId, inId: ev.inId, zone: 'card' },
{ taskId: ev.taskId, inId: ev.inId, zone: 'priorities' },
ev.memberId
)
break
case 'grid-pin':
if (typeof ev.y === 'string') {
ev.y = parseInt(ev.y)
}
if (typeof ev.x === 'string') {
ev.x = parseInt(ev.x)
}
atomicCardPlay(
tasks,
{ taskId: ev.taskId, inId: ev.inId, zone: 'subTasks' },
{
taskId: ev.taskId,
inId: ev.inId,
zone: 'grid',
coords: { y: parseInt(ev.y), x: parseInt(ev.x) },
},
ev?.memberId
)
break
case 'grid-unpin':
//console.log("event type is", ev.type)
atomicCardPlay(
tasks,
{
taskId: ev.taskId,
inId: ev.inId,
zone: 'grid',
coords: { y: parseInt(ev.y), x: parseInt(ev.x) },
},
{
taskId: ev.taskId,
inId: ev.inId,
zone: 'subTasks',
coords: { y: 0 },
},
ev.memberId
)
break
case 'task-emptied':
let updateParents = []
const emptiedParent = theTask
updateParents = [...theTask.priorities, ...theTask.subTasks]
theTask.priorities = []
theTask.subTasks = []
tasks.forEach(task => {
if (updateParents.indexOf(task.taskId) >= 0) {
removeParentIfNotParent(task, emptiedParent)
}
})
break
case 'task-guilded':
theTask.guild = ev.guild
break
case 'task-property-set':
let properties = ev.property.split('.')
if (properties?.length >= 2) {
if (
!theTask.hasOwnProperty(properties[0]) ||
typeof theTask[properties[0]] != 'object'
) {
theTask[properties[0]] = {}
}
theTask[properties[0]][properties[1]] = ev.value
} else {
theTask[ev.property] = ev.value
}
if (ev.property === 'pinboard.spread') {
unpinTasksOutOfBounds(tasks, theTask)
}
break
case 'task-colored':
theTask.color = ev.color
if (ev.inId) {
addSubTask(inTask, ev.taskId)
}
break
case 'task-claimed':
let paid = parseFloat(ev.paid) > 0 ? parseFloat(ev.paid) : 0
let bounty = 0
tasks.forEach(task => {
let found = false
task.priorities.some(taskId => {
if (taskId !== ev.taskId) {
return false
} else {
found = true
return true
}
})
task.subTasks.some(taskId => {
if (taskId !== ev.taskId) {
return false
} else {
found = true
return true
}
})
if (found) {
if (task.priorities.indexOf(ev.taskId) === -1) {
task.subTasks = task.subTasks.filter(tId => tId !== ev.subTask)
task.completed = task.completed.filter(tId => tId !== ev.subTask)
task.completed.push(ev.taskId)
}
// let alloc = false
if (task.allocations && Array.isArray(task.allocations)) {
task.allocations = task.allocations.filter(al => {
if (al.allocatedId === ev.taskId) {
bounty += al.amount
return false
}
return true
})
}
}
if (task.taskId === ev.taskId) {
clearPassesTo(tasks, task, ev.memberId)
if (task.deck.indexOf(ev.memberId) === -1) {
if (ev.taskId !== ev.memberId && ev.memberId) {
task.deck.push(ev.memberId)
}
}
if (task.claimed.indexOf(ev.memberId) === -1) {
task.claimed.push(ev.memberId)
}
task.lastClaimed = ev.timestamp
}
})
tasks.forEach(task => {
if (task.taskId === ev.memberId) {
task.boost += paid + bounty
}
})
break
case 'task-unclaimed':
theTask.claimed = theTask.claimed.filter(mId => mId !== ev.memberId)
if (theTask.claimed.length < 1) {
tasks.forEach(p => {
if (
p.priorities.indexOf(ev.taskId) === -1 &&
p.completed.indexOf(ev.taskId) > -1
) {
p.completed = p.completed.filter(taskId => taskId !== ev.taskId)
addSubTask(p, ev.taskId)
}
})
}
break
case 'task-reset':
const clearAllCheckmarksFromTask = taskToReset => {
if (!taskToReset) return
taskToReset.claimed = []
taskToReset.lastClaimed = ev.timestamp
}
if (theTask.uncheckThisCard) {
clearAllCheckmarksFromTask(theTask)
}
if (
theTask.uncheckPriorities &&
theTask.priorities &&
theTask.priorities.length >= 1
) {
theTask.priorities.forEach(tId => {
const priorityCard = getTask(tasks, tId)
clearAllCheckmarksFromTask(priorityCard)
})
}
if (theTask.uncheckPinned && theTask.pins && theTask.pins.length >= 1) {
theTask.pins.forEach(pin => {
const pinnedCard = getTask(tasks, pin.taskId)
clearAllCheckmarksFromTask(pinnedCard)
})
}
break
case 'task-boosted':
let amount = parseFloat(ev.amount)
let boost = parseFloat(theTask.boost)
if (amount > 0) {
theTask.boost = amount + boost
theTask.address = ''
}
break
case 'task-boosted-lightning':
const taskToBoostLightning = getTaskBy(
tasks,
ev.payment_hash,
'payment_hash'
)
let amountToBoost = parseFloat(ev.amount)
let boostLightning = parseFloat(taskToBoostLightning.boost)
if (amountToBoost > 0) {
taskToBoostLightning.boost = amountToBoost + boostLightning
taskToBoostLightning.bolt11 = ''
taskToBoostLightning.payment_hash = ''
}
break
case 'task-allocated':
if (!Number.isInteger(ev.amount) || ev.amount < 0) {
break
}
if (
!theTask.hasOwnProperty('allocations') ||
!Array.isArray(theTask.allocations)
) {
theTask.allocations = []
}
if (ev.amount === 0) {
theTask.allocations = theTask.allocations.filter(als => {
if (als.allocatedId == ev.allocatedId) {
theTask.boost += als.amount
return false
}
return true
})
break
}
const alreadyPointed = theTask.allocations.some(als => {
const diff = ev.amount - als.amount
if (als.allocatedId !== ev.allocatedId) return false
if (diff > theTask.boost) return true
theTask.boost -= diff
als.amount = ev.amount
return true
})
if (!alreadyPointed) {
theTask.allocations.push(ev)
theTask.boost -= ev.amount
}
addPriority(theTask, ev.allocatedId)
break
case 'resource-booked':
theTask.book = ev
break
case 'resource-used':
const charged = parseFloat(ev.charged)
if (charged > 0) {
memberTask.boost -= charged
const resourceCard = theTask
resourceCard.boost += charged
}
break
case 'invoice-created':
theTask.payment_hash = ev.payment_hash
theTask.bolt11 = ev.bolt11
break
case 'task-swapped':
let task
tasks.forEach(t => {
if (t.taskId === ev.taskId) {
task = t
}
})
if (task) {
let originalIndex = task.subTasks.indexOf(ev.swapId1)
let swapIndex = task.subTasks.indexOf(ev.swapId2)
let originalIndexCompleted = task.completed.indexOf(ev.swapId1)
let swapIndexCompleted = task.completed.indexOf(ev.swapId2)
if (originalIndex > -1 && swapIndex > -1) {
let newST = task.subTasks.slice()
newST[originalIndex] = ev.swapId2
newST[swapIndex] = ev.swapId1
task.subTasks = newST
}
if (originalIndexCompleted > -1 && swapIndexCompleted > -1) {
let newCompleted = task.completed.slice()
newCompleted[originalIndexCompleted] = ev.swapId2
newCompleted[swapIndexCompleted] = ev.swapId1
task.completed = newCompleted
}
}
break
case 'task-bumped':
let taskB
tasks.forEach(t => {
if (t.taskId === ev.taskId) {
taskB = t
}
})
if (taskB) {
let originalIndex = taskB.subTasks.indexOf(ev.bumpId)
let originalIndexCompleted = taskB.completed.indexOf(ev.bumpId)
if (
originalIndex === taskB.subTasks.length - 1 &&
ev.direction === -1
) {
let newST = [ev.bumpId]
newST = newST.concat(
taskB.subTasks.slice(0, taskB.subTasks.length - 1)
)
taskB.subTasks = newST
}
if (originalIndex === 0 && ev.direction === 1) {
let newST = taskB.subTasks.slice(1)
newST.push(ev.bumpId)
taskB.subTasks = newST
}
}
break
case 'tasks-received':
ev.tasks.forEach(newT => {
let existingTask = getTask(tasks, newT.taskId)
if (!existingTask) {
existingTask = blankCard(
newT.taskId,
newT.name,
newT.color,
newT.timestamp,
newT.parents,
newT.height,
newT.width
)
tasks.push(existingTask)
}
safeMerge(existingTask, newT)
})
// Loop through the new cards and remove invalid references to cards that don't exist on this server
/*changedIndexes.forEach(tId => {
const t = tasks[tId]
let beforeLength = t.subTasks.length
t.subTasks = t.subTasks.filter(stId => taskExists(tasks, stId))
t.priorities = t.priorities.filter(stId => taskExists(tasks, stId))
t.completed = t.completed.filter(stId => taskExists(tasks, stId))
t.deck = t.deck.filter(stId =>
tasks.some(sst => sst.taskId === stId && sst.taskId === sst.name)
)
if (t?.grid?.rows && Object.keys(t.grid.rows).length >= 1) {
let filteredRows = {}
Object.entries(t.grid.rows).forEach(([x, row]) => {
let filteredRow = {}
if (row) {
Object.entries(row).forEach(([y, stId]) => {
if (taskExists(tasks, stId)) {
filteredRow[y] = stId
}
})
if (Object.keys(filteredRow).length < 1) {
filteredRows[x] = {}
} else {
filteredRows[x] = filteredRow
}
}
})
t.grid.rows = filteredRows
}
})*/
break
case 'task-visited':
// Remove the avatar from everywhere else
tasks.forEach(task => {
if (task.hasOwnProperty('avatars')) {
task.avatars = task.avatars.filter(
avatarLocation => avatarLocation.memberId !== ev.memberId
)
}
})
if (!theTask.hasOwnProperty('avatars')) {
theTask.avatars = []
}
theTask.avatars.push({
memberId: ev.memberId,
timestamp: ev.timestamp,
area: ev.area,
})
theTask.lastClaimed = ev.timestamp
break
case 'member-charged':
memberTask.boost -= parseFloat(ev.charged)
if (memberTask.boost < 0) {
memberTask.boost = 0
}
break
case 'grid-created':
tasks.push(
blankCard(
ev.taskId,
ev.name,
ev.color,
ev.timestamp,
ev.deck,
ev.height,
ev.width
)
)
break
case 'grid-added':
theTask.pinboard = blankPinboard(ev.height, ev.width, ev.spread)
break
case 'grid-removed':
theTask.pins = []
theTask.pinboard = false
break
case 'grid-resized':
if (!theTask.pinboard) {
theTask.pinboard = blankPinboard(ev.height, ev.width)
}
theTask.pinboard.height = ev.height
theTask.pinboard.width = ev.width
theTask.pinboard.size = ev.size || 9
unpinTasksOutOfBounds(tasks, theTask)
break
}
}
export default {
aoMuts,
cashMuts,
membersMuts,
resourcesMuts,
memesMuts,
sessionsMuts,
tasksMuts,
POTENTIALS_TO_EXECUTE,
}