Components, styling
Этот коммит содержится в:
родитель
a011a1b2a6
Коммит
c9a418f0b7
47
src/lib/components/Checkbox.svelte
Обычный файл
47
src/lib/components/Checkbox.svelte
Обычный файл
@ -0,0 +1,47 @@
|
||||
<script>
|
||||
// custom checkbox
|
||||
export let id;
|
||||
export let checked = false;
|
||||
</script>
|
||||
|
||||
<input type="checkbox"
|
||||
id="checkbox-{id}"
|
||||
class:checked
|
||||
on:click|stopPropagation />
|
||||
<label for="checkbox-{id}" class="choice-entry-name">
|
||||
<slot></slot>
|
||||
</label>
|
||||
|
||||
<style>
|
||||
label {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding-left: 1.5rem;
|
||||
cursor: pointer;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
input[type="checkbox"] {
|
||||
display: none;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
input[type="checkbox"] + label::before {
|
||||
display: block;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin-top: 3px;
|
||||
margin-right: 0.4rem;
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
background-color: var(--bg);
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0.5rem;
|
||||
}
|
||||
|
||||
input[type="checkbox"].checked + label::before {
|
||||
box-shadow: inset 0px 0px 0px 3px var(--bg);
|
||||
background-color: var(--usa-blue);
|
||||
}
|
||||
</style>
|
||||
100
src/lib/components/Controls.svelte
Обычный файл
100
src/lib/components/Controls.svelte
Обычный файл
@ -0,0 +1,100 @@
|
||||
<script>
|
||||
import Dropdown from '$lib/components/Dropdown.svelte'
|
||||
|
||||
let items = [
|
||||
{
|
||||
"id": "Facebook",
|
||||
"name": "Facebook",
|
||||
"selected": true,
|
||||
"count": 34,
|
||||
"liveCount": 34
|
||||
},
|
||||
{
|
||||
"id": "Instagram",
|
||||
"name": "Instagram",
|
||||
"selected": true,
|
||||
"count": 21,
|
||||
"liveCount": 21
|
||||
},
|
||||
{
|
||||
"id": "Twitter",
|
||||
"name": "Twitter",
|
||||
"selected": true,
|
||||
"count": 34,
|
||||
"liveCount": 34
|
||||
},
|
||||
{
|
||||
"id": "YouTube",
|
||||
"name": "YouTube",
|
||||
"selected": true,
|
||||
"count": 16,
|
||||
"liveCount": 16
|
||||
},
|
||||
{
|
||||
"id": "unspecified",
|
||||
"name": "unspecified",
|
||||
"selected": true,
|
||||
"count": 33,
|
||||
"liveCount": 33
|
||||
},
|
||||
{
|
||||
"id": "Reddit",
|
||||
"name": "Reddit",
|
||||
"selected": true,
|
||||
"count": 13,
|
||||
"liveCount": 13
|
||||
},
|
||||
{
|
||||
"id": "WhatsApp",
|
||||
"name": "WhatsApp",
|
||||
"selected": true,
|
||||
"count": 2,
|
||||
"liveCount": 2
|
||||
},
|
||||
{
|
||||
"id": "SMS",
|
||||
"name": "SMS",
|
||||
"selected": true,
|
||||
"count": 2,
|
||||
"liveCount": 2
|
||||
},
|
||||
{
|
||||
"id": "Telegram",
|
||||
"name": "Telegram",
|
||||
"selected": true,
|
||||
"count": 1,
|
||||
"liveCount": 1
|
||||
},
|
||||
{
|
||||
"id": "Quora",
|
||||
"name": "Quora",
|
||||
"selected": true,
|
||||
"count": 1,
|
||||
"liveCount": 1
|
||||
},
|
||||
{
|
||||
"id": "Forum Board",
|
||||
"name": "Forum Board",
|
||||
"selected": true,
|
||||
"count": 1,
|
||||
"liveCount": 1
|
||||
},
|
||||
{
|
||||
"id": "Parler",
|
||||
"name": "Parler",
|
||||
"selected": true,
|
||||
"count": 1,
|
||||
"liveCount": 1
|
||||
},
|
||||
{
|
||||
"id": "Gab",
|
||||
"name": "Gab",
|
||||
"selected": true,
|
||||
"count": 1,
|
||||
"liveCount": 1
|
||||
}
|
||||
]
|
||||
|
||||
</script>
|
||||
|
||||
<Dropdown items={items} label='Platform'></Dropdown>
|
||||
241
src/lib/components/Dropdown.svelte
Обычный файл
241
src/lib/components/Dropdown.svelte
Обычный файл
@ -0,0 +1,241 @@
|
||||
<script>
|
||||
// a custom dropdown
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { slide } from 'svelte/transition';
|
||||
import { sortConsistently } from '$lib/utils/misc';
|
||||
|
||||
import Checkbox from '$lib/components/Checkbox.svelte';
|
||||
|
||||
export let items = [];
|
||||
export let label = '';
|
||||
export let nameField = 'id';
|
||||
export let hideOneHitWonders = false;
|
||||
export let superior = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let elem;
|
||||
let expanded = false;
|
||||
|
||||
function handleBodyClick() {
|
||||
expanded = false
|
||||
}
|
||||
|
||||
function toggleExpanded() {
|
||||
expanded = !expanded;
|
||||
}
|
||||
|
||||
function selectAll() {
|
||||
dispatch('itemsAdded', items.map((d) => d.id));
|
||||
}
|
||||
|
||||
function unselectAll() {
|
||||
dispatch('itemsRemoved', items.map((d) => d.id));
|
||||
}
|
||||
|
||||
function handleDropdownClick() {
|
||||
toggleExpanded();
|
||||
}
|
||||
|
||||
function handleChoiceClick(id) {
|
||||
if (!items.filter((d) => d.selected).map((d) => d.id).includes(id)) {
|
||||
dispatch('itemsAdded', id);
|
||||
} else {
|
||||
dispatch('itemsRemoved', id);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:body on:click={(e) => handleBodyClick(e)}></svelte:body>
|
||||
|
||||
<div class="dropdown" bind:this={elem}>
|
||||
<div class="label">
|
||||
{label}
|
||||
</div>
|
||||
<div class="selected-items" on:click|stopPropagation={handleDropdownClick}>
|
||||
<span class="selected-items-icon"></span>
|
||||
<span class="selected-items-text">
|
||||
{items.filter((d) => d.selected).length === 0
|
||||
? 'none'
|
||||
: (items.every((d) => d.selected && items.length > 1)
|
||||
? 'all'
|
||||
: items.filter((d) => d.selected).map((d) => d[nameField]).join(', '))}
|
||||
</span>
|
||||
<button class="selected-items-arrow">
|
||||
<svg class:expanded width="15" height="10">
|
||||
<path d="M0 0L15 0L7.5 10Z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="choice-wrapper">
|
||||
{#if (expanded)}
|
||||
<div class="choice" transition:slide class:superior>
|
||||
<div class="choice-controls">
|
||||
<button class="choice-controls-selectall" on:click|stopPropagation={selectAll}>Select all</button>
|
||||
<button class="choice-controls-unselectall" on:click|stopPropagation={unselectAll}>Unselect all</button>
|
||||
</div>
|
||||
<ul class="choice-list">
|
||||
{#each items.sort((a, b) => -sortConsistently(a, b, 'id', 'id')) as item, i (item.id)}
|
||||
{#if (!(hideOneHitWonders && item.count === 1))}
|
||||
<li on:click|stopPropagation>
|
||||
<Checkbox id="{label}-{i}"
|
||||
checked={item.selected}
|
||||
on:click={() => handleChoiceClick(item.id)}>
|
||||
<span class="choice-entry-name">{item[nameField]}</span>
|
||||
{#if (item.liveCount)}
|
||||
<span class="choice-entry-count">({item.liveCount})</span>
|
||||
{:else if (item.source)}
|
||||
<span class="choice-entry-source">({item.source})</span>
|
||||
{/if}
|
||||
</Checkbox>
|
||||
</li>
|
||||
{/if}
|
||||
{/each}
|
||||
</ul>
|
||||
{#if (hideOneHitWonders)}
|
||||
<p class="info">{label}s with only one result in the dataset are hidden.</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.dropdown {
|
||||
align-self: flex-end;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-family: var(--font-02);
|
||||
width: 200px;
|
||||
max-width: 200px;
|
||||
min-width: 200px;
|
||||
margin: 0.3rem 0.3rem 0 0.3rem;
|
||||
position: relative;
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin: 0 0 0.1rem 0;
|
||||
font-size: 0.7rem;
|
||||
color: var(--usa-blue);
|
||||
}
|
||||
|
||||
.selected-items {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: 1.7rem;
|
||||
padding: 0.1rem 0.3rem;
|
||||
font-size: 0.8rem;
|
||||
background-color: var(--bg);
|
||||
border: 2px solid var(--usa-blue);
|
||||
border-radius: 3px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-items-text {
|
||||
width: 100%;
|
||||
color: var(--text-black);
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.selected-items-arrow {
|
||||
background: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.selected-items-arrow svg {
|
||||
margin-top: 0.2rem;
|
||||
stroke: none;
|
||||
fill: var(--usa-blue);
|
||||
transition: transform 400ms ease;
|
||||
}
|
||||
|
||||
.selected-items-arrow svg.expanded{
|
||||
transform: rotate(-540deg);
|
||||
}
|
||||
|
||||
.choice-wrapper {
|
||||
width: 100%;
|
||||
height: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.choice {
|
||||
width: 100%;
|
||||
border: 2px solid var(--usa-lightblue);
|
||||
border-radius: 3px;
|
||||
background-color: var(--bg);
|
||||
position: absolute;
|
||||
z-index: 12000;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.superior {
|
||||
z-index: 100000 !important;
|
||||
}
|
||||
|
||||
.choice-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.choice-controls button {
|
||||
margin: 0.5rem;
|
||||
padding: 0.2rem 0.5rem;
|
||||
color: var(--bg);
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
button.choice-controls-selectall {
|
||||
background-color: var(--usa-lightblue);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
button.choice-controls-unselectall {
|
||||
background-color: var(--usa-lightred);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
ul.choice-list {
|
||||
width: 100%;
|
||||
max-height: 600px;
|
||||
overflow-y: scroll;
|
||||
list-style-type: none;
|
||||
font-size: 0.8rem;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
ul.choice-list li {
|
||||
padding: 0.4rem 0.5rem;
|
||||
cursor: pointer;
|
||||
background-color: var(--bg);
|
||||
transition: background-color 200ms ease;
|
||||
}
|
||||
|
||||
ul.choice-list li:hover {
|
||||
background-color: var(--usa-lightblue);
|
||||
}
|
||||
|
||||
.choice-entry-count, .choice-entry-source {
|
||||
font-size: 0.8em;
|
||||
}
|
||||
|
||||
.choice-entry-source {
|
||||
display: block;
|
||||
}
|
||||
|
||||
p.info {
|
||||
padding: 0.4rem 0.5rem;
|
||||
font-size: 0.7rem;
|
||||
color: var(--dfrlab-gray);
|
||||
}
|
||||
</style>
|
||||
21
src/lib/utils/misc.js
Обычный файл
21
src/lib/utils/misc.js
Обычный файл
@ -0,0 +1,21 @@
|
||||
// consistent sort function
|
||||
export const sortConsistently = (itemA, itemB, property, key) => {
|
||||
let valueA = itemA[property];
|
||||
let valueB = itemB[property];
|
||||
|
||||
if (typeof valueA === 'string') valueA = valueA.trim().toLowerCase();
|
||||
if (typeof valueB === 'string') valueB = valueB.trim().toLowerCase();
|
||||
if (typeof valueA === 'number') valueA = +valueA;
|
||||
if (typeof valueB === 'number') valueB = +valueB;
|
||||
if (typeof valueA === 'number' && isNaN(valueA)) valueA = 0;
|
||||
if (typeof valueB === 'number' && isNaN(valueB)) valueB = 0;
|
||||
|
||||
let r = valueA > valueB ? -1 : valueA < valueB ? 1 : 0;
|
||||
|
||||
if (r === 0) {
|
||||
r = typeof itemA[key] !== 'undefined' && typeof itemB[key] !== 'undefined'
|
||||
? +itemA[key] - +itemB[key]
|
||||
: 0;
|
||||
}
|
||||
return r;
|
||||
};
|
||||
5
src/routes/+layout.svelte
Обычный файл
5
src/routes/+layout.svelte
Обычный файл
@ -0,0 +1,5 @@
|
||||
<script>
|
||||
import '../style.css'
|
||||
</script>
|
||||
|
||||
<slot />
|
||||
@ -6,7 +6,8 @@
|
||||
import CaseCard from '$lib/components/CaseCard.svelte';
|
||||
import CaseTable from '$lib/components/CaseTable.svelte';
|
||||
import Timeline from '$lib/components/Timeline.svelte';
|
||||
import Select from 'svelte-select';
|
||||
//import Select from 'svelte-select';
|
||||
import Controls from '$lib/components/Controls.svelte';
|
||||
|
||||
let cases = [];
|
||||
|
||||
@ -38,14 +39,15 @@
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="select-container">
|
||||
<Controls></Controls>
|
||||
<!--div class="select-container">
|
||||
<Select
|
||||
items={actorNationsUnique}
|
||||
multiple={true}
|
||||
bind:value={selectedActorNations}
|
||||
placeholder={'Select 1 or more actor countries'}
|
||||
></Select>
|
||||
</div>
|
||||
</div-->
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
|
||||
9
src/style.css
Обычный файл
9
src/style.css
Обычный файл
@ -0,0 +1,9 @@
|
||||
:root {
|
||||
--bg: #F9F8F8;
|
||||
|
||||
--usa-blue: #3c3b6e;
|
||||
--usa-lightblue: #c9c7eb;
|
||||
|
||||
--usa-red: #b22234;
|
||||
--usa-lightred: #c5888f;
|
||||
}
|
||||
Загрузка…
x
Ссылка в новой задаче
Block a user