// 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, postEvent } 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 postEvent({ type: 'task-unclaimed', taskId: taskId, memberId: memberId }) } else { await postEvent({ type: 'task-claimed', taskId: taskId, memberId: memberId }) } break case 'upboat': const { newPosition, newEchelonScore } = getNewHighestEchelonScore(card.echelon, allPriorities) await postEvent({ type: 'task-prioritized', taskId: taskId, inId: memberId, position: newPosition, ...(newEchelonScore && { echelon: newEchelonScore }), }) return false case 'downboat': await postEvent({ type: 'task-refocused', taskId: taskId, inId: 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 } }