Case modal
Этот коммит содержится в:
родитель
3ddd9c467a
Коммит
ad33923c37
20
src/lib/components/CardModal.svelte
Обычный файл
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>
|
||||||
|
|||||||
Загрузка…
x
Ссылка в новой задаче
Block a user