forked from yvv/decision
Lot U, travail en cours : icône poignée, crans dessinés (Crans), champs annoncés de S et V, canCast et byChain
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,42 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// The inertia of a module, drawn: five notches side by side, the level filled
|
||||||
|
// (ADR 0007, « cinq crans »). The water level of a vote stands upright; the
|
||||||
|
// weight of a text lies flat — two shapes one does not confuse. The words stay
|
||||||
|
// beside it: « inertie 3 / 5 · moyenne », or only « moyenne » when `short`
|
||||||
|
// (a screen reader still hears the whole).
|
||||||
|
import { t } from '$lib/i18n/index.svelte';
|
||||||
|
import { inertiaBadge } from '$lib/labels';
|
||||||
|
import type { Inertia } from '$lib/types';
|
||||||
|
|
||||||
|
let { level, short = false }: { level: Inertia; short?: boolean } = $props();
|
||||||
|
const full = $derived(inertiaBadge(level));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="crans" title={full}>
|
||||||
|
<span class="notches" aria-hidden="true">
|
||||||
|
{#each [1, 2, 3, 4, 5] as k (k)}<span class="notch" class:on={k <= level}></span>{/each}
|
||||||
|
</span>
|
||||||
|
{#if short}<span class="visually-hidden">{full}</span><span aria-hidden="true">{t(`inertia.name.${level}`)}</span>{:else}<span>{full}</span>{/if}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.crans {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.4rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.notches {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 2px;
|
||||||
|
}
|
||||||
|
.notch {
|
||||||
|
width: 5px;
|
||||||
|
height: 0.62rem;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: color-mix(in srgb, var(--ink-faint) 28%, transparent);
|
||||||
|
}
|
||||||
|
.notch.on {
|
||||||
|
background: var(--ink-soft);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
// A few line icons, drawn here rather than pulled from a library.
|
// A few line icons, drawn here rather than pulled from a library.
|
||||||
let { name, size = 18 }: { name: 'lock' | 'close' | 'menu' | 'moon' | 'sun' | 'chevron' | 'message' | 'key' | 'plus' | 'inbox'; size?: number } = $props();
|
let { name, size = 18 }: { name: 'lock' | 'close' | 'menu' | 'moon' | 'sun' | 'chevron' | 'message' | 'key' | 'plus' | 'inbox' | 'grip'; size?: number } = $props();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
|
||||||
@@ -22,6 +22,8 @@
|
|||||||
<circle cx="8" cy="15" r="4" /><path d="M11 12l8-8M16 7l2 2M14 9l2 2" />
|
<circle cx="8" cy="15" r="4" /><path d="M11 12l8-8M16 7l2 2M14 9l2 2" />
|
||||||
{:else if name === 'plus'}
|
{:else if name === 'plus'}
|
||||||
<path d="M12 5v14M5 12h14" />
|
<path d="M12 5v14M5 12h14" />
|
||||||
|
{:else if name === 'grip'}
|
||||||
|
<g fill="currentColor" stroke="none"><circle cx="9" cy="6" r="1.6" /><circle cx="15" cy="6" r="1.6" /><circle cx="9" cy="12" r="1.6" /><circle cx="15" cy="12" r="1.6" /><circle cx="9" cy="18" r="1.6" /><circle cx="15" cy="18" r="1.6" /></g>
|
||||||
{:else if name === 'inbox'}
|
{:else if name === 'inbox'}
|
||||||
<path d="M4 13l2.2-7.3A1 1 0 0 1 7.2 5h9.6a1 1 0 0 1 1 .7L20 13v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z" /><path d="M4 13h4.5l1 2h5l1-2H20" />
|
<path d="M4 13l2.2-7.3A1 1 0 0 1 7.2 5h9.6a1 1 0 0 1 1 .7L20 13v5a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1z" /><path d="M4 13h4.5l1 2h5l1-2H20" />
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -125,6 +125,18 @@ export interface Proposal {
|
|||||||
translations: Partial<Record<Lang, Translation>>;
|
translations: Partial<Record<Lang, Translation>>;
|
||||||
/** An order version: the order it gives its section now. */
|
/** An order version: the order it gives its section now. */
|
||||||
order?: OrderInfo;
|
order?: OrderInfo;
|
||||||
|
/**
|
||||||
|
* Lot S: a « Proposition seed », proposed at the start to set the vote going — not in
|
||||||
|
* force. Absent before lot S: read as false.
|
||||||
|
*/
|
||||||
|
seedProposal?: boolean;
|
||||||
|
/**
|
||||||
|
* Lot V: where this session's vote stands — recorded, waiting (the chain, the buffer of
|
||||||
|
* intentions), or simulated while the chain is closed. Absent before lot V.
|
||||||
|
*/
|
||||||
|
myVoteState?: 'recorded' | 'pending' | 'simulated' | null;
|
||||||
|
/** Lot V: votes cast and not counted yet (the buffer of intentions). */
|
||||||
|
tallyPending?: Tally;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Choice = 'for' | 'against' | 'none';
|
export type Choice = 'for' | 'against' | 'none';
|
||||||
@@ -156,6 +168,11 @@ export interface VoteAccess {
|
|||||||
open: boolean;
|
open: boolean;
|
||||||
reason: 'members_only' | 'chain_off' | null;
|
reason: 'members_only' | 'chain_off' | null;
|
||||||
chain: ChainSettings | null;
|
chain: ChainSettings | null;
|
||||||
|
/**
|
||||||
|
* Lot V: the vote is simulated while the chain is closed — the buttons show as if it
|
||||||
|
* were open, marked « vote simulé ». Absent before lot V.
|
||||||
|
*/
|
||||||
|
simulated?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** A ballot of the chain, as `GET /api/ballots/{id}` tells it to the session that asked. */
|
/** A ballot of the chain, as `GET /api/ballots/{id}` tells it to the session that asked. */
|
||||||
|
|||||||
+14
-1
@@ -6,7 +6,7 @@ import type { DocState } from './doc.svelte';
|
|||||||
import { i18n, t, type Key } from './i18n/index.svelte';
|
import { i18n, t, type Key } from './i18n/index.svelte';
|
||||||
import { toasts } from './toast.svelte';
|
import { toasts } from './toast.svelte';
|
||||||
import { moduleName, sectionLabel } from './labels';
|
import { moduleName, sectionLabel } from './labels';
|
||||||
import type { Entered } from './types';
|
import type { Entered, VoteAccess } from './types';
|
||||||
|
|
||||||
/** A line of a vote: one version, for or against, or null to withdraw one's vote. */
|
/** A line of a vote: one version, for or against, or null to withdraw one's vote. */
|
||||||
export interface Line {
|
export interface Line {
|
||||||
@@ -14,6 +14,19 @@ export interface Line {
|
|||||||
choice: 'for' | 'against' | null;
|
choice: 'for' | 'against' | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this session sees the buttons of the vote: open, or simulated while the chain is
|
||||||
|
* closed (lot V: « les boutons de vote apparaissent partout comme si le vote était ouvert »).
|
||||||
|
*/
|
||||||
|
export function canCast(access: VoteAccess | null | undefined): boolean {
|
||||||
|
return !!access && (access.open || access.simulated === true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** A vote that goes through the person's Ğ1 wallet; a simulated vote never does. */
|
||||||
|
export function byChain(access: VoteAccess | null | undefined): boolean {
|
||||||
|
return access?.way === 'chain' && access.simulated !== true && !!access.chain;
|
||||||
|
}
|
||||||
|
|
||||||
/** Cents of Ğ1, as the person reads them: « 0,1 » in French. */
|
/** Cents of Ğ1, as the person reads them: « 0,1 » in French. */
|
||||||
export function g1Amount(cents: number): string {
|
export function g1Amount(cents: number): string {
|
||||||
return i18n.number(cents / 100);
|
return i18n.number(cents / 100);
|
||||||
|
|||||||
Reference in New Issue
Block a user