|
|
|
// Prioritize cards within other cards. Each card has a .priorities array of other taskIds.
|
|
|
|
import { headerStyle } from './styles.js'
|
|
|
|
import { aoEnv } from '../ao-lib/settings.js'
|
|
|
|
import { getCard, prioritizeCard, completeCard, uncheckCard, refocusCard } from '../ao-lib/api.js'
|
|
|
|
import { createCardInteractive } from './cards.js'
|
|
|
|
import { promptMenu } from './welcome.js'
|
|
|
|
|
|
|
|
// Prints the text (.name) of the first card prioritized in the logged-in users member card
|
|
|
|
export async function getTopPriorityText() {
|
|
|
|
return (await getFirstPriorityCard())?.name
|
|
|
|
}
|
|
|
|
|
|
|
|
// Makes an API request to get the first prioritized card in the member card of the logged-in user
|
|
|
|
async function getFirstPriorityCard() {
|
|
|
|
// Get the first priority of my member card
|
|
|
|
const memberId = aoEnv('AO_CLI_SESSION_MEMBERID')
|
|
|
|
if(!memberId) {
|
|
|
|
return 'Not logged in'
|
|
|
|
}
|
|
|
|
const fetchedCards = await getCard(memberId, 'priority')
|
|
|
|
if(fetchedCards === null) {
|
|
|
|
return null
|
|
|
|
}
|
|
|
|
if(!fetchedCards || fetchedCards.length < 2) {
|
|
|
|
return 'None'
|
|
|
|
}
|
|
|
|
return fetchedCards[1]
|
|
|
|
}
|
|
|
|
|
|
|
|
// Displays the priorities of the given taskId in a menu. Selecting a card shows a menu for that card. If taskId is null, member card is used.
|
|
|
|
export async function prioritiesMenu(taskId = null) {
|
|
|
|
console.log(`\n${headerStyle('My Priorities')}`)
|
|
|
|
let prioritiesChoices = []
|
|
|
|
|
|
|
|
const memberId = aoEnv('AO_CLI_SESSION_MEMBERID')
|
|
|
|
if(!memberId) {
|
|
|
|
console.log('Not logged in.')
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if(!taskId) {
|
|
|
|
// Get the priorities of my member card
|
|
|
|
taskId = memberId
|
|
|
|
}
|
|
|
|
const fetchedCards = await getCard(taskId, 'priorities')
|
|
|
|
if(!fetchedCards || fetchedCards.length < 1) {
|
|
|
|
console.log('Failed to fetch member card, this is bad.')
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const card = fetchedCards[0]
|
|
|
|
let priorityCards = fetchedCards.slice(1) // First card is member card itself
|
|
|
|
let priorities = card.priorities.slice()
|
|
|
|
priorities.reverse()
|
|
|
|
// Fix order - incoming card order is absolute order on server, not order within this card
|
|
|
|
priorityCards = priorities.map((priorityTaskId, i) => {
|
|
|
|
const priorityCard = priorityCards.find(p => p.taskId === priorityTaskId)
|
|
|
|
if(!priorityCard) {
|
|
|
|
return 'Missing card, repair your database'
|
|
|
|
}
|
|
|
|
return priorityCard
|
|
|
|
})
|
|
|
|
console.log('You have', priorityCards.length, 'priorities:')
|
|
|
|
prioritiesChoices = priorities.map((priorityTaskId, i) => {
|
|
|
|
const priorityCard = priorityCards.find(p => p.taskId === priorityTaskId)
|
|
|
|
if(!priorityCard) {
|
|
|
|
return 'Missing card, repair your database'
|
|
|
|
}
|
|
|
|
const shortenedName = priorityCard.name.substring(0, 70) + (priorityCard.name.length >= 70 ? '...' : '')
|
|
|
|
return {
|
|
|
|
title: shortenedName,
|
|
|
|
value: { index: i, card: priorityCard },
|
|
|
|
short: shortenedName
|
|
|
|
}
|
|
|
|
})
|
|
|
|
let firstIndexEchelonDecreases
|
|
|
|
let firstEchelon = priorityCards.length >= 1 ? priorityCards[0].echelon : null
|
|
|
|
if(firstEchelon) {
|
|
|
|
priorityCards.some((pc, i) => {
|
|
|
|
if(!pc.echelon || pc.echelon !== firstEchelon) {
|
|
|
|
firstIndexEchelonDecreases = i
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
})
|
|
|
|
if(!isNaN(firstIndexEchelonDecreases)) {
|
|
|
|
prioritiesChoices.splice(firstIndexEchelonDecreases, 0, { title: '───', disabled: true })
|
|
|
|
}
|
|
|
|
}
|
|
|
|
prioritiesChoices.push(
|
|
|
|
{ title: 'Play priority', value: 'create_here', short: 'new priority' },
|
|
|
|
{ title: 'Back to Deck', value: false, short: 'back' }
|
|
|
|
)
|
|
|
|
const answer = await promptMenu(prioritiesChoices, 'My Priorities', undefined, undefined, 'Prioritized cards will be inserted above this line')
|
|
|
|
switch(answer) {
|
|
|
|
case false:
|
|
|
|
return false
|
|
|
|
case 'create_here':
|
|
|
|
let previousCardCreatedText
|
|
|
|
do {
|
|
|
|
previousCardCreatedText = await createCardInteractive(true)
|
|
|
|
} while(previousCardCreatedText != '\n')
|
|
|
|
return true
|
|
|
|
case 'Missing card, repair your database':
|
|
|
|
console.log('Database repair yet implemented, sorry.')
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
let chosenTask = answer.card
|
|
|
|
const chosenTaskId = chosenTask.taskId
|
|
|
|
let previousAnswer
|
|
|
|
do {
|
|
|
|
previousAnswer = await priorityCardMenu(chosenTask, answer.index, priorityCards)
|
|
|
|
if(previousAnswer) {
|
|
|
|
const fetchedCards = await getCard(chosenTaskId, false)
|
|
|
|
if(!fetchedCards || fetchedCards.length < 1) {
|
|
|
|
console.log('The card has disappeared. Maybe it was deleted, or cards held by no one are automatically cleaned up every five minutes.')
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
chosenTask = fetchedCards[0]
|
|
|
|
}
|
|
|
|
} while(previousAnswer !== false)
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Short action-oriented menu for cards in the priorities list
|
|
|
|
// Index is the position of the card in the list that it is in, used for fencepost case to display upboat contextually
|
|
|
|
// allPriorities is an array of task objects for the other priorities in the priorities list this card is in (adjacent to this card)
|
|
|
|
async function priorityCardMenu(card, index, allPriorities) {
|
|
|
|
if(!card) {
|
|
|
|
console.log('priorityCardMenu: card is required.')
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const taskId = card.taskId
|
|
|
|
const memberId = aoEnv('AO_CLI_SESSION_MEMBERID')
|
|
|
|
if(!memberId) {
|
|
|
|
console.log('Not logged in.')
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
const isChecked = card.claimed.includes(memberId)
|
|
|
|
let priorityChoices = []
|
|
|
|
if(index != 0) {
|
|
|
|
priorityChoices.push({ title: 'Prioritize (upboat)', value: 'upboat', short: 'upboat' })
|
|
|
|
}
|
|
|
|
priorityChoices.push(
|
|
|
|
{ title: isChecked ? 'Uncheck' : 'Check off', value: 'check', short: 'check!' },
|
|
|
|
{ title: 'Discard from priorities (downboat)', value: 'downboat', short: 'downboat' },
|
|
|
|
//{ title: 'Browse within', value: 'browse', short: 'browse' }
|
|
|
|
{ title: 'Back to Priorities', value: false, short: 'back' }
|
|
|
|
)
|
|
|
|
const answer = await promptMenu(priorityChoices, 'Priority: ' + card.name)
|
|
|
|
switch(answer) {
|
|
|
|
case 'check':
|
|
|
|
if(isChecked) {
|
|
|
|
await uncheckCard(taskId)
|
|
|
|
} else {
|
|
|
|
await completeCard(taskId)
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case 'upboat':
|
|
|
|
const { newPosition, newEchelonScore } = getNewHighestEchelonScore(card.echelon, allPriorities)
|
|
|
|
await prioritizeCard(taskId, memberId, newPosition, newEchelonScore)
|
|
|
|
return false
|
|
|
|
case 'downboat':
|
|
|
|
await refocusCard(taskId, memberId)
|
|
|
|
return false
|
|
|
|
case 'browse':
|
|
|
|
break
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Calculates and returns what the echelon score should be to prioritize a priority with the given echelon score to the correct place
|
|
|
|
// in tnhe given list of tasks.
|
|
|
|
// newPriorityEchelonScore is the .echolon of the task object about to be prioritized
|
|
|
|
// allPriorities is an array of task objects for the other priorities in the priorities list this card is in (adjacent to this card)
|
|
|
|
export function getNewHighestEchelonScore(newPriorityEchelon, allPriorities) {
|
|
|
|
let firstEchelonScore
|
|
|
|
let newEchelonScore
|
|
|
|
let newPosition = 0
|
|
|
|
for(let i = 0; i < allPriorities.length; i++) {
|
|
|
|
const priority = allPriorities[i]
|
|
|
|
if(i === 0) {
|
|
|
|
firstEchelonScore = priority.echelon
|
|
|
|
if(isNaN(firstEchelonScore)) {
|
|
|
|
// Top priority does have a (valid) echelon score, so echelon = 0 and the new echelon score should be 1
|
|
|
|
newEchelonScore = 1
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if(!newPriorityEchelon || newPriorityEchelon < priority.echelon) {
|
|
|
|
// Prioritized card does not have an echelon score or it is less than the current priority's echelon score (so keep going to find insert position)
|
|
|
|
newEchelonScore = priority.echelon
|
|
|
|
//break
|
|
|
|
} else if(newPriorityEchelon && priority.echelon && newPriorityEchelon === priority.echelon) {
|
|
|
|
// Echelon of new priority is same as top priority, so increase it by one (and it goes to top)
|
|
|
|
newEchelonScore = priority.echelon + 1
|
|
|
|
break
|
|
|
|
} else if(newPriorityEchelon && priority.echelon && newPriorityEchelon > priority.echelon) {
|
|
|
|
// Echelon of new priority is already greater than the new priority, so its echelon doesn't change (and it goes to top)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(priority.echelon !== firstEchelonScore) {
|
|
|
|
newPosition = i
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return { newPosition, newEchelonScore }
|
|
|
|
}
|