Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions src/App.vue
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<script setup lang="ts">
import type { ComputedRef } from 'vue';
import { onBeforeMount, ref, computed } from 'vue';
import { onBeforeMount, ref, computed, nextTick } from 'vue';
import { GF, Font } from './models';
import type { Tagging } from './models';
import type { Panel } from './components/Panel.vue';
import type { FilterSet } from './components/AddTags.vue';
import { EventBus } from './eventbus';
import { SCRIPT_TAGGING, LINT_RULES } from './config';
import { sampleTextFor } from './pangrams';

let appLoaded = ref(false);
let showUndefined = ref(false);
Expand Down Expand Up @@ -51,6 +52,20 @@ function addCategoriesPanel(categories: string[]) {
});
}
function removePanel(idx: number) { panels.value.splice(idx, 1); }
// After Start script tagging: three views of the category. Panels are keyed by
// index, so clear them first to get fresh views instead of reused ones.
async function startScriptTagging({ script, category }: { script: string; category: string }) {
panels.value = [];
await nextTick();
// The two script panels show Latin and the script side by side. The third
// panel lists every family, most without the script, so it keeps the default.
const sampleText = sampleTextFor([script]);
panels.value = [
{ type: 'categories', categories: [category], sortBy: 'score', scriptFilter: [script], placeholdersOnly: true, sampleText },
{ type: 'categories', categories: [category], sortBy: 'family', scriptFilter: [script], sampleText },
{ type: 'categories', categories: [category], sortBy: 'score' },
];
}
function shiftLeft(idx: number) {
if (idx > 0) {
const panel = panels.value.splice(idx, 1)[0];
Expand Down Expand Up @@ -120,7 +135,7 @@ onBeforeMount(async () => {
style="width: 280px; font-family: monospace;" /></label>
<span v-if="SCRIPT_TAGGING" class="script-mode">Script tagging: saves to families-scripts.csv</span>
<placeholder-tag-adder v-if="showPlaceholderDialog" :gf="gf" @close="showPlaceholderDialog = false" />
<script-starter v-if="showScriptStarter" :gf="gf" @close="showScriptStarter = false" />
<script-starter v-if="showScriptStarter" :gf="gf" @start="startScriptTagging" @close="showScriptStarter = false" />
<div style="display: flex; flex-direction: row; width: 100vw; min-height: 100vh;">
<div v-for="(panel, idx) in panels" :key="idx"
:style="{ flex: '1 1 0', minWidth: 0, borderRight: idx < panels.length - 1 ? '1px solid #eee' : 'none', height: '100vh', overflow: 'auto' }">
Expand Down
8 changes: 7 additions & 1 deletion src/components/Panel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@ type FontPanel = {
type CategoriesPanel = {
type: "categories"; // Type of panel
categories: string[]; // Array of category names
// tagGroups: FontTagGroup[]; // Array of FontTagGroup objects
// Optional initial state of the view's controls
sortBy?: string;
scriptFilter?: string[];
placeholdersOnly?: boolean;
sampleText?: string;
};
type TodoPanel = {
type: "todo"; // Type of panel
Expand Down Expand Up @@ -61,6 +65,8 @@ onMounted(() => {
<tags-by-font v-if="panel.type === 'font'" :font="panel.font" :gf="gf"
:showUndefined="props.showUndefined" :vfDisplayMode="props.vfDisplayMode"></tags-by-font>
<tags-by-categories v-else-if="panel.type === 'categories'" :categories="panel.categories"
:initialSortBy="panel.sortBy" :initialScriptFilter="panel.scriptFilter" :initialPlaceholdersOnly="panel.placeholdersOnly"
:initialSampleText="panel.sampleText"
:gf="gf" :showUndefined="props.showUndefined" :vfDisplayMode="props.vfDisplayMode"></tags-by-categories>
<todo v-else-if="panel.type === 'todo'" :gf="gf"></todo>
<untagged-families v-else-if="panel.type === 'untagged'" :gf="gf"></untagged-families>
Expand Down
39 changes: 21 additions & 18 deletions src/components/ScriptStarter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { ref, computed } from 'vue';
import { GF, StaticTagging, VariableTagging } from '../models';
import type { Font, Location, Tagging } from '../models';

// Scripts build only. One dialog to set up a tagger for a script: every family
// that supports the script gets a 0-score placeholder for each chosen category,
// tagged for that script. Families with a wght axis get a variable placeholder
// Scripts build only. One dialog to set up a tagger for a script and category:
// every family that supports the script gets a 0-score placeholder for the
// category, tagged for that script, and the view changes to three category
// panels for the work. Families with a wght axis get a variable placeholder
// at the wght extremes, crossed with the wdth extremes when the family has wdth.
// Everything else gets a static placeholder. A family that already has any
// tagging for the category, for all scripts or for this one, gets nothing: the
Expand All @@ -19,15 +20,15 @@ const props = defineProps({
}
});

const emit = defineEmits(['close']);

// The categories a script tagger starts with. Change the prefixes to widen the set.
const DEFAULT_CATEGORY_PREFIXES = ['/Expressive/', '/Quality/'];
const emit = defineEmits<{
(e: 'close'): void;
(e: 'start', payload: { script: string; category: string }): void;
}>();

const script = ref<string | null>(null);
const categories = ref<string[]>(
props.gf.uniqueTagNames().filter(name => DEFAULT_CATEGORY_PREFIXES.some(p => name.startsWith(p)))
);
const category = ref<string | null>(null);
// One category at a time, so the three panels opened by OK have one focus.
const categories = computed<string[]>(() => category.value ? [category.value] : []);

const scriptOptions = computed(() => {
const all = new Set<string>();
Expand Down Expand Up @@ -95,7 +96,7 @@ const plan = computed<Plan>(() => {
});

function submit() {
if (!script.value) return;
if (!script.value || !category.value) return;
const scripts = [script.value];
for (const family of families.value) {
const axes = gridAxes(family);
Expand All @@ -116,6 +117,7 @@ function submit() {
family.taggings.push(tagging);
}
}
emit('start', { script: script.value, category: category.value });
emit('close');
}
</script>
Expand All @@ -125,17 +127,18 @@ function submit() {
<div class="dialog">
<h3>Start script tagging</h3>
<p class="hint">
Adds a 0-score placeholder for every family that supports the script and every
category below. Families with a wght axis get a variable placeholder at the wght
extremes, and at the wdth extremes too when they have wdth. Families that already
have any tagging for a category, for all scripts or for this script, are skipped.
Adds a 0-score placeholder for the category to every family that supports the script.
Families with a wght axis get a variable placeholder at the wght extremes, and at the
wdth extremes too when they have wdth. Families that already have any tagging for the
category, for all scripts or for this script, are skipped. OK then opens three panels
for the category: the new placeholders, every tagging for the script, and every tagging.
</p>

<h4>Script</h4>
<v-select v-model="script" :options="scriptOptions" placeholder="Choose one script"></v-select>

<h4>Categories</h4>
<v-select v-model="categories" multiple :options="gf.uniqueTagNames()"></v-select>
<h4>Category</h4>
<v-select v-model="category" :options="gf.uniqueTagNames()" placeholder="Choose one category"></v-select>

<div v-if="script" class="plan">
<div><b>{{ families.length }}</b> families support {{ script }}:
Expand All @@ -145,7 +148,7 @@ function submit() {
</div>

<hr />
<button @click="submit" :disabled="!script || categories.length === 0 || plan.newTaggings === 0">OK</button>
<button @click="submit" :disabled="!script || !category">OK</button>
<button @click="emit('close')">Cancel</button>
</div>
</div>
Expand Down
25 changes: 21 additions & 4 deletions src/components/TagsByCategories.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,36 @@ const props = defineProps({
vfDisplayMode: {
type: String as () => 'animated' | 'list',
default: 'animated'
},
// Initial state of the controls, for panels opened with a preset
initialSortBy: {
type: String,
default: 'family'
},
initialScriptFilter: {
type: Array as () => string[],
default: () => []
},
initialPlaceholdersOnly: {
type: Boolean,
default: false
},
initialSampleText: {
type: String,
default: ''
}
});

const selectedCategories = ref<string[]>(props.categories);
const sortBy = ref('family'); // Default sorting option
const sortBy = ref(props.initialSortBy);
const tagFilter = ref('');
const axisFilter = ref('');
const reverseTags = ref(false);
const variableOnly = ref(false);
const staticOnly = ref(false);
const scriptFilter = ref<string[]>([]);
const placeholdersOnly = ref(false);
const sampleText = ref('');
const scriptFilter = ref<string[]>([...props.initialScriptFilter]);
const placeholdersOnly = ref(props.initialPlaceholdersOnly);
const sampleText = ref(props.initialSampleText);

// Every script any font supports, from the family metadata subsets
const scriptOptions = computed(() => {
Expand Down
53 changes: 53 additions & 0 deletions src/pangrams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Sample text per script, keyed by the subset names in the family metadata.
// Two words at most per script, so Latin plus one script is four words at most.

export const LATIN_SAMPLE = "Hello world";

export const SAMPLES: Record<string, string> = {
latin: LATIN_SAMPLE,
vietnamese: "Xin chào",
cyrillic: "Привет мир",
greek: "Καλημέρα κόσμε",
armenian: "Բարև աշխարհ",
georgian: "გამარჯობა მსოფლიო",
hebrew: "שלום עולם",
arabic: "مرحبا بالعالم",
syriac: "ܫܠܡܐ ܥܠܡܐ",
ethiopic: "ሰላም ዓለም",
devanagari: "नमस्ते दुनिया",
bengali: "নমস্কার বিশ্ব",
gurmukhi: "ਨਮਸਤੇ ਦੁਨੀਆ",
gujarati: "નમસ્તે દુનિયા",
oriya: "ନମସ୍କାର ଦୁନିଆ",
tamil: "வணக்கம் உலகம்",
telugu: "నమస్కారం ప్రపంచం",
kannada: "ನಮಸ್ಕಾರ ಜಗತ್ತು",
malayalam: "നമസ്കാരം ലോകം",
sinhala: "ආයුබෝවන් ලෝකය",
thai: "สวัสดี ชาวโลก",
lao: "ສະບາຍດີ ໂລກ",
khmer: "សួស្តី ពិភពលោក",
myanmar: "မင်္ဂလာပါ ကမ္ဘာ",
tibetan: "བཀྲ་ཤིས་བདེ་ལེགས།",
japanese: "こんにちは 世界",
korean: "안녕 세상",
"chinese-simplified": "你好 世界",
"chinese-traditional": "你好 世界",
"chinese-hongkong": "你好 世界",
cherokee: "ᎣᏏᏲ ᎡᎶᎯ",
"canadian-aboriginal": "ᐊᐃ ᓄᓇ",
};

export function hasSample(script: string): boolean {
return script in SAMPLES;
}

// Latin first, then the sample for each other script that has one. Scripts
// without a sample add nothing, so the result is never empty.
export function sampleTextFor(scripts: string[]): string {
const parts = [LATIN_SAMPLE];
for (const script of scripts) {
if (script !== "latin" && hasSample(script)) parts.push(SAMPLES[script]);
}
return parts.join(" ");
}
Loading