mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-18 21:45:31 +09:00
refactor(app): merge duplicated components and stores (#3383)
This commit is contained in:
1 parent
c16d414d93
commit
5695510980
202 files changed
+2924
-6532
No files matched your search
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@pkgs/list_utils",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./src/*": "./src/*",
|
||||
"./*": "./*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fluxer/constants": "workspace:*"
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Locales} from '@fluxer/constants/src/Locales';
|
||||
import {getCachedListFormatter} from '@pkgs/list_utils/src/ListFormattingCache';
|
||||
import {formatListWithFallback} from '@pkgs/list_utils/src/ListFormattingFallback';
|
||||
import {isIntlListFormatLocaleSupported, isIntlListFormatSupported} from '@pkgs/list_utils/src/ListFormattingSupport';
|
||||
import type {
|
||||
ListFormatStyle,
|
||||
ListFormatType,
|
||||
ListFormatterConfig,
|
||||
ResolvedListFormatterConfig,
|
||||
} from '@pkgs/list_utils/src/ListFormattingTypes';
|
||||
|
||||
const DEFAULT_LOCALE = Locales.EN_US;
|
||||
const DEFAULT_LIST_FORMAT_STYLE: ListFormatStyle = 'long';
|
||||
const DEFAULT_LIST_FORMAT_TYPE: ListFormatType = 'conjunction';
|
||||
|
||||
function normalizeLocale(locale: string): string {
|
||||
if (!isIntlListFormatLocaleSupported(locale)) {
|
||||
return DEFAULT_LOCALE;
|
||||
}
|
||||
return locale;
|
||||
}
|
||||
|
||||
function resolveListFormatterConfig(config: ListFormatterConfig): ResolvedListFormatterConfig {
|
||||
const requestedLocale = config.locale?.trim();
|
||||
const locale = requestedLocale == null || requestedLocale === '' ? DEFAULT_LOCALE : normalizeLocale(requestedLocale);
|
||||
const style = config.style ?? DEFAULT_LIST_FORMAT_STYLE;
|
||||
const type = config.type ?? DEFAULT_LIST_FORMAT_TYPE;
|
||||
return {
|
||||
locale,
|
||||
style,
|
||||
type,
|
||||
};
|
||||
}
|
||||
|
||||
function formatWithIntl(items: ReadonlyArray<string>, config: ResolvedListFormatterConfig): string {
|
||||
try {
|
||||
return getCachedListFormatter(config).format(items);
|
||||
} catch {
|
||||
return formatListWithFallback(items, config.type);
|
||||
}
|
||||
}
|
||||
|
||||
function formatItems(items: ReadonlyArray<string>, config: ResolvedListFormatterConfig): string {
|
||||
if (items.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (items.length === 1) {
|
||||
return items[0] ?? '';
|
||||
}
|
||||
if (!isIntlListFormatSupported()) {
|
||||
return formatListWithFallback(items, config.type);
|
||||
}
|
||||
return formatWithIntl(items, config);
|
||||
}
|
||||
|
||||
export function formatListWithConfig(items: ReadonlyArray<string>, config: ListFormatterConfig = {}): string {
|
||||
const resolvedConfig = resolveListFormatterConfig(config);
|
||||
return formatItems(items, resolvedConfig);
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ResolvedListFormatterConfig} from '@pkgs/list_utils/src/ListFormattingTypes';
|
||||
|
||||
const formatterCache = new Map<string, Intl.ListFormat>();
|
||||
|
||||
export function getCachedListFormatter(config: ResolvedListFormatterConfig): Intl.ListFormat {
|
||||
const cacheKey = `${config.locale}:${config.style}:${config.type}`;
|
||||
const cachedFormatter = formatterCache.get(cacheKey);
|
||||
if (cachedFormatter != null) {
|
||||
return cachedFormatter;
|
||||
}
|
||||
const formatter = new Intl.ListFormat(config.locale, {
|
||||
style: config.style,
|
||||
type: config.type,
|
||||
});
|
||||
formatterCache.set(cacheKey, formatter);
|
||||
return formatter;
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import type {ListFormatType} from '@pkgs/list_utils/src/ListFormattingTypes';
|
||||
|
||||
function getFallbackConjunction(type: ListFormatType): string {
|
||||
if (type === 'disjunction') {
|
||||
return 'or';
|
||||
}
|
||||
return 'and';
|
||||
}
|
||||
|
||||
export function formatListWithFallback(items: ReadonlyArray<string>, type: ListFormatType): string {
|
||||
if (items.length === 0) {
|
||||
return '';
|
||||
}
|
||||
if (items.length === 1) {
|
||||
return items[0] ?? '';
|
||||
}
|
||||
if (type === 'unit') {
|
||||
return items.join(', ');
|
||||
}
|
||||
const conjunction = getFallbackConjunction(type);
|
||||
if (items.length === 2) {
|
||||
return `${items[0]} ${conjunction} ${items[1]}`;
|
||||
}
|
||||
const lastItem = items[items.length - 1];
|
||||
const leadingItems = items.slice(0, -1).join(', ');
|
||||
return `${leadingItems}, ${conjunction} ${lastItem}`;
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export function isIntlListFormatSupported(): boolean {
|
||||
return typeof Intl !== 'undefined' && typeof Intl.ListFormat !== 'undefined';
|
||||
}
|
||||
|
||||
export function isIntlListFormatLocaleSupported(locale: string): boolean {
|
||||
if (!isIntlListFormatSupported()) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
return Intl.ListFormat.supportedLocalesOf([locale]).length > 0;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export type ListFormatStyle = 'long' | 'short' | 'narrow';
|
||||
export type ListFormatType = 'conjunction' | 'disjunction' | 'unit';
|
||||
|
||||
export interface ListFormatOptions {
|
||||
style?: ListFormatStyle;
|
||||
type?: ListFormatType;
|
||||
}
|
||||
|
||||
export interface ListFormatterConfig extends ListFormatOptions {
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
export interface ResolvedListFormatterConfig {
|
||||
locale: string;
|
||||
style: ListFormatStyle;
|
||||
type: ListFormatType;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "../../../tsconfigs/package.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@fluxer/*": ["../../../packages/*", "../../../packages/*/src/index.ts"],
|
||||
"@pkgs/*": ["../*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"name": "@pkgs/number_utils",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@fluxer/constants": "workspace:*"
|
||||
},
|
||||
"exports": {
|
||||
"./src/*": "./src/*",
|
||||
"./*": "./*"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {Locales} from '@fluxer/constants/src/Locales';
|
||||
|
||||
export const DEFAULT_COMPACT_MAX_FRACTION_DIGITS = 1;
|
||||
export const DEFAULT_NUMBER_FALLBACK = 0;
|
||||
export const DEFAULT_NUMBER_LOCALE = Locales.EN_US;
|
||||
@@ -1,25 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
const numberFormatterCache = new Map<string, Intl.NumberFormat>();
|
||||
|
||||
export function getNumberFormatter(
|
||||
locale: string,
|
||||
numberFormatOptions: Intl.NumberFormatOptions = {},
|
||||
): Intl.NumberFormat {
|
||||
const cacheKey = buildFormatterCacheKey(locale, numberFormatOptions);
|
||||
const cachedFormatter = numberFormatterCache.get(cacheKey);
|
||||
if (cachedFormatter !== undefined) {
|
||||
return cachedFormatter;
|
||||
}
|
||||
const formatter = new Intl.NumberFormat(locale, numberFormatOptions);
|
||||
numberFormatterCache.set(cacheKey, formatter);
|
||||
return formatter;
|
||||
}
|
||||
|
||||
function buildFormatterCacheKey(locale: string, numberFormatOptions: Intl.NumberFormatOptions): string {
|
||||
const optionEntries = Object.entries(numberFormatOptions)
|
||||
.filter(([, value]) => value !== undefined)
|
||||
.sort(([left], [right]) => left.localeCompare(right));
|
||||
const optionKey = optionEntries.map(([key, value]) => `${key}:${String(value)}`).join('|');
|
||||
return `${locale}|${optionKey}`;
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {
|
||||
DEFAULT_COMPACT_MAX_FRACTION_DIGITS,
|
||||
DEFAULT_NUMBER_FALLBACK,
|
||||
DEFAULT_NUMBER_LOCALE,
|
||||
} from '@pkgs/number_utils/src/NumberConstants';
|
||||
import {getNumberFormatter} from '@pkgs/number_utils/src/NumberFormatterCache';
|
||||
import {parseNumberInput} from '@pkgs/number_utils/src/NumberParsing';
|
||||
import type {
|
||||
CompactNumberFormatOptions,
|
||||
NumberFormatBaseOptions,
|
||||
NumberFormatOptions,
|
||||
NumberInput,
|
||||
} from '@pkgs/number_utils/src/NumberTypes';
|
||||
|
||||
interface ResolvedNumberFormatBaseOptions {
|
||||
locale: string;
|
||||
fallbackValue: number;
|
||||
}
|
||||
|
||||
interface CompactFormatOptionsInput {
|
||||
maximumFractionDigits?: number;
|
||||
minimumFractionDigits?: number;
|
||||
}
|
||||
|
||||
function resolveBaseOptions(options?: NumberFormatBaseOptions): ResolvedNumberFormatBaseOptions {
|
||||
return {
|
||||
locale: options?.locale ?? DEFAULT_NUMBER_LOCALE,
|
||||
fallbackValue: options?.fallbackValue ?? DEFAULT_NUMBER_FALLBACK,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveNumberFormatOptions(optionsOrLocale: NumberFormatOptions | string | undefined): NumberFormatOptions {
|
||||
if (typeof optionsOrLocale === 'string') {
|
||||
return {locale: optionsOrLocale};
|
||||
}
|
||||
return optionsOrLocale ?? {};
|
||||
}
|
||||
|
||||
function resolveCompactFormatOptions(
|
||||
optionsOrLocale: CompactNumberFormatOptions | string | undefined,
|
||||
maximumFractionDigits: number | undefined,
|
||||
): CompactNumberFormatOptions {
|
||||
if (typeof optionsOrLocale === 'string') {
|
||||
return {
|
||||
locale: optionsOrLocale,
|
||||
maximumFractionDigits,
|
||||
};
|
||||
}
|
||||
if (optionsOrLocale === undefined) {
|
||||
if (maximumFractionDigits === undefined) {
|
||||
return {};
|
||||
}
|
||||
return {maximumFractionDigits};
|
||||
}
|
||||
if (maximumFractionDigits === undefined) {
|
||||
return optionsOrLocale;
|
||||
}
|
||||
return {
|
||||
...optionsOrLocale,
|
||||
maximumFractionDigits: optionsOrLocale.maximumFractionDigits ?? maximumFractionDigits,
|
||||
};
|
||||
}
|
||||
|
||||
function formatNumberValue(
|
||||
value: NumberInput,
|
||||
resolvedOptions: ResolvedNumberFormatBaseOptions,
|
||||
numberFormatOptions: Intl.NumberFormatOptions = {},
|
||||
): string {
|
||||
const parsedValue = parseNumberInput(value, resolvedOptions.fallbackValue);
|
||||
return getNumberFormatter(resolvedOptions.locale, numberFormatOptions).format(parsedValue);
|
||||
}
|
||||
|
||||
function buildCompactFormatOptions(options: CompactFormatOptionsInput): Intl.NumberFormatOptions {
|
||||
const numberFormatOptions: Intl.NumberFormatOptions = {
|
||||
notation: 'compact',
|
||||
maximumFractionDigits: options.maximumFractionDigits ?? DEFAULT_COMPACT_MAX_FRACTION_DIGITS,
|
||||
};
|
||||
if (options.minimumFractionDigits !== undefined) {
|
||||
numberFormatOptions.minimumFractionDigits = options.minimumFractionDigits;
|
||||
}
|
||||
return numberFormatOptions;
|
||||
}
|
||||
|
||||
export function formatNumber(value: NumberInput, locale?: string): string;
|
||||
export function formatNumber(value: NumberInput, options?: NumberFormatOptions): string;
|
||||
export function formatNumber(value: NumberInput, optionsOrLocale: NumberFormatOptions | string = {}): string {
|
||||
const options = resolveNumberFormatOptions(optionsOrLocale);
|
||||
const resolvedOptions = resolveBaseOptions(options);
|
||||
return formatNumberValue(value, resolvedOptions, options.numberFormatOptions);
|
||||
}
|
||||
|
||||
export function formatCompactNumber(value: NumberInput, locale?: string, maximumFractionDigits?: number): string;
|
||||
export function formatCompactNumber(value: NumberInput, options?: CompactNumberFormatOptions): string;
|
||||
export function formatCompactNumber(
|
||||
value: NumberInput,
|
||||
optionsOrLocale: CompactNumberFormatOptions | string = {},
|
||||
maximumFractionDigits?: number,
|
||||
): string {
|
||||
const options = resolveCompactFormatOptions(optionsOrLocale, maximumFractionDigits);
|
||||
const resolvedOptions = resolveBaseOptions(options);
|
||||
return formatNumberValue(value, resolvedOptions, buildCompactFormatOptions(options));
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
import {DEFAULT_NUMBER_FALLBACK} from '@pkgs/number_utils/src/NumberConstants';
|
||||
import type {NumberInput} from '@pkgs/number_utils/src/NumberTypes';
|
||||
|
||||
export function parseNumberInput(value: NumberInput, fallbackValue: number = DEFAULT_NUMBER_FALLBACK): number {
|
||||
if (typeof value === 'number') {
|
||||
if (Number.isFinite(value)) {
|
||||
return value;
|
||||
}
|
||||
return fallbackValue;
|
||||
}
|
||||
if (typeof value === 'string') {
|
||||
const trimmedValue = value.trim();
|
||||
if (trimmedValue === '') {
|
||||
return fallbackValue;
|
||||
}
|
||||
const parsedValue = Number(trimmedValue);
|
||||
if (Number.isFinite(parsedValue)) {
|
||||
return parsedValue;
|
||||
}
|
||||
}
|
||||
return fallbackValue;
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
export type NumberInput = number | string | null | undefined;
|
||||
|
||||
export interface NumberFormatBaseOptions {
|
||||
locale?: string;
|
||||
fallbackValue?: number;
|
||||
}
|
||||
|
||||
export interface NumberFormatOptions extends NumberFormatBaseOptions {
|
||||
numberFormatOptions?: Intl.NumberFormatOptions;
|
||||
}
|
||||
|
||||
export interface CompactNumberFormatOptions extends NumberFormatBaseOptions {
|
||||
maximumFractionDigits?: number;
|
||||
minimumFractionDigits?: number;
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"extends": "../../../tsconfigs/package.json",
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@fluxer/*": ["../../../packages/*", "../../../packages/*/src/index.ts"],
|
||||
"@pkgs/*": ["../*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user