diff options
| -rw-r--r-- | src/App.vue | 5 | ||||
| -rw-r--r-- | src/components/AncestryBar.vue | 2 | ||||
| -rw-r--r-- | src/components/SearchModal.vue | 2 | ||||
| -rw-r--r-- | src/components/SettingsModal.vue | 2 | ||||
| -rw-r--r-- | src/components/Tile.vue | 2 | ||||
| -rw-r--r-- | src/components/TileInfoModal.vue | 2 | ||||
| -rw-r--r-- | src/components/TutorialPane.vue | 2 | ||||
| -rw-r--r-- | src/layout.ts | 2 | ||||
| -rw-r--r-- | src/lib.ts | 139 | ||||
| -rw-r--r-- | src/util.ts | 135 |
10 files changed, 146 insertions, 147 deletions
diff --git a/src/App.vue b/src/App.vue index eae0a16..a8c98f8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -14,13 +14,12 @@ import SearchIcon from './components/icon/SearchIcon.vue'; import PlayIcon from './components/icon/PlayIcon.vue'; import SettingsIcon from './components/icon/SettingsIcon.vue'; import HelpIcon from './components/icon/HelpIcon.vue'; -// Classes and types +// Other // Note: Import paths lack a .ts or .js extension because .ts makes vue-tsc complain, and .js makes vite complain import {TolNode, TolMap, UiOptions, Action} from './lib'; import {LayoutNode, LayoutOptions, LayoutTreeChg} from './layout'; -// Functions -import {arraySum, randWeightedChoice, getScrollBarWidth, getBreakpoint} from './lib'; import {initLayoutTree, initLayoutMap, tryLayout} from './layout'; +import {arraySum, randWeightedChoice, getScrollBarWidth, getBreakpoint} from './util'; // Type representing auto-mode actions type AutoAction = 'move across' | 'move down' | 'move up' | Action; diff --git a/src/components/AncestryBar.vue b/src/components/AncestryBar.vue index 85a8f99..c973195 100644 --- a/src/components/AncestryBar.vue +++ b/src/components/AncestryBar.vue @@ -1,8 +1,8 @@ <script lang="ts"> import {defineComponent, PropType} from 'vue'; import Tile from './Tile.vue' -import {LayoutNode, LayoutOptions} from '../layout'; import {TolMap, UiOptions} from '../lib'; +import {LayoutNode, LayoutOptions} from '../layout'; // Displays a sequence of nodes, representing ancestors from a tree-of-life root to a currently-active root export default defineComponent({ diff --git a/src/components/SearchModal.vue b/src/components/SearchModal.vue index 7b0c23e..1a3ef27 100644 --- a/src/components/SearchModal.vue +++ b/src/components/SearchModal.vue @@ -3,8 +3,8 @@ import {defineComponent, PropType} from 'vue'; import SearchIcon from './icon/SearchIcon.vue'; import LogInIcon from './icon/LogInIcon.vue'; import InfoIcon from './icon/InfoIcon.vue'; -import {LayoutNode} from '../layout'; import {TolMap, SearchSugg, SearchSuggResponse, UiOptions} from '../lib'; +import {LayoutNode} from '../layout'; // Displays a search box, and sends search requests export default defineComponent({ diff --git a/src/components/SettingsModal.vue b/src/components/SettingsModal.vue index 36e3564..5be06b7 100644 --- a/src/components/SettingsModal.vue +++ b/src/components/SettingsModal.vue @@ -2,8 +2,8 @@ import {defineComponent, PropType} from 'vue'; import CloseIcon from './icon/CloseIcon.vue'; import RButton from './RButton.vue'; -import {LayoutOptions} from '../layout'; import {UiOptions} from '../lib'; +import {LayoutOptions} from '../layout'; // Displays configurable options, and sends option-change requests export default defineComponent({ diff --git a/src/components/Tile.vue b/src/components/Tile.vue index 22a7333..e03f13e 100644 --- a/src/components/Tile.vue +++ b/src/components/Tile.vue @@ -3,7 +3,7 @@ import {defineComponent, PropType} from 'vue'; import InfoIcon from './icon/InfoIcon.vue'; import {LayoutNode, LayoutOptions} from '../layout'; import {TolNode, TolMap, UiOptions} from '../lib'; -import {capitalizeWords} from '../lib'; +import {capitalizeWords} from '../util'; // Displays one, or a hierarchy of, tree-of-life nodes, as a 'tile' export default defineComponent({ diff --git a/src/components/TileInfoModal.vue b/src/components/TileInfoModal.vue index 6fcb023..423a2f5 100644 --- a/src/components/TileInfoModal.vue +++ b/src/components/TileInfoModal.vue @@ -4,7 +4,7 @@ import CloseIcon from './icon/CloseIcon.vue'; import Tile from './Tile.vue' import {LayoutNode, LayoutOptions} from '../layout'; import {TolNode, TolMap, UiOptions, DescInfo, ImgInfo, TileInfoResponse} from '../lib'; -import {capitalizeWords} from '../lib'; +import {capitalizeWords} from '../util'; // Displays information about a tree-of-life node export default defineComponent({ diff --git a/src/components/TutorialPane.vue b/src/components/TutorialPane.vue index 0bbe8e9..6fa7bac 100644 --- a/src/components/TutorialPane.vue +++ b/src/components/TutorialPane.vue @@ -1,8 +1,8 @@ <script lang="ts"> import {defineComponent, PropType} from 'vue'; import CloseIcon from './icon/CloseIcon.vue'; -import RButton from './RButton.vue'; import {Action, UiOptions} from '../lib'; +import RButton from './RButton.vue'; export default defineComponent({ props: { diff --git a/src/layout.ts b/src/layout.ts index 883192f..9b4a58f 100644 --- a/src/layout.ts +++ b/src/layout.ts @@ -7,7 +7,7 @@ */ import {TolMap} from './lib'; -import {range, arraySum, linspace, limitVals, updateAscSeq} from './lib'; +import {range, arraySum, linspace, limitVals, updateAscSeq} from './util'; // Represents a node/tree that holds layout data for a TolNode node/tree export class LayoutNode { @@ -1,7 +1,8 @@ /* - * Types/classes + * Project-wide types/classes */ + // Used for tree-of-life representation // Maps tree-of-life node names to node objects export type TolMap = Map<string, TolNode>; @@ -87,139 +88,3 @@ export type UiOptions = { disabledActions: Set<Action>, scrollGap: number, // Size of scroll bar, in px }; - -/* - * General utility functions - */ - -export type Breakpoint = 'sm' | 'md' | 'lg'; // These represent screen sizes -export function getBreakpoint(): Breakpoint { - let w = window.innerWidth; - if (w < 768){ - return 'sm'; - } else if (w < 1024){ - return 'md'; - } else { - return 'lg'; - } -} - -// Returns [0 ... len] -export function range(len: number): number[] { - return [...Array(len).keys()]; -} -// Returns sum of array values -export function arraySum(array: number[]): number { - return array.reduce((x,y) => x+y); -} -// Returns an array of increasing evenly-spaced numbers from 'start' to 'end' with size 'size' -export function linspace(start: number, end: number, size: number): number[] { - let step = (end - start) / (size - 1); - let ar = []; - for (let i = 0; i < size; i++){ - ar.push(start + step * i); - } - return ar; -} -// Returns array copy with vals clipped to within [min,max], redistributing to compensate -// Returns null on failure -export function limitVals(arr: number[], min: number, max: number): number[] | null { - let vals = [...arr]; - let clipped = new Array(vals.length).fill(false); - let owedChg = 0; // Stores total change made after clipping values - while (true){ - // Clip values - for (let i = 0; i < vals.length; i++){ - if (clipped[i]){ - continue; - } - if (vals[i] < min){ - owedChg += vals[i] - min; - vals[i] = min; - clipped[i] = true; - } else if (vals[i] > max){ - owedChg += vals[i] - max; - vals[i] = max; - clipped[i] = true; - } - } - if (Math.abs(owedChg) < Number.EPSILON){ - return vals; - } - // Compensate for changes made - let indicesToUpdate = (owedChg > 0) ? - range(vals.length).filter(idx => vals[idx] < max) : - range(vals.length).filter(idx => vals[idx] > min); - if (indicesToUpdate.length == 0){ - return null; - } - for (let i of indicesToUpdate){ - vals[i] += owedChg / indicesToUpdate.length; - } - owedChg = 0; - } -} -// Usable to iterate through possible int arrays with ascending values in the range 0 to maxLen-1, starting with [0] - // eg: With maxLen 3, updates [0] to [0,1], then to [0,2], then [0,1,2] -// Returns false when there is no next array -export function updateAscSeq(seq: number[], maxLen: number): boolean { - // Try increasing last element, then preceding elements, then extending the array - let i = seq.length - 1; - while (true){ - if (i > 0 && seq[i] < (maxLen - 1) - (seq.length - 1 - i)){ - seq[i]++; - return true; - } else if (i > 0){ - i--; - } else { - if (seq.length < maxLen){ - seq.push(0); - seq.splice(0, seq.length, ...range(seq.length)); - return true; - } else { - return false; - } - } - } -} -// Given a non-empty array of non-negative weights, returns an array index chosen with weighted pseudorandomness -// Returns null if array contains all zeros -export function randWeightedChoice(weights: number[]): number | null { - let thresholds = Array(weights.length); - let sum = 0; - for (let i = 0; i < weights.length; i++){ - sum += weights[i]; - thresholds[i] = sum; - } - let rand = Math.random(); - for (let i = 0; i < weights.length; i++){ - if (rand <= thresholds[i] / sum){ - return i; - } - } - return null; -} -// Returns a string with words first-letter capitalised -export function capitalizeWords(str: string){ - str = str.replace(/\b\w/g, x => x.toUpperCase()); // '\b' matches word boundary, '\w' is like [a-zA-Z0-9_] - str = str.replace(/(\w)'S/, '$1\'s'); // Avoid cases like "traveler's tree" -> "Traveler'S Tree" - return str; -} -// Dynamically obtains scroll bar width -// From stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript -export function getScrollBarWidth(){ - // Create hidden outer div - let outer = document.createElement('div'); - outer.style.visibility = 'hidden'; - outer.style.overflow = 'scroll'; - document.body.appendChild(outer); - // Create inner div - let inner = document.createElement('div'); - outer.appendChild(inner); - // Get width difference - let scrollBarWidth = outer.offsetWidth - inner.offsetWidth; - // Remove temporary divs - outer.parentNode!.removeChild(outer); - // - return scrollBarWidth; -} diff --git a/src/util.ts b/src/util.ts new file mode 100644 index 0000000..139e668 --- /dev/null +++ b/src/util.ts @@ -0,0 +1,135 @@ +/* + * General utility functions + */ + +export type Breakpoint = 'sm' | 'md' | 'lg'; // These represent screen sizes +export function getBreakpoint(): Breakpoint { + let w = window.innerWidth; + if (w < 768){ + return 'sm'; + } else if (w < 1024){ + return 'md'; + } else { + return 'lg'; + } +} + +// Returns [0 ... len] +export function range(len: number): number[] { + return [...Array(len).keys()]; +} +// Returns sum of array values +export function arraySum(array: number[]): number { + return array.reduce((x,y) => x+y); +} +// Returns an array of increasing evenly-spaced numbers from 'start' to 'end' with size 'size' +export function linspace(start: number, end: number, size: number): number[] { + let step = (end - start) / (size - 1); + let ar = []; + for (let i = 0; i < size; i++){ + ar.push(start + step * i); + } + return ar; +} +// Returns array copy with vals clipped to within [min,max], redistributing to compensate +// Returns null on failure +export function limitVals(arr: number[], min: number, max: number): number[] | null { + let vals = [...arr]; + let clipped = new Array(vals.length).fill(false); + let owedChg = 0; // Stores total change made after clipping values + while (true){ + // Clip values + for (let i = 0; i < vals.length; i++){ + if (clipped[i]){ + continue; + } + if (vals[i] < min){ + owedChg += vals[i] - min; + vals[i] = min; + clipped[i] = true; + } else if (vals[i] > max){ + owedChg += vals[i] - max; + vals[i] = max; + clipped[i] = true; + } + } + if (Math.abs(owedChg) < Number.EPSILON){ + return vals; + } + // Compensate for changes made + let indicesToUpdate = (owedChg > 0) ? + range(vals.length).filter(idx => vals[idx] < max) : + range(vals.length).filter(idx => vals[idx] > min); + if (indicesToUpdate.length == 0){ + return null; + } + for (let i of indicesToUpdate){ + vals[i] += owedChg / indicesToUpdate.length; + } + owedChg = 0; + } +} +// Usable to iterate through possible int arrays with ascending values in the range 0 to maxLen-1, starting with [0] + // eg: With maxLen 3, updates [0] to [0,1], then to [0,2], then [0,1,2] +// Returns false when there is no next array +export function updateAscSeq(seq: number[], maxLen: number): boolean { + // Try increasing last element, then preceding elements, then extending the array + let i = seq.length - 1; + while (true){ + if (i > 0 && seq[i] < (maxLen - 1) - (seq.length - 1 - i)){ + seq[i]++; + return true; + } else if (i > 0){ + i--; + } else { + if (seq.length < maxLen){ + seq.push(0); + seq.splice(0, seq.length, ...range(seq.length)); + return true; + } else { + return false; + } + } + } +} +// Given a non-empty array of non-negative weights, returns an array index chosen with weighted pseudorandomness +// Returns null if array contains all zeros +export function randWeightedChoice(weights: number[]): number | null { + let thresholds = Array(weights.length); + let sum = 0; + for (let i = 0; i < weights.length; i++){ + sum += weights[i]; + thresholds[i] = sum; + } + let rand = Math.random(); + for (let i = 0; i < weights.length; i++){ + if (rand <= thresholds[i] / sum){ + return i; + } + } + return null; +} +// Returns a string with words first-letter capitalised +export function capitalizeWords(str: string){ + str = str.replace(/\b\w/g, x => x.toUpperCase()); // '\b' matches word boundary, '\w' is like [a-zA-Z0-9_] + str = str.replace(/(\w)'S/, '$1\'s'); // Avoid cases like "traveler's tree" -> "Traveler'S Tree" + return str; +} +// Dynamically obtains scroll bar width +// From stackoverflow.com/questions/13382516/getting-scroll-bar-width-using-javascript +export function getScrollBarWidth(){ + // Create hidden outer div + let outer = document.createElement('div'); + outer.style.visibility = 'hidden'; + outer.style.overflow = 'scroll'; + document.body.appendChild(outer); + // Create inner div + let inner = document.createElement('div'); + outer.appendChild(inner); + // Get width difference + let scrollBarWidth = outer.offsetWidth - inner.offsetWidth; + // Remove temporary divs + outer.parentNode!.removeChild(outer); + // + return scrollBarWidth; +} |
