|
<script lang="ts"> |
|
import { createEventDispatcher, getContext, onMount, tick } from "svelte"; |
|
|
|
import type { MenuListEntry } from "@graphite/messages"; |
|
import type { FontsState } from "@graphite/state-providers/fonts"; |
|
|
|
import MenuList from "@graphite/components/floating-menus/MenuList.svelte"; |
|
import LayoutRow from "@graphite/components/layout/LayoutRow.svelte"; |
|
import IconLabel from "@graphite/components/widgets/labels/IconLabel.svelte"; |
|
import TextLabel from "@graphite/components/widgets/labels/TextLabel.svelte"; |
|
|
|
const fonts = getContext<FontsState>("fonts"); |
|
|
|
const dispatch = createEventDispatcher<{ |
|
fontFamily: string; |
|
fontStyle: string; |
|
changeFont: { fontFamily: string; fontStyle: string; fontFileUrl: string | undefined }; |
|
}>(); |
|
|
|
let menuList: MenuList | undefined; |
|
|
|
export let fontFamily: string; |
|
export let fontStyle: string; |
|
export let isStyle = false; |
|
export let disabled = false; |
|
export let tooltip: string | undefined = undefined; |
|
|
|
let open = false; |
|
let entries: MenuListEntry[] = []; |
|
let activeEntry: MenuListEntry | undefined = undefined; |
|
let minWidth = isStyle ? 0 : 300; |
|
|
|
$: watchFont(fontFamily, fontStyle); |
|
|
|
async function watchFont(..._: string[]) { |
|
|
|
const newEntries = await getEntries(); |
|
entries = newEntries; |
|
activeEntry = getActiveEntry(newEntries); |
|
} |
|
|
|
async function setOpen() { |
|
open = true; |
|
|
|
|
|
await tick(); |
|
|
|
if (activeEntry) { |
|
const index = entries.indexOf(activeEntry); |
|
menuList?.scrollViewTo(Math.max(0, index * 20 - 190)); |
|
} |
|
} |
|
|
|
function toggleOpen() { |
|
if (!disabled) { |
|
open = !open; |
|
|
|
if (open) setOpen(); |
|
} |
|
} |
|
|
|
async function selectFont(newName: string) { |
|
let family; |
|
let style; |
|
|
|
if (isStyle) { |
|
dispatch("fontStyle", newName); |
|
|
|
family = fontFamily; |
|
style = newName; |
|
} else { |
|
dispatch("fontFamily", newName); |
|
|
|
family = newName; |
|
style = "Regular (400)"; |
|
} |
|
|
|
const fontFileUrl = await fonts.getFontFileUrl(family, style); |
|
dispatch("changeFont", { fontFamily: family, fontStyle: style, fontFileUrl }); |
|
} |
|
|
|
async function getEntries(): Promise<MenuListEntry[]> { |
|
const x = isStyle ? fonts.getFontStyles(fontFamily) : fonts.fontNames(); |
|
return (await x).map((entry: { name: string; url: URL | undefined }) => ({ |
|
label: entry.name, |
|
value: entry.name, |
|
font: entry.url, |
|
action: () => selectFont(entry.name), |
|
})); |
|
} |
|
|
|
function getActiveEntry(entries: MenuListEntry[]): MenuListEntry { |
|
const selectedChoice = isStyle ? fontStyle : fontFamily; |
|
|
|
return entries.find((entry) => entry.value === selectedChoice) as MenuListEntry; |
|
} |
|
|
|
onMount(async () => { |
|
entries = await getEntries(); |
|
|
|
activeEntry = getActiveEntry(entries); |
|
}); |
|
</script> |
|
|
|
<!-- TODO: Combine this widget into the DropdownInput widget --> |
|
<LayoutRow class="font-input"> |
|
<LayoutRow |
|
class="dropdown-box" |
|
classes={{ disabled }} |
|
styles={{ ...(minWidth > 0 ? { "min-width": `${minWidth}px` } : {}) }} |
|
{tooltip} |
|
tabindex={disabled ? -1 : 0} |
|
on:click={toggleOpen} |
|
data-floating-menu-spawner |
|
> |
|
<TextLabel class="dropdown-label">{activeEntry?.value || ""}</TextLabel> |
|
<IconLabel class="dropdown-arrow" icon="DropdownArrow" /> |
|
</LayoutRow> |
|
<MenuList |
|
on:naturalWidth={({ detail }) => isStyle && (minWidth = detail)} |
|
{activeEntry} |
|
on:activeEntry={({ detail }) => (activeEntry = detail)} |
|
{open} |
|
on:open={({ detail }) => (open = detail)} |
|
entries={[entries]} |
|
minWidth={isStyle ? 0 : minWidth} |
|
virtualScrollingEntryHeight={isStyle ? 0 : 20} |
|
scrollableY={true} |
|
bind:this={menuList} |
|
/> |
|
</LayoutRow> |
|
|
|
<style lang="scss" global> |
|
.font-input { |
|
position: relative; |
|
|
|
.dropdown-box { |
|
align-items: center; |
|
white-space: nowrap; |
|
background: var(--color-1-nearblack); |
|
height: 24px; |
|
border-radius: 2px; |
|
|
|
.dropdown-label { |
|
margin: 0; |
|
margin-left: 8px; |
|
flex: 1 1 100%; |
|
} |
|
|
|
.dropdown-arrow { |
|
margin: 6px 2px; |
|
flex: 0 0 auto; |
|
} |
|
|
|
&:hover, |
|
&.open { |
|
background: var(--color-6-lowergray); |
|
|
|
.text-label { |
|
color: var(--color-f-white); |
|
} |
|
} |
|
|
|
&.open { |
|
border-radius: 2px 2px 0 0; |
|
} |
|
|
|
&.disabled { |
|
background: var(--color-2-mildblack); |
|
|
|
.text-label { |
|
color: var(--color-8-uppergray); |
|
} |
|
} |
|
} |
|
|
|
.menu-list .floating-menu-container .floating-menu-content { |
|
max-height: 400px; |
|
padding: 4px 0; |
|
} |
|
} |
|
</style> |
|
|