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">
|
||||
// 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>
|
||||
|
||||
<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" />
|
||||
{:else if name === 'plus'}
|
||||
<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'}
|
||||
<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}
|
||||
|
||||
@@ -125,6 +125,18 @@ export interface Proposal {
|
||||
translations: Partial<Record<Lang, Translation>>;
|
||||
/** An order version: the order it gives its section now. */
|
||||
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';
|
||||
@@ -156,6 +168,11 @@ export interface VoteAccess {
|
||||
open: boolean;
|
||||
reason: 'members_only' | 'chain_off' | 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. */
|
||||
|
||||
+14
-1
@@ -6,7 +6,7 @@ import type { DocState } from './doc.svelte';
|
||||
import { i18n, t, type Key } from './i18n/index.svelte';
|
||||
import { toasts } from './toast.svelte';
|
||||
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. */
|
||||
export interface Line {
|
||||
@@ -14,6 +14,19 @@ export interface Line {
|
||||
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. */
|
||||
export function g1Amount(cents: number): string {
|
||||
return i18n.number(cents / 100);
|
||||
|
||||
Reference in New Issue
Block a user