@knime/utils 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +710 -0
- package/README.md +23 -0
- package/package.json +40 -0
- package/src/capitalize.js +33 -0
- package/src/config/globals.d.ts +3 -0
- package/src/copyText.js +10 -0
- package/src/dateMinMaxCheck.js +57 -0
- package/src/debounce.ts +28 -0
- package/src/fileTypeIcons.ts +41 -0
- package/src/filters.ts +114 -0
- package/src/format.js +70 -0
- package/src/formatHotkeys.ts +98 -0
- package/src/getWrappedAroundIndex.ts +9 -0
- package/src/index.ts +50 -0
- package/src/localTimezone.js +7 -0
- package/src/navigator.ts +11 -0
- package/src/numIntegerDigits.js +7 -0
- package/src/sleep.ts +6 -0
- package/src/svgWithTitle.js +31 -0
- package/src/truncateString.js +7 -0
- package/src/updateDate.js +22 -0
- package/src/updateTime.js +19 -0
- package/test-utils/index.ts +3 -0
- package/test-utils/muteConsole.js +46 -0
package/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#  @knime/utils
|
|
2
|
+
|
|
3
|
+
Internal utility functions and constants shared across `@knime` packages.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
To install the `@knime/utils` package, you can use npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install @knime/utils
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
To use it in your project, you can import it as follows:
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
import { copyText } from "@knime/utils";
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
# Join the Community!
|
|
22
|
+
|
|
23
|
+
- [KNIME Forum](https://forum.knime.com/)
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@knime/utils",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Internal utility functions and constants shared across @knime packages.",
|
|
5
|
+
"homepage": "https://knime.github.io/webapps-common/",
|
|
6
|
+
"license": "GPL 3 and Additional Permissions according to Sec. 7 (SEE the file LICENSE)",
|
|
7
|
+
"author": "KNIME AG, Zurich, Switzerland",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test:unit": "TZ='Europe/Berlin' vitest"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@knime/styles": "1.0.0",
|
|
14
|
+
"consola": "2.15.3",
|
|
15
|
+
"date-fns": "2.30.0",
|
|
16
|
+
"date-fns-tz": "2.0.0"
|
|
17
|
+
},
|
|
18
|
+
"main": "src/index.ts",
|
|
19
|
+
"module": "src/index.ts",
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"!src/__tests__",
|
|
23
|
+
"test-utils",
|
|
24
|
+
"CHANGELOG.md"
|
|
25
|
+
],
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"import": "./src/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"./globals.d.ts": {
|
|
31
|
+
"import": "./src/config/globals.d.ts"
|
|
32
|
+
},
|
|
33
|
+
"./test-utils": {
|
|
34
|
+
"import": "./test-utils/index.ts"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"flush-promises": "^1.0.2"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Uppercases first letter of string
|
|
3
|
+
* @param {String} str to be capitalized first string, e.g. 'workflow'
|
|
4
|
+
* @return {String} Capitalized String, e.g. Workflow
|
|
5
|
+
*/
|
|
6
|
+
export const capitalize = (str) =>
|
|
7
|
+
str.charAt(0).toUpperCase() + str.substring(1);
|
|
8
|
+
|
|
9
|
+
const formats = {
|
|
10
|
+
customFormat: (str, delimiter) =>
|
|
11
|
+
capitalize(str.replace(delimiter, " ").toLowerCase()),
|
|
12
|
+
kebabFormat: (str) => formats.customFormat(str, "-"),
|
|
13
|
+
snakeFormat: (str) => formats.customFormat(str, "_"),
|
|
14
|
+
camelPascalCase: (str) =>
|
|
15
|
+
capitalize(
|
|
16
|
+
str
|
|
17
|
+
.match(/([A-Z]?[^A-Z]*)/g)
|
|
18
|
+
.slice(0, -1)
|
|
19
|
+
.join(" ")
|
|
20
|
+
.toLowerCase(),
|
|
21
|
+
),
|
|
22
|
+
[undefined]: (str) => capitalize(str), // eslint-disable-line no-undefined
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const caseFormatter = ({ string, delimiter, format }) => {
|
|
26
|
+
if (delimiter) {
|
|
27
|
+
return formats.customFormat(string, delimiter);
|
|
28
|
+
}
|
|
29
|
+
if (formats) {
|
|
30
|
+
return formats[format](string);
|
|
31
|
+
}
|
|
32
|
+
return capitalize(string);
|
|
33
|
+
};
|
package/src/copyText.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const copyText = (text) => {
|
|
2
|
+
let tmpElement = document.createElement("textarea");
|
|
3
|
+
tmpElement.style = "position:fixed;pointer-events:none;opacity:0;";
|
|
4
|
+
tmpElement.setAttribute("readonly", "");
|
|
5
|
+
tmpElement.value = text;
|
|
6
|
+
document.body.appendChild(tmpElement);
|
|
7
|
+
tmpElement.select();
|
|
8
|
+
document.execCommand("copy");
|
|
9
|
+
document.body.removeChild(tmpElement);
|
|
10
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { isAfter, isBefore } from "date-fns";
|
|
2
|
+
import updateTime from "./updateTime";
|
|
3
|
+
import updateDate from "./updateDate";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if date is before min.
|
|
7
|
+
* @param {Date} date
|
|
8
|
+
* @param {Date | null} min
|
|
9
|
+
* @param {Boolean} checkDate - check date part (year, day, month).
|
|
10
|
+
* @param {Boolean} checkTime - check time part (hour, minutes etc.).
|
|
11
|
+
* @returns {Boolean} whether limit is kept or not.
|
|
12
|
+
*/
|
|
13
|
+
export const isBeforeMinDate = (date, min, checkDate, checkTime) => {
|
|
14
|
+
if (min) {
|
|
15
|
+
if (checkTime && checkDate) {
|
|
16
|
+
return isBefore(date, min);
|
|
17
|
+
}
|
|
18
|
+
if (checkTime) {
|
|
19
|
+
// only time is visible so ignore date: use same datre as base
|
|
20
|
+
const base = new Date(0);
|
|
21
|
+
return isBefore(updateTime(base, date), updateTime(base, min));
|
|
22
|
+
}
|
|
23
|
+
if (checkDate) {
|
|
24
|
+
// only date is visible so ignore time: use same time as base
|
|
25
|
+
const base = new Date(0);
|
|
26
|
+
return isBefore(updateDate(base, date), updateDate(base, min));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return false;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Check
|
|
34
|
+
* @param {Date} date - date to check.
|
|
35
|
+
* @param {Date | null} max - maximum date.
|
|
36
|
+
* @param {Boolean} checkDate - check date part (year, day, month).
|
|
37
|
+
* @param {Boolean} checkTime - check time part (hour, minutes etc.).
|
|
38
|
+
* @returns {Boolean} whether limit is kept or not.
|
|
39
|
+
*/
|
|
40
|
+
export const isAfterMaxDate = (date, max, checkDate, checkTime) => {
|
|
41
|
+
if (max) {
|
|
42
|
+
if (checkTime && checkDate) {
|
|
43
|
+
return isAfter(date, max);
|
|
44
|
+
}
|
|
45
|
+
if (checkTime) {
|
|
46
|
+
// only time is visible so ignore date: use same datre as base
|
|
47
|
+
const base = new Date(0);
|
|
48
|
+
return isAfter(updateTime(base, date), updateTime(base, max));
|
|
49
|
+
}
|
|
50
|
+
if (checkDate) {
|
|
51
|
+
// use same time base
|
|
52
|
+
const base = new Date(0);
|
|
53
|
+
return isAfter(updateDate(base, date), updateDate(base, max));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return false;
|
|
57
|
+
};
|
package/src/debounce.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Use this method to reduce many method calls to a few.
|
|
2
|
+
// It's an 'immediate' implementation, so it will call the callbackindex: number once immediately an then again after the given wait.
|
|
3
|
+
|
|
4
|
+
type CallbackFunc<T> = (value: T, index: number) => void;
|
|
5
|
+
|
|
6
|
+
const debounce = <T>(callback: CallbackFunc<T>, wait: number) => {
|
|
7
|
+
let timer: ReturnType<typeof setTimeout>;
|
|
8
|
+
let lastCall = 0;
|
|
9
|
+
|
|
10
|
+
return (...args: Parameters<typeof callback>) => {
|
|
11
|
+
const [value, index] = args;
|
|
12
|
+
clearTimeout(timer);
|
|
13
|
+
const now = Date.now();
|
|
14
|
+
const timeFromLastCall = now - lastCall;
|
|
15
|
+
|
|
16
|
+
if (timeFromLastCall > wait) {
|
|
17
|
+
lastCall = now;
|
|
18
|
+
callback(value, index);
|
|
19
|
+
} else {
|
|
20
|
+
timer = setTimeout(() => {
|
|
21
|
+
lastCall = now;
|
|
22
|
+
callback(value, index);
|
|
23
|
+
}, wait);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default debounce;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import csvIcon from "@knime/styles/img/icons/file-csv.svg";
|
|
2
|
+
import docxIcon from "@knime/styles/img/icons/file-docx.svg";
|
|
3
|
+
import htmlIcon from "@knime/styles/img/icons/file-html.svg";
|
|
4
|
+
import mdIcon from "@knime/styles/img/icons/file-md.svg";
|
|
5
|
+
import odpIcon from "@knime/styles/img/icons/file-odp.svg";
|
|
6
|
+
import odsIcon from "@knime/styles/img/icons/file-ods.svg";
|
|
7
|
+
import odtIcon from "@knime/styles/img/icons/file-odt.svg";
|
|
8
|
+
import pdfIcon from "@knime/styles/img/icons/file-pdf.svg";
|
|
9
|
+
import pptxIcon from "@knime/styles/img/icons/file-pptx.svg";
|
|
10
|
+
import psIcon from "@knime/styles/img/icons/file-ps.svg";
|
|
11
|
+
import xlsIcon from "@knime/styles/img/icons/file-xls.svg";
|
|
12
|
+
import xlsxIcon from "@knime/styles/img/icons/file-xlsx.svg";
|
|
13
|
+
import xmlIcon from "@knime/styles/img/icons/file-xml.svg";
|
|
14
|
+
import zipIcon from "@knime/styles/img/icons/file-zip.svg";
|
|
15
|
+
import exeIcon from "@knime/styles/img/icons/file-zip-exe.svg";
|
|
16
|
+
import txtIcon from "@knime/styles/img/icons/file-text.svg";
|
|
17
|
+
import fileIcon from "@knime/styles/img/icons/file-question.svg";
|
|
18
|
+
|
|
19
|
+
export const icons = {
|
|
20
|
+
csvIcon,
|
|
21
|
+
docxIcon,
|
|
22
|
+
htmlIcon,
|
|
23
|
+
mdIcon,
|
|
24
|
+
odpIcon,
|
|
25
|
+
odsIcon,
|
|
26
|
+
odtIcon,
|
|
27
|
+
pdfIcon,
|
|
28
|
+
pptxIcon,
|
|
29
|
+
psIcon,
|
|
30
|
+
xlsIcon,
|
|
31
|
+
xlsxIcon,
|
|
32
|
+
xmlIcon,
|
|
33
|
+
zipIcon,
|
|
34
|
+
exeIcon,
|
|
35
|
+
txtIcon,
|
|
36
|
+
fileIcon,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export const isIconExisting = function (iconName: string): boolean {
|
|
40
|
+
return icons.hasOwnProperty(iconName);
|
|
41
|
+
};
|
package/src/filters.ts
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* These definitions can be used to filter an array. Each entry should contain a method normalize which can be used to
|
|
3
|
+
* compile any filter settings and a method "test" which takes the compiled result, additional settings and the item to
|
|
4
|
+
* be matched/filtered
|
|
5
|
+
* */
|
|
6
|
+
interface FilterDefinition<SearchTerm, Normalized, Testable> {
|
|
7
|
+
id: string;
|
|
8
|
+
normalize: (
|
|
9
|
+
searchTerm: SearchTerm,
|
|
10
|
+
caseSensitiveSearch: boolean,
|
|
11
|
+
) => Normalized;
|
|
12
|
+
test: (
|
|
13
|
+
text: Testable,
|
|
14
|
+
normalizedSearchTerm: Normalized,
|
|
15
|
+
caseSensitiveSearch: boolean,
|
|
16
|
+
inverseSearch: boolean,
|
|
17
|
+
) => boolean;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface StringTest {
|
|
21
|
+
test(str: string): boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type SearchFilterDefinition = FilterDefinition<string, string, string>;
|
|
25
|
+
type PatternFilterDefinition = FilterDefinition<
|
|
26
|
+
string,
|
|
27
|
+
RegExp | StringTest,
|
|
28
|
+
string
|
|
29
|
+
>;
|
|
30
|
+
type TypeFilterDefinition = FilterDefinition<
|
|
31
|
+
string[],
|
|
32
|
+
StringTest,
|
|
33
|
+
string | undefined
|
|
34
|
+
>;
|
|
35
|
+
|
|
36
|
+
const modeDefinitions = [
|
|
37
|
+
{
|
|
38
|
+
id: "search",
|
|
39
|
+
normalize(searchTerm, caseSensitiveSearch) {
|
|
40
|
+
return caseSensitiveSearch ? searchTerm : searchTerm.toLowerCase();
|
|
41
|
+
},
|
|
42
|
+
test(text, normalizedSearchTerm, caseSensitiveSearch, inverseSearch) {
|
|
43
|
+
const testText = caseSensitiveSearch ? text : text.toLowerCase();
|
|
44
|
+
const matches = testText.includes(normalizedSearchTerm);
|
|
45
|
+
return inverseSearch ? !matches : matches;
|
|
46
|
+
},
|
|
47
|
+
} satisfies SearchFilterDefinition,
|
|
48
|
+
{
|
|
49
|
+
id: "wildcard",
|
|
50
|
+
normalize(searchTerm, caseSensitiveSearch) {
|
|
51
|
+
// Do a regex search, explicitly matching start and end of the search term.
|
|
52
|
+
// All regex special character except from "* and ?" (wildcards) are escaped.
|
|
53
|
+
if (searchTerm.length > 0) {
|
|
54
|
+
const escapedSearchTerm = searchTerm.replace(
|
|
55
|
+
/[-[\]{}()+.,\\^$|#\s]/g,
|
|
56
|
+
"\\$&",
|
|
57
|
+
);
|
|
58
|
+
const wildcardSearchTerm = escapedSearchTerm
|
|
59
|
+
.replace(/\*/g, ".*")
|
|
60
|
+
.replace(/\?/g, ".?");
|
|
61
|
+
searchTerm = `^${wildcardSearchTerm}$`;
|
|
62
|
+
} else {
|
|
63
|
+
return { test: () => false };
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
const flags = caseSensitiveSearch ? "" : "i";
|
|
67
|
+
return new RegExp(searchTerm, flags);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
// In case of an invalid regular expression, an impossible
|
|
70
|
+
// regex is returned, not matching anything.
|
|
71
|
+
return /$^/;
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
test(text, normalizedSearchTerm, caseSensitiveSearch, inverseSearch) {
|
|
75
|
+
const matches = normalizedSearchTerm.test(text);
|
|
76
|
+
return inverseSearch ? !matches : matches;
|
|
77
|
+
},
|
|
78
|
+
} satisfies PatternFilterDefinition,
|
|
79
|
+
{
|
|
80
|
+
id: "regex",
|
|
81
|
+
normalize(searchTerm, caseSensitiveSearch) {
|
|
82
|
+
try {
|
|
83
|
+
const flags = caseSensitiveSearch ? "" : "i";
|
|
84
|
+
return new RegExp(`^(${searchTerm})$`, flags);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
// In case of an invalid regular expression, an impossible
|
|
87
|
+
// regex is returned, not matching anything.
|
|
88
|
+
return /$^/;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
test(text, normalizedSearchTerm, caseSensitiveSearch, inverseSearch) {
|
|
92
|
+
const matches = normalizedSearchTerm.test(text);
|
|
93
|
+
return inverseSearch ? !matches : matches;
|
|
94
|
+
},
|
|
95
|
+
} satisfies PatternFilterDefinition,
|
|
96
|
+
{
|
|
97
|
+
id: "type",
|
|
98
|
+
normalize(selectedTypes) {
|
|
99
|
+
return { test: (type: string) => selectedTypes.includes(type) };
|
|
100
|
+
},
|
|
101
|
+
test(type, normalizedSearchTerm) {
|
|
102
|
+
if (typeof type === "undefined") {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return normalizedSearchTerm.test(type);
|
|
106
|
+
},
|
|
107
|
+
} satisfies TypeFilterDefinition,
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
// convert [{id: "key1", text: "asdf"}, ...] to {"key1": {id:"key1", text: "asdf"} ... }
|
|
111
|
+
export const filters: Record<
|
|
112
|
+
string,
|
|
113
|
+
FilterDefinition<any, any, any>
|
|
114
|
+
> = Object.assign({}, ...modeDefinitions.map((obj) => ({ [obj.id]: obj })));
|
package/src/format.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import getLocalTimeZone from "./localTimezone";
|
|
2
|
+
/**
|
|
3
|
+
* Converts the specified date String to human readable text containing the day, month, and year.
|
|
4
|
+
* Example: '2018-07-31T09:44:31+00:00' => 'Jul 31, 2018'
|
|
5
|
+
*
|
|
6
|
+
* @param {Boolean} useTimeZone to set the timeZone where the user is located, and display local date and time
|
|
7
|
+
* @param {String | number} dateString String representation of the Date to format
|
|
8
|
+
* @returns {String} human readable date String
|
|
9
|
+
*/
|
|
10
|
+
export const formatDateString = (dateString, useTimeZone = false) => {
|
|
11
|
+
let date = new Date(dateString);
|
|
12
|
+
if (isNaN(date)) {
|
|
13
|
+
throw Error("Invalid Date format");
|
|
14
|
+
}
|
|
15
|
+
// This only works for the default locale of en-US, as support of other locals in node js might not be available
|
|
16
|
+
// See for example this issue https://github.com/nodejs/node/issues/8500
|
|
17
|
+
let dateOptions = { day: "numeric", month: "short", year: "numeric" };
|
|
18
|
+
if (useTimeZone) {
|
|
19
|
+
dateOptions.timeZone = getLocalTimeZone();
|
|
20
|
+
}
|
|
21
|
+
return date.toLocaleDateString("en-US", dateOptions);
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Converts the specified time String to human readable text containing the hour (12-hour) and minute.
|
|
25
|
+
* Example: '2018-07-31T09:44:31+00:00' => '9:44 AM'
|
|
26
|
+
*
|
|
27
|
+
* @param {Boolean} useTimeZone to set the timeZone where the user is located, and display local date and time
|
|
28
|
+
* @param {String | number} timeString String representation of the Date (including time) to format
|
|
29
|
+
* @returns {String} human readable time String
|
|
30
|
+
*/
|
|
31
|
+
export const formatTimeString = (timeString, useTimeZone = false) => {
|
|
32
|
+
let time = new Date(timeString);
|
|
33
|
+
if (isNaN(time)) {
|
|
34
|
+
throw Error("Invalid Date format");
|
|
35
|
+
}
|
|
36
|
+
// This only works for the default locale of en-US, as support of other locals in node js might not be available
|
|
37
|
+
// See for example this issue https://github.com/nodejs/node/issues/8500
|
|
38
|
+
|
|
39
|
+
let timeOptions = { hour: "numeric", minute: "2-digit" };
|
|
40
|
+
if (useTimeZone) {
|
|
41
|
+
timeOptions.timeZone = getLocalTimeZone();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return time.toLocaleTimeString("en-US", timeOptions);
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Converts the specified time String to human readable text containing the date (day, month, year) and time (hour,
|
|
48
|
+
* minute; 12-hour).
|
|
49
|
+
* Example: '2018-07-31T09:44:31+00:00' => 'Jul 31, 2018 9:44 AM'
|
|
50
|
+
*
|
|
51
|
+
* @param {Boolean} useTimeZone to set the timeZone where the user is located, and display local date and time
|
|
52
|
+
* @param {String | number} dateTimeString String representation of the Date (including time and date) to format
|
|
53
|
+
* @returns {String} human readable date/time String
|
|
54
|
+
*/
|
|
55
|
+
export const formatDateTimeString = (dateTimeString, useTimeZone = false) => {
|
|
56
|
+
// eslint-disable-line arrow-body-style
|
|
57
|
+
return `${formatDateString(dateTimeString, useTimeZone)} ${formatTimeString(
|
|
58
|
+
dateTimeString,
|
|
59
|
+
useTimeZone,
|
|
60
|
+
)}`;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Converts the specified date and time to the user time zone with option to display (day, month, year) only or with time (hour,
|
|
64
|
+
* minute; 12-hour).
|
|
65
|
+
* @param {Boolean} showTime String representation of the Date to convert and format
|
|
66
|
+
* @param {String | number} date String representation of the Date to convert and format
|
|
67
|
+
* @returns {String} human readable date/time String converted to user time zone
|
|
68
|
+
*/
|
|
69
|
+
export const formatLocalDateTimeString = (date, showTime) =>
|
|
70
|
+
showTime ? formatDateTimeString(date, true) : formatDateString(date, true);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { isMac } from "./navigator";
|
|
2
|
+
|
|
3
|
+
type Keys =
|
|
4
|
+
| "F1"
|
|
5
|
+
| "F2"
|
|
6
|
+
| "F3"
|
|
7
|
+
| "F4"
|
|
8
|
+
| "F5"
|
|
9
|
+
| "F6"
|
|
10
|
+
| "F7"
|
|
11
|
+
| "F8"
|
|
12
|
+
| "F9"
|
|
13
|
+
| "F10"
|
|
14
|
+
| "F11"
|
|
15
|
+
| "F12"
|
|
16
|
+
| "PageUp"
|
|
17
|
+
| "PageDown"
|
|
18
|
+
| "ArrowUp"
|
|
19
|
+
| "ArrowRight"
|
|
20
|
+
| "ArrowDown"
|
|
21
|
+
| "ArrowLeft"
|
|
22
|
+
| "Delete"
|
|
23
|
+
| "Enter"
|
|
24
|
+
| "Backspace"
|
|
25
|
+
| "Home"
|
|
26
|
+
| "End"
|
|
27
|
+
| " "; // Space;
|
|
28
|
+
|
|
29
|
+
type Modifiers = "CtrlOrCmd" | "Ctrl" | "Alt" | "Shift";
|
|
30
|
+
|
|
31
|
+
// creates loose autocomplete by using a union of the passed-in type
|
|
32
|
+
// and the entire `string` set *except* the passed-in type
|
|
33
|
+
type LooseAutoComplete<T extends string> = T | Omit<string, T>;
|
|
34
|
+
|
|
35
|
+
export type NumberRange = `${number}-${number}`;
|
|
36
|
+
export type KnownHotkey = Keys | Modifiers | NumberRange;
|
|
37
|
+
|
|
38
|
+
export type Hotkey = LooseAutoComplete<Keys | Modifiers>;
|
|
39
|
+
|
|
40
|
+
const globalKeyMap = {
|
|
41
|
+
ArrowUp: "↑",
|
|
42
|
+
ArrowDown: "↓",
|
|
43
|
+
ArrowLeft: "←",
|
|
44
|
+
ArrowRight: "→",
|
|
45
|
+
Enter: "↵",
|
|
46
|
+
" ": "Space", // we use event.key and there space is an actual space not the code "Space"
|
|
47
|
+
CtrlOrCmd: "Ctrl",
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
const MacOSkeyMap = {
|
|
51
|
+
Shift: "⇧",
|
|
52
|
+
Delete: "⌫",
|
|
53
|
+
CtrlOrCmd: "⌘",
|
|
54
|
+
Ctrl: "⌃",
|
|
55
|
+
Alt: "⌥",
|
|
56
|
+
Enter: "↩",
|
|
57
|
+
} as const;
|
|
58
|
+
|
|
59
|
+
type KeyFormatMap = typeof MacOSkeyMap | typeof globalKeyMap;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns a string representation of a single hotkey. Replaces some special key names with symbols
|
|
63
|
+
*/
|
|
64
|
+
export const formatHotkey = (hotkey: Hotkey): string => {
|
|
65
|
+
/**
|
|
66
|
+
* Returns the symbol corresponding to the `key` based on the given
|
|
67
|
+
* `formatMap`. Otherwise, if not found, returns the same `key` value given
|
|
68
|
+
*/
|
|
69
|
+
const mapSymbols = (formatMap: KeyFormatMap) => (key: Hotkey) =>
|
|
70
|
+
formatMap[key as keyof KeyFormatMap] ?? key;
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Pipe function implementation. Receives a list of functions to execute in order
|
|
74
|
+
* and will pipe the parameter passed at the end to each function returning the final output
|
|
75
|
+
*/
|
|
76
|
+
const pipe = <T extends () => any>(
|
|
77
|
+
...fns: Array<(...args: any[]) => ReturnType<T>>
|
|
78
|
+
) => fns.reduce((f, g) => (arg) => g(f(arg)));
|
|
79
|
+
|
|
80
|
+
const macKeyFormatter = pipe(
|
|
81
|
+
mapSymbols(MacOSkeyMap),
|
|
82
|
+
mapSymbols(globalKeyMap),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const generalKeyFormatter = mapSymbols(globalKeyMap);
|
|
86
|
+
|
|
87
|
+
return isMac() ? macKeyFormatter(hotkey) : generalKeyFormatter(hotkey);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
export const getDefaultSeparator = () => " ";
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Returns a string representation of a list of hotkeys. Replaces some special key names with symbols
|
|
94
|
+
*/
|
|
95
|
+
export const formatHotkeys = (
|
|
96
|
+
hotkeys: Array<Hotkey>,
|
|
97
|
+
getSeparator = getDefaultSeparator,
|
|
98
|
+
) => hotkeys.map(formatHotkey).join(getSeparator());
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Since (a % b) yields a negative outcome for negative a, we need this method in order
|
|
3
|
+
* to correctly compute wrapped around indices in lists
|
|
4
|
+
* @param {number} index an arbitrary integer
|
|
5
|
+
* @param {number} length a positive integer
|
|
6
|
+
* @returns {number} the non-negative modulo outcome
|
|
7
|
+
*/
|
|
8
|
+
export default (index: number, length: number) =>
|
|
9
|
+
((index % length) + length) % length;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { capitalize, caseFormatter } from "./capitalize";
|
|
2
|
+
import { copyText } from "./copyText";
|
|
3
|
+
import debounce from "./debounce";
|
|
4
|
+
import { filters } from "./filters";
|
|
5
|
+
import truncateString from "./truncateString";
|
|
6
|
+
import getWrappedAroundIndex from "./getWrappedAroundIndex";
|
|
7
|
+
import sleep from "./sleep";
|
|
8
|
+
import { icons, isIconExisting } from "./fileTypeIcons";
|
|
9
|
+
import svgWithTitle from "./svgWithTitle";
|
|
10
|
+
import updateDate from "./updateDate";
|
|
11
|
+
import { isAfterMaxDate, isBeforeMinDate } from "./dateMinMaxCheck";
|
|
12
|
+
import {
|
|
13
|
+
formatDateString,
|
|
14
|
+
formatDateTimeString,
|
|
15
|
+
formatLocalDateTimeString,
|
|
16
|
+
formatTimeString,
|
|
17
|
+
} from "./format";
|
|
18
|
+
|
|
19
|
+
import getLocalTimeZone from "./localTimezone";
|
|
20
|
+
import numIntegerDigits from "./numIntegerDigits";
|
|
21
|
+
import { formatHotkeys } from "./formatHotkeys";
|
|
22
|
+
import type { Hotkey, KnownHotkey } from "./formatHotkeys";
|
|
23
|
+
import * as navigator from "./navigator";
|
|
24
|
+
|
|
25
|
+
export type { Hotkey, KnownHotkey };
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
capitalize,
|
|
29
|
+
caseFormatter,
|
|
30
|
+
copyText,
|
|
31
|
+
debounce,
|
|
32
|
+
filters,
|
|
33
|
+
truncateString,
|
|
34
|
+
getWrappedAroundIndex,
|
|
35
|
+
sleep,
|
|
36
|
+
icons,
|
|
37
|
+
isIconExisting,
|
|
38
|
+
svgWithTitle,
|
|
39
|
+
formatDateString,
|
|
40
|
+
formatDateTimeString,
|
|
41
|
+
formatLocalDateTimeString,
|
|
42
|
+
formatTimeString,
|
|
43
|
+
updateDate,
|
|
44
|
+
isAfterMaxDate,
|
|
45
|
+
getLocalTimeZone,
|
|
46
|
+
isBeforeMinDate,
|
|
47
|
+
numIntegerDigits,
|
|
48
|
+
formatHotkeys,
|
|
49
|
+
navigator,
|
|
50
|
+
};
|
package/src/navigator.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
*
|
|
3
|
+
* @returns true when the platform of the user is Mac
|
|
4
|
+
*/
|
|
5
|
+
export const isMac = () => navigator?.userAgent?.toLowerCase()?.includes("mac");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
*
|
|
9
|
+
* @returns name of the primary modifier key for the current platform
|
|
10
|
+
*/
|
|
11
|
+
export const getMetaOrCtrlKey = () => (isMac() ? "metaKey" : "ctrlKey");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Count the number of integer digits for a given number.
|
|
3
|
+
*
|
|
4
|
+
* @param {Number} x - input number.
|
|
5
|
+
* @returns {Number} - number of integer digits x has. 1234 = 4 but 1234.44 is also 4.
|
|
6
|
+
*/
|
|
7
|
+
export default (x) => Math.max(Math.floor(Math.log10(Math.abs(x))), 0) + 1;
|
package/src/sleep.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { h as createElement } from "vue";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Higher order component that decorates a SVG component (loaded with vue-svg-loader) with a `<title>` element
|
|
5
|
+
* Note that `title` attributes have no effect on `<svg>` elements, so we need to inject a DOM element.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
*
|
|
9
|
+
* import MyIcon from '~/icons/foo.svg';
|
|
10
|
+
* import svgWithTitle from '…';
|
|
11
|
+
* export default {
|
|
12
|
+
* components: {
|
|
13
|
+
* MyIcon: svgWithTitle(MyIcon, 'My Title')
|
|
14
|
+
* }
|
|
15
|
+
* };
|
|
16
|
+
*
|
|
17
|
+
* @param { Vue.component } SvgComponent The component to decorate
|
|
18
|
+
* @param { String } title The title to insert
|
|
19
|
+
* @returns { Vue.component } The component with a title inserted as the first child of `<svg>`
|
|
20
|
+
*/
|
|
21
|
+
export default (SvgComponent, title) => ({
|
|
22
|
+
render() {
|
|
23
|
+
const renderedComponent = SvgComponent.render.apply(this, createElement);
|
|
24
|
+
|
|
25
|
+
const titleEl = createElement("title", title);
|
|
26
|
+
titleEl.ns = "svg";
|
|
27
|
+
|
|
28
|
+
renderedComponent.children.unshift(titleEl);
|
|
29
|
+
return renderedComponent;
|
|
30
|
+
},
|
|
31
|
+
});
|