-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'develop' into generate-diff-2024-02-23
- Loading branch information
Showing
7 changed files
with
333 additions
and
268 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
64 changes: 64 additions & 0 deletions
64
...p/views/dashboard/governance/components/proposal-details/ProposalAccountVotingPane.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<script lang="ts"> | ||
import { Table, Text } from '@bloomwalletio/ui' | ||
import { | ||
participationOverviewForSelectedAccount, | ||
selectedParticipationEventStatus, | ||
selectedProposal, | ||
} from '@contexts/governance/stores' | ||
import { calculateTotalVotesForTrackedParticipations } from '@contexts/governance/utils' | ||
import { selectedAccount } from '@core/account/stores' | ||
import { localize } from '@core/i18n' | ||
import { networkStatus } from '@core/network/stores' | ||
import { activeProfile } from '@core/profile/stores' | ||
import { formatTokenAmountBestMatch } from '@core/token' | ||
import { visibleSelectedAccountTokens } from '@core/token/stores' | ||
import { EventStatus } from '@iota/sdk/out/types' | ||
import { Pane } from '@ui' | ||
const { metadata } = $visibleSelectedAccountTokens?.[$activeProfile?.network?.id]?.baseCoin ?? {} | ||
let totalVotes = BigInt(0) | ||
const hasMounted = false | ||
$: selectedProposalOverview = $participationOverviewForSelectedAccount?.participations?.[$selectedProposal?.id] | ||
$: trackedParticipations = Object.values(selectedProposalOverview ?? {}) | ||
$: currentMilestone = $networkStatus.currentMilestone | ||
// Reactively start updating votes once component has mounted and participation overview is available. | ||
$: hasMounted && $selectedParticipationEventStatus && trackedParticipations && currentMilestone && setTotalVotes() | ||
function setTotalVotes(): void { | ||
switch ($selectedParticipationEventStatus?.status) { | ||
case EventStatus.Upcoming: | ||
totalVotes = BigInt(0) | ||
break | ||
case EventStatus.Commencing: | ||
totalVotes = BigInt(0) | ||
break | ||
case EventStatus.Holding: | ||
totalVotes = calculateTotalVotesForTrackedParticipations(trackedParticipations) | ||
break | ||
case EventStatus.Ended: | ||
totalVotes = calculateTotalVotesForTrackedParticipations(trackedParticipations) | ||
break | ||
} | ||
} | ||
</script> | ||
|
||
<Pane classes="p-6 h-fit shrink-0 space-y-5"> | ||
<Text type="body2"> | ||
{localize('views.governance.details.yourVote.title')} | ||
</Text> | ||
<Table | ||
items={[ | ||
{ | ||
key: localize('views.governance.details.yourVote.total'), | ||
value: formatTokenAmountBestMatch(totalVotes, metadata), | ||
}, | ||
{ | ||
key: localize('views.governance.details.yourVote.power'), | ||
value: formatTokenAmountBestMatch($selectedAccount?.votingPower, metadata), | ||
}, | ||
]} | ||
/> | ||
</Pane> |
21 changes: 21 additions & 0 deletions
21
...desktop/views/dashboard/governance/components/proposal-details/ProposalDetailsPane.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
<script lang="ts"> | ||
import { MarkdownBlock, Text } from '@bloomwalletio/ui' | ||
import { Pane } from '@ui' | ||
import { ProposalDetailsMenu, ProposalStatusPill } from '../' | ||
import { IProposal } from '@contexts/governance' | ||
export let proposal: IProposal | ||
</script> | ||
|
||
<Pane classes="p-6 flex flex-col h-fit"> | ||
<header-container class="flex justify-between items-center mb-4"> | ||
<ProposalStatusPill {proposal} /> | ||
<ProposalDetailsMenu {proposal} modalPosition={{ right: '24px', top: '54px' }} /> | ||
</header-container> | ||
<div class="flex flex-1 flex-col space-y-4 justify-between scrollable-y"> | ||
<Text type="h4">{proposal?.title}</Text> | ||
{#if proposal?.additionalInfo} | ||
<MarkdownBlock text={proposal?.additionalInfo} /> | ||
{/if} | ||
</div> | ||
</Pane> |
File renamed without changes.
234 changes: 234 additions & 0 deletions
234
...op/views/dashboard/governance/components/proposal-details/ProposalQuestionListPane.svelte
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,234 @@ | ||
<script lang="ts"> | ||
import { | ||
EventStatus, | ||
ParticipationEventType, | ||
VotingEventPayload, | ||
TrackedParticipationOverview, | ||
} from '@iota/sdk/out/types' | ||
import { Alert, Button } from '@bloomwalletio/ui' | ||
import { getVotingEvent } from '@contexts/governance/actions' | ||
import { ABSTAIN_VOTE_VALUE } from '@contexts/governance/constants' | ||
import { | ||
participationOverviewForSelectedAccount, | ||
selectedParticipationEventStatus, | ||
selectedProposal, | ||
} from '@contexts/governance/stores' | ||
import { getActiveParticipation, isProposalVotable, isVotingForSelectedProposal } from '@contexts/governance/utils' | ||
import { selectedAccount } from '@core/account/stores' | ||
import { handleError } from '@core/error/handlers' | ||
import { localize } from '@core/i18n' | ||
import { networkStatus } from '@core/network/stores' | ||
import { getBestTimeDuration, milestoneToDate } from '@core/utils' | ||
import { PopupId, openPopup } from '@desktop/auxiliary/popup' | ||
import { ProposalQuestion } from '../../components' | ||
import { Pane } from '@ui' | ||
import { onMount } from 'svelte' | ||
export let statusLoaded: boolean = false | ||
export let overviewLoaded: boolean = false | ||
let selectedAnswerValues: number[] = [] | ||
let votedAnswerValues: number[] = [] | ||
let votingPayload: VotingEventPayload | ||
let hasMounted = false | ||
let alertText = '' | ||
let proposalQuestions: HTMLElement | ||
let isVotingForProposal: boolean = false | ||
let openedQuestionIndex: number = -1 | ||
let isUpdatingVotedAnswerValues: boolean = false | ||
let lastAction: 'vote' | 'stopVote' | ||
$: selectedProposalOverview = $participationOverviewForSelectedAccount?.participations?.[$selectedProposal?.id] | ||
$: trackedParticipations = Object.values(selectedProposalOverview ?? {}) | ||
$: currentMilestone = $networkStatus.currentMilestone | ||
// Reactively start updating votes once component has mounted and participation overview is available. | ||
$: hasMounted && | ||
$selectedParticipationEventStatus && | ||
trackedParticipations && | ||
currentMilestone && | ||
setVotedAnswerValues() | ||
$: hasMounted && selectedProposalOverview && updateIsVoting() | ||
$: questions = votingPayload?.questions | ||
$: if (questions?.length > 0 && selectedAnswerValues?.length === 0) { | ||
selectedAnswerValues = [ | ||
...(getActiveParticipation($selectedProposal?.id)?.answers ?? Array.from({ length: questions?.length })), | ||
] | ||
} | ||
$: $selectedParticipationEventStatus, (alertText = getAlertText()) | ||
$: hasGovernanceTransactionInProgress = | ||
$selectedAccount?.hasVotingPowerTransactionInProgress || $selectedAccount?.hasVotingTransactionInProgress | ||
$: areSelectedAndVotedAnswersEqual = JSON.stringify(selectedAnswerValues) === JSON.stringify(votedAnswerValues) | ||
$: { | ||
if (hasGovernanceTransactionInProgress) { | ||
isUpdatingVotedAnswerValues = true | ||
} | ||
const hasVoted = lastAction === 'vote' && areSelectedAndVotedAnswersEqual | ||
const hasStoppedVoting = lastAction === 'stopVote' && !areSelectedAndVotedAnswersEqual | ||
if (hasVoted || hasStoppedVoting) { | ||
isUpdatingVotedAnswerValues = hasGovernanceTransactionInProgress | ||
} | ||
} | ||
function hasSelectedNoAnswers(_selectedAnswerValues: number[]): boolean { | ||
return ( | ||
_selectedAnswerValues.length === 0 || | ||
_selectedAnswerValues.every((answerValue) => answerValue === undefined) | ||
) | ||
} | ||
async function setVotingEventPayload(eventId: string): Promise<void> { | ||
try { | ||
const event = await getVotingEvent(eventId) | ||
if (!event) { | ||
throw new Error('Event not found') | ||
} | ||
if (event.data?.payload?.type === ParticipationEventType.Voting) { | ||
votingPayload = event.data.payload | ||
} else { | ||
throw new Error('Event is a staking event') | ||
} | ||
} catch (err) { | ||
handleError(err) | ||
} | ||
} | ||
function updateIsVoting(): void { | ||
isVotingForProposal = isVotingForSelectedProposal() | ||
} | ||
function setVotedAnswerValues(): void { | ||
let lastActiveOverview: TrackedParticipationOverview | ||
switch ($selectedParticipationEventStatus?.status) { | ||
case EventStatus.Commencing: | ||
lastActiveOverview = trackedParticipations?.find((overview) => overview.endMilestoneIndex === 0) | ||
break | ||
case EventStatus.Holding: | ||
lastActiveOverview = trackedParticipations?.find((overview) => overview.endMilestoneIndex === 0) | ||
break | ||
case EventStatus.Ended: | ||
lastActiveOverview = trackedParticipations?.find( | ||
(overview) => overview.endMilestoneIndex > $selectedProposal.milestones.ended | ||
) | ||
break | ||
} | ||
votedAnswerValues = lastActiveOverview?.answers ?? [] | ||
} | ||
function onQuestionClick(questionIndex: number): void { | ||
openedQuestionIndex = questionIndex === openedQuestionIndex ? null : questionIndex | ||
} | ||
function onStopVotingClick(): void { | ||
lastAction = 'stopVote' | ||
openPopup({ | ||
id: PopupId.StopVoting, | ||
}) | ||
} | ||
function onVoteClick(): void { | ||
lastAction = 'vote' | ||
const chosenAnswerValues = selectedAnswerValues.map((answerValue) => | ||
answerValue === undefined ? ABSTAIN_VOTE_VALUE : answerValue | ||
) | ||
openPopup({ | ||
id: PopupId.VoteForProposal, | ||
props: { selectedAnswerValues: chosenAnswerValues }, | ||
}) | ||
} | ||
function onAnswerClick(answerValue: number, questionIndex: number): void { | ||
selectedAnswerValues[questionIndex] = answerValue | ||
openedQuestionIndex = questionIndex + 1 | ||
const selectedQuestionElement: HTMLElement = proposalQuestions?.querySelector( | ||
`proposal-question:nth-child(${openedQuestionIndex})` | ||
) | ||
setTimeout(() => { | ||
proposalQuestions.scrollTo({ top: selectedQuestionElement?.offsetTop, behavior: 'smooth' }) | ||
}, 250) | ||
} | ||
function getAlertText(): string { | ||
if (!$selectedProposal) { | ||
return '' | ||
} | ||
const millis = | ||
milestoneToDate( | ||
$networkStatus.currentMilestone, | ||
$selectedProposal.milestones[EventStatus.Commencing] | ||
).getTime() - new Date().getTime() | ||
const timeString = getBestTimeDuration(millis, 'second') | ||
return localize('views.governance.details.hintVote', { values: { time: timeString } }) | ||
} | ||
onMount(async () => { | ||
await setVotingEventPayload($selectedProposal?.id) | ||
openedQuestionIndex = votingPayload?.questions.length > 1 ? -1 : 0 | ||
hasMounted = true | ||
}) | ||
</script> | ||
|
||
<Pane classes="w-3/5 h-full p-6 pr-3 flex flex-col justify-between"> | ||
<proposal-questions | ||
class="relative flex flex-1 flex-col space-y-5 overflow-y-scroll pr-3" | ||
bind:this={proposalQuestions} | ||
> | ||
{#if questions} | ||
{#each questions as question, questionIndex} | ||
<ProposalQuestion | ||
{question} | ||
{questionIndex} | ||
isOpened={openedQuestionIndex === questionIndex} | ||
isLoading={!overviewLoaded || !statusLoaded} | ||
selectedAnswerValue={selectedAnswerValues[questionIndex]} | ||
votedAnswerValue={votedAnswerValues[questionIndex]} | ||
answerStatuses={$selectedParticipationEventStatus?.questions[questionIndex]?.answers} | ||
{onQuestionClick} | ||
{onAnswerClick} | ||
/> | ||
{/each} | ||
{/if} | ||
</proposal-questions> | ||
{#if $selectedProposal?.status === EventStatus.Upcoming} | ||
<Alert variant="info" text={alertText} /> | ||
{:else if [EventStatus.Commencing, EventStatus.Holding].includes($selectedProposal?.status)} | ||
{@const isLoaded = questions && overviewLoaded && statusLoaded} | ||
{@const isStoppingVote = lastAction === 'stopVote' && hasGovernanceTransactionInProgress} | ||
{@const isStopVotingDisabled = !isLoaded || !isVotingForProposal || isUpdatingVotedAnswerValues} | ||
{@const isVoting = lastAction === 'vote' && hasGovernanceTransactionInProgress} | ||
{@const isVotingDisabled = | ||
!isLoaded || | ||
!isProposalVotable($selectedProposal?.status) || | ||
hasSelectedNoAnswers(selectedAnswerValues) || | ||
isUpdatingVotedAnswerValues || | ||
areSelectedAndVotedAnswersEqual} | ||
<buttons-container class="flex w-full space-x-4 mt-6"> | ||
<Button | ||
variant="outlined" | ||
width="full" | ||
on:click={onStopVotingClick} | ||
disabled={isStopVotingDisabled} | ||
busy={isStoppingVote} | ||
text={localize('actions.stopVoting')} | ||
/> | ||
<Button | ||
width="full" | ||
on:click={onVoteClick} | ||
disabled={isVotingDisabled} | ||
busy={isVoting} | ||
text={localize('actions.vote')} | ||
/> | ||
</buttons-container> | ||
{/if} | ||
</Pane> |
4 changes: 4 additions & 0 deletions
4
packages/desktop/views/dashboard/governance/components/proposal-details/index.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
export { default as ProposalAccountVotingPane } from './ProposalAccountVotingPane.svelte' | ||
export { default as ProposalDetailsPane } from './ProposalDetailsPane.svelte' | ||
export { default as ProposalInformationPane } from './ProposalInformationPane.svelte' | ||
export { default as ProposalQuestionListPane } from './ProposalQuestionListPane.svelte' |
Oops, something went wrong.