Этот коммит содержится в:
Maarten 2024-10-11 10:00:39 +02:00
родитель 3ddd9c467a
Коммит ad33923c37
3 изменённых файлов: 125 добавлений и 80 удалений

20
src/lib/components/CardModal.svelte Обычный файл
Просмотреть файл

@ -0,0 +1,20 @@
<script>
import CaseCard from "./CaseCard.svelte";
export let modalOpen
export let activeCaseData
function closeModal(){
modalOpen = false
}
</script>
<div id="card-modal" class={modalOpen ? "modal is-active" : "modal"}>
<div class="modal-background" on:click={closeModal}></div>
<div class="modal-content">
{#if activeCaseData}
<CaseCard cardData={activeCaseData} expanded={true} ></CaseCard>
{/if}
</div>
<div class="modal-close" on:click={closeModal}></div>
</div>

Просмотреть файл

@ -3,7 +3,16 @@
import { utcFormat } from 'd3-time-format'; import { utcFormat } from 'd3-time-format';
import { platformFilter, actorNationFilter, sourceFilter } from '../../stores/filters'; import { platformFilter, actorNationFilter, sourceFilter } from '../../stores/filters';
import ScoreBar from '$lib/components/ScoreBar.svelte'; import ScoreBar from '$lib/components/ScoreBar.svelte';
export let cardData; export let cardData;
export let expanded;
export let modalOpen;
export let activeCaseData
let openCase = function(caseID){
modalOpen = true
activeCaseData = cardData
}
</script> </script>
<div class="card" transition:fade id={'case-' + cardData.attribution_id}> <div class="card" transition:fade id={'case-' + cardData.attribution_id}>
@ -12,27 +21,29 @@
<h2 class="is-size-5">{cardData.short_title}</h2> <h2 class="is-size-5">{cardData.short_title}</h2>
</div> </div>
</div> </div>
<div class="score-bars"> {#if expanded}
<div class="score-bar-wrapper"> <div class="score-bars">
<ScoreBar value={cardData.credibility} maxValue={5} /> <div class="score-bar-wrapper">
<p>Credibility</p> <ScoreBar value={cardData.credibility} maxValue={5} />
</div> <p>Credibility</p>
<div class="score-bar-wrapper"> </div>
<ScoreBar value={cardData.objectivity} maxValue={3} /> <div class="score-bar-wrapper">
<p>Objectivity</p> <ScoreBar value={cardData.objectivity} maxValue={3} />
</div> <p>Objectivity</p>
<div class="score-bar-wrapper"> </div>
<ScoreBar value={cardData.evidence} maxValue={5} /> <div class="score-bar-wrapper">
<p>Evidence</p> <ScoreBar value={cardData.evidence} maxValue={5} />
</div> <p>Evidence</p>
<div class="score-bar-wrapper"> </div>
<ScoreBar value={cardData.transparency} maxValue={5} /> <div class="score-bar-wrapper">
<p>Transparency</p> <ScoreBar value={cardData.transparency} maxValue={5} />
</div> <p>Transparency</p>
<!--span class="score-info-icon disable-select" on:click|self={() => scoreQuestionsExpanded = !scoreQuestionsExpanded}> </div>
<!--span class="score-info-icon disable-select" on:click|self={() => scoreQuestionsExpanded = !scoreQuestionsExpanded}>
{scoreQuestionsExpanded ? 'X' : '?'} {scoreQuestionsExpanded ? 'X' : '?'}
</span--> </span-->
</div> </div>
{/if}
<div class="card-image"> <div class="card-image">
<figure class="image"> <figure class="image">
<img src={`/images/${cardData.attribution_id}.jpg`} /> <img src={`/images/${cardData.attribution_id}.jpg`} />
@ -40,33 +51,40 @@
</div> </div>
<div class="card-content"> <div class="card-content">
<div class="content"> <div class="content">
<p>{utcFormat('%B %d, %Y')(new Date(cardData.attribution_date))}</p> {#if expanded}
<p><a href={cardData.attribution_url} target="_blank">{cardData.source}</a></p> <p>{utcFormat('%B %d, %Y')(new Date(cardData.attribution_date))}</p>
<p><a href={cardData.attribution_url} target="_blank">{cardData.source}</a></p>
{/if}
<p>{cardData.short_description}</p> <p>{cardData.short_description}</p>
<p> {#if expanded}
<button class="button is-info is-small" on:click={sourceFilter.selectOne(cardData.source)} <p>
>{cardData.source}</button <button class="button is-info is-small" on:click={sourceFilter.selectOne(cardData.source)}
> >{cardData.source}</button
{#each cardData.actor_nation as nation}
<button class="button is-danger is-small" on:click={actorNationFilter.selectOne(nation)}
>{nation}</button
> >
{/each} {#each cardData.actor_nation as nation}
{#each cardData.platform as platform} <button class="button is-danger is-small" on:click={actorNationFilter.selectOne(nation)}
<button class="button is-link is-small" on:click={platformFilter.selectOne(platform)} >{nation}</button
>{platform}</button >
> {/each}
{/each} {#each cardData.platform as platform}
</p> <button class="button is-link is-small" on:click={platformFilter.selectOne(platform)}
>{platform}</button
>
{/each}
</p>
{/if}
</div> </div>
</div> </div>
{#if !expanded}
<footer class="card-footer">
<button on:click={openCase(cardData.attribution_id)} class="card-footer-item">Open case</button>
</footer>
{/if}
</div> </div>
<style> <style>
div.card { div.card {
max-width: 780px; max-width: 780px;
/*max-height: 600px;
overflow-y: scroll;*/
} }
.score-bars { .score-bars {
display: flex; display: flex;

Просмотреть файл

@ -3,12 +3,13 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { csv } from 'd3-fetch'; import { csv } from 'd3-fetch';
import { max, extent } from 'd3-array'; import { max, extent } from 'd3-array';
import Header from '$lib/components/Header.svelte'; import Header from '$lib/components/Header.svelte';
import CaseCard from '$lib/components/CaseCard.svelte'; import CaseCard from '$lib/components/CaseCard.svelte';
import CaseTable from '$lib/components/CaseTable.svelte'; import CaseTable from '$lib/components/CaseTable.svelte';
import Timeline from '$lib/components/Timeline.svelte'; import Timeline from '$lib/components/Timeline.svelte';
import TimelineMobile from '$lib/components/TimelineMobile.svelte'; import TimelineMobile from '$lib/components/TimelineMobile.svelte';
import Controls from '$lib/components/Controls.svelte'; import Controls from '$lib/components/Controls.svelte';
import CardModal from '$lib/components/CardModal.svelte';
import AnimatedFilterIcon from '$lib/components/AnimatedFilterIcon.svelte'; import AnimatedFilterIcon from '$lib/components/AnimatedFilterIcon.svelte';
import { splitString, haveOverlap, withinRange, includesTextSearch } from '$lib/utils/misc'; import { splitString, haveOverlap, withinRange, includesTextSearch } from '$lib/utils/misc';
//import { setScales } from '$lib/utils/scales'; //import { setScales } from '$lib/utils/scales';
@ -21,7 +22,7 @@
sourceFilter, sourceFilter,
sourceCategoryFilter, sourceCategoryFilter,
methodFilter, methodFilter,
campaignFilter, campaignFilter,
attributionScoreFilter, attributionScoreFilter,
attributionScoreDef, attributionScoreDef,
textSearchFilter, textSearchFilter,
@ -29,8 +30,8 @@
fullTimeRange fullTimeRange
} from '../stores/filters'; } from '../stores/filters';
$: console.log($timeRangeFilter) //$: console.log($timeRangeFilter)
//$: console.log($fullTimeRange) //$: console.log($fullTimeRange)
$: innerWidth = 0; $: innerWidth = 0;
$: isMobile = innerWidth < 520; $: isMobile = innerWidth < 520;
@ -39,21 +40,21 @@
let cases = []; let cases = [];
let events = []; let events = [];
let metrics = []; let metrics = [];
let maxAttribution = 0; let maxAttribution = 0;
onMount(async function () { onMount(async function () {
const response = await csv( const response = await csv(
`https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv` `https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv`
); );
cases = response; cases = response;
cases = cases.filter((d) => d.attribution_id != ''); cases = cases.filter((d) => d.attribution_id != '');
cases.forEach((d) => { cases.forEach((d) => {
d.platform = splitString(d.platforms); d.platform = splitString(d.platforms);
d.medium = splitString(d.medium) d.medium = splitString(d.medium);
d.actor_nation = splitString(d.actor_nation); d.actor_nation = splitString(d.actor_nation);
d.source = splitString(d.source); d.source = splitString(d.source);
d.methods = splitString(d.methods); d.methods = splitString(d.methods);
d.campaign = splitString(d.campaign) d.campaign = splitString(d.campaign);
d.attribution_total_score = +d.attribution_score; d.attribution_total_score = +d.attribution_score;
d.attribution_date = new Date(d.attribution_date); d.attribution_date = new Date(d.attribution_date);
d.search = [ d.search = [
@ -79,12 +80,12 @@
sourceFilter.init(cases, 'source'); sourceFilter.init(cases, 'source');
sourceCategoryFilter.init(cases, 'source_category'); sourceCategoryFilter.init(cases, 'source_category');
methodFilter.init(cases, 'methods'); methodFilter.init(cases, 'methods');
campaignFilter.init(cases, 'campaign') campaignFilter.init(cases, 'campaign');
$attributionScoreFilter = attributionScoreDef; $attributionScoreFilter = attributionScoreDef;
$timeRangeFilter = extent(cases.map((d) => new Date(d.attribution_date))); $timeRangeFilter = extent(cases.map((d) => new Date(d.attribution_date)));
//$timeRangeFilter = [new Date('2024-01-01'), max(cases.map((d) => new Date(d.attribution_date)))]; //$timeRangeFilter = [new Date('2024-01-01'), max(cases.map((d) => new Date(d.attribution_date)))];
$fullTimeRange = extent(cases.map((d) => new Date(d.attribution_date))); $fullTimeRange = extent(cases.map((d) => new Date(d.attribution_date)));
//$fullTimeRange = [new Date('2022-01-01'), max(cases.map((d) => new Date(d.attribution_date)))]; //$fullTimeRange = [new Date('2022-01-01'), max(cases.map((d) => new Date(d.attribution_date)))];
const eventsResponse = await csv( const eventsResponse = await csv(
`https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/Key_Events_List.csv` `https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/Key_Events_List.csv`
@ -153,7 +154,7 @@
haveOverlap($sourceFilter, d.source) && haveOverlap($sourceFilter, d.source) &&
haveOverlap($sourceCategoryFilter, d.source_category) && haveOverlap($sourceCategoryFilter, d.source_category) &&
haveOverlap($methodFilter, d.methods) && haveOverlap($methodFilter, d.methods) &&
haveOverlap($campaignFilter, d.campaign) && haveOverlap($campaignFilter, d.campaign) &&
withinRange($attributionScoreFilter, d.attribution_total_score) && withinRange($attributionScoreFilter, d.attribution_total_score) &&
withinRange($timeRangeFilter, d.attribution_date) && withinRange($timeRangeFilter, d.attribution_date) &&
includesTextSearch($textSearchFilter, d.search) includesTextSearch($textSearchFilter, d.search)
@ -188,18 +189,21 @@
]; ];
let selectedSorting = { id: 'attribution_date', label: 'Attribution Date', type: 'date' }; let selectedSorting = { id: 'attribution_date', label: 'Attribution Date', type: 'date' };
let modalOpen = false;
let activeCaseData;
</script> </script>
<svelte:window bind:innerWidth /> <svelte:window bind:innerWidth />
<svelte:head> <svelte:head>
<title>{copy.meta.title}</title> <title>{copy.meta.title}</title>
<meta property="og:site_name" content={copy.meta.og_site_name} /> <meta property="og:site_name" content={copy.meta.og_site_name} />
<meta property="og:description" content={copy.meta.og_description} /> <meta property="og:description" content={copy.meta.og_description} />
<meta property="og:url" content={copy.meta.og_url} /> <meta property="og:url" content={copy.meta.og_url} />
<meta property="og:image" content={copy.meta.og_image} /> <meta property="og:image" content={copy.meta.og_image} />
<meta property="og:type" content="website"> <meta property="og:type" content="website" />
<meta property="og:locale" content="en_US"> <meta property="og:locale" content="en_US" />
</svelte:head> </svelte:head>
{#if isMobile} {#if isMobile}
@ -211,7 +215,7 @@
{/if} {/if}
<section class="section"> <section class="section">
<Header /> <Header />
</section> </section>
<section class="section"> <section class="section">
@ -222,17 +226,18 @@
{#if block.type == 'text'} {#if block.type == 'text'}
<p class="intro">{block.text}</p> <p class="intro">{block.text}</p>
{/if} {/if}
{/each} {/each}
</div> </div>
<div class="container"> <div class="container">
{#each copy.intro as block} {#each copy.intro as block}
{#if block.type == 'concealed-text'} {#if block.type == 'concealed-text'}
<p>{block.title}</p> <p>{block.title}</p>
{/if} {/if}
{/each} {/each}
</div> </div>
</section> </section>
{#if !modalOpen}
<section <section
class={isMobile && sidebarOpen class={isMobile && sidebarOpen
? 'section sidebar open controls' ? 'section sidebar open controls'
@ -240,8 +245,10 @@
? 'section sidebar closed controls' ? 'section sidebar closed controls'
: 'section sticky controls'} : 'section sticky controls'}
> >
<Controls {cases}></Controls> <Controls {cases}></Controls>
</section> </section>
{/if}
<section class="section"> <section class="section">
<div> <div>
@ -276,7 +283,7 @@
</div> </div>
</div> </div>
<div class="cases-control"> <div class="cases-control">
<label for="sort-select">Sort cases by </label> <label for="sort-select">Sort cases by </label>
<div class="select is-small"> <div class="select is-small">
<select bind:value={selectedSorting} id="sort-select"> <select bind:value={selectedSorting} id="sort-select">
{#each sortOptions as sortOpt} {#each sortOptions as sortOpt}
@ -293,12 +300,15 @@
{#if displayDataAs == 'Cards'} {#if displayDataAs == 'Cards'}
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<a href="https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv">Download the data</a> <a
href="https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv"
>Download the data</a
>
<div class="grid is-col-min-16"> <div class="grid is-col-min-16">
{#each sortedCases as attrCase} {#each sortedCases as attrCase}
{#if attrCase.show} {#if attrCase.show}
<div class="cell"> <div class="cell">
<CaseCard cardData={attrCase}></CaseCard> <CaseCard cardData={attrCase} expanded={false} bind:modalOpen bind:activeCaseData></CaseCard>
</div> </div>
{/if} {/if}
{/each} {/each}
@ -310,7 +320,10 @@
{#if displayDataAs == 'Table' && sortedCases.length > 0} {#if displayDataAs == 'Table' && sortedCases.length > 0}
<section class="section"> <section class="section">
<div class="container"> <div class="container">
<a href="https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv">Download the data</a> <a
href="https://fiat-2024-processed-data.s3.us-west-2.amazonaws.com/fiat_2024_attribution_data.csv"
>Download the data</a
>
<CaseTable cases={sortedCases}></CaseTable> <CaseTable cases={sortedCases}></CaseTable>
</div> </div>
</section> </section>
@ -322,25 +335,19 @@
{#if block.type == 'text'} {#if block.type == 'text'}
<p>{block.text}</p> <p>{block.text}</p>
{/if} {/if}
{#if block.type == 'concealed-text'} {#if block.type == 'concealed-text'}
<p>{block.title}</p> <p>{block.title}</p>
{/if} {/if}
{/each} {/each}
</div> </div>
</section> </section>
<CardModal bind:modalOpen {activeCaseData}></CardModal>
<style> <style>
section { section {
font-family: var(--font-02); font-family: var(--font-02);
} }
.title {
font-family: var(--font-01);
color: var(--usa-blue)
}
.subtitle {
font-family: var(--font-02);
color: var(--usa-blue)
}
.intro { .intro {
max-width: 800px; max-width: 800px;
margin: auto; margin: auto;
@ -378,6 +385,6 @@
} }
.cases-control { .cases-control {
display: inline-block; display: inline-block;
margin-left: 3rem; margin-left: 3rem;
} }
</style> </style>