sliftutils 0.13.0 → 0.15.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/.cursorrules CHANGED
@@ -79,6 +79,10 @@ Coding Styles
79
79
 
80
80
  Never use environment variables. All configuration should be on the disk, or, if specific to a current run, passed as command line parameters.
81
81
 
82
+ Never use inline styles, always use the CSS helper.
83
+
84
+ Don't use as any.
85
+
82
86
 
83
87
  General Styling
84
88
  Never use em or rem. Only use px or vw/vh/%.
package/index.d.ts CHANGED
@@ -19,6 +19,7 @@ declare module "sliftutils/misc/fs" {
19
19
  }
20
20
 
21
21
  declare module "sliftutils/misc/getSecret" {
22
+ export declare function resetSecret(key: string): void;
22
23
  export declare const getSecret: {
23
24
  (key: string): Promise<string>;
24
25
  clear(key: string): void;
@@ -135,7 +136,6 @@ declare module "sliftutils/render-utils/Input" {
135
136
  declare module "sliftutils/render-utils/InputLabel" {
136
137
  import preact from "preact";
137
138
  import { InputProps } from "./Input";
138
- import { URLParamStr } from "./URLParam";
139
139
  export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
140
140
  label?: preact.ComponentChild;
141
141
  number?: boolean;
@@ -164,7 +164,9 @@ declare module "sliftutils/render-utils/InputLabel" {
164
164
  render(): preact.JSX.Element;
165
165
  }
166
166
  export declare class InputLabelURL extends preact.Component<InputLabelProps & {
167
- persisted: URLParamStr;
167
+ persisted: {
168
+ value: unknown;
169
+ };
168
170
  }> {
169
171
  render(): preact.JSX.Element;
170
172
  }
@@ -1,3 +1,4 @@
1
+ export declare function resetSecret(key: string): void;
1
2
  export declare const getSecret: {
2
3
  (key: string): Promise<string>;
3
4
  clear(key: string): void;
package/misc/getSecret.ts CHANGED
@@ -1,28 +1,99 @@
1
1
 
2
2
  import { cache } from "socket-function/src/caching";
3
- import os from "os";
4
- import fs from "fs";
3
+ import { isNode } from "typesafecss";
5
4
 
6
- export const getSecret = cache(async function getSecret(key: string): Promise<string> {
7
- const jsonIndex = key.indexOf(".json");
8
- if (jsonIndex === -1) {
9
- const filePath = os.homedir() + "/" + key;
10
- return fs.readFileSync(filePath, "utf-8").trim();
5
+ const secretStoragePrefix = "secret_";
6
+
7
+ function getStorageKey(key: string) {
8
+ return secretStoragePrefix + key.replace(/[\/\\\.]/g, "_");
9
+ }
10
+
11
+ export function resetSecret(key: string) {
12
+ if (isNode()) {
13
+ throw new Error("resetSecret is only supported in the browser");
11
14
  }
15
+ localStorage.removeItem(getStorageKey(key));
16
+ }
17
+
18
+ export const getSecret = cache(async function getSecret(key: string): Promise<string> {
19
+ if (isNode()) {
20
+ const os = await import("os");
21
+ const fs = await import("fs");
22
+ const jsonIndex = key.indexOf(".json");
23
+ if (jsonIndex === -1) {
24
+ const filePath = os.homedir() + "/" + key;
25
+ return fs.readFileSync(filePath, "utf-8").trim();
26
+ }
12
27
 
13
- const pathPart = key.slice(0, jsonIndex + ".json".length);
14
- const filePath = os.homedir() + "/" + pathPart;
15
- const contents = fs.readFileSync(filePath, "utf-8");
16
- const json = JSON.parse(contents);
28
+ const pathPart = key.slice(0, jsonIndex + ".json".length);
29
+ const filePath = os.homedir() + "/" + pathPart;
30
+ const contents = fs.readFileSync(filePath, "utf-8");
31
+ const json = JSON.parse(contents);
17
32
 
18
- const keyPart = key.slice(jsonIndex + ".json.".length);
19
- if (!keyPart) {
20
- return JSON.stringify(json);
33
+ const keyPart = key.slice(jsonIndex + ".json.".length);
34
+ if (!keyPart) {
35
+ return JSON.stringify(json);
36
+ }
37
+
38
+ const value = json[keyPart];
39
+ if (value === undefined) {
40
+ throw new Error(`Expected key "${keyPart}" in ${filePath}, was undefined`);
41
+ }
42
+ return String(value);
21
43
  }
22
44
 
23
- const value = json[keyPart];
24
- if (value === undefined) {
25
- throw new Error(`Expected key "${keyPart}" in ${filePath}, was undefined`);
45
+ // Browser implementation
46
+ const storageKey = getStorageKey(key);
47
+ const cached = localStorage.getItem(storageKey);
48
+ if (cached) {
49
+ return cached;
26
50
  }
27
- return String(value);
28
- });
51
+
52
+ // Show modal to prompt user for secret
53
+ const { showFullscreenModal, FullscreenModal } = await import("../render-utils/FullscreenModal");
54
+ const { showModal } = await import("../render-utils/modal");
55
+ const { observable } = await import("mobx");
56
+ const preact = await import("preact");
57
+
58
+ return new Promise<string>((resolve) => {
59
+ const state = observable({
60
+ value: "",
61
+ });
62
+
63
+ const { close } = showModal({
64
+ contents: preact.createElement(FullscreenModal, {
65
+ onCancel: () => {
66
+ // Don't allow cancel without a value
67
+ },
68
+ },
69
+ preact.createElement("div", { style: { display: "flex", flexDirection: "column", gap: 10 } },
70
+ preact.createElement("div", { style: { fontWeight: "bold" } }, `Enter secret for: ${key}`),
71
+ preact.createElement("input", {
72
+ type: "password",
73
+ style: { padding: 10, fontSize: 16 },
74
+ onInput: (e: Event) => {
75
+ state.value = (e.target as HTMLInputElement).value;
76
+ },
77
+ onKeyDown: (e: KeyboardEvent) => {
78
+ if (e.code === "Enter" && state.value) {
79
+ localStorage.setItem(storageKey, state.value);
80
+ close();
81
+ resolve(state.value);
82
+ }
83
+ },
84
+ }),
85
+ preact.createElement("button", {
86
+ style: { padding: 10, fontSize: 16, cursor: "pointer" },
87
+ onClick: () => {
88
+ if (state.value) {
89
+ localStorage.setItem(storageKey, state.value);
90
+ close();
91
+ resolve(state.value);
92
+ }
93
+ },
94
+ }, "Save"),
95
+ ),
96
+ ),
97
+ });
98
+ });
99
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sliftutils",
3
- "version": "0.13.0",
3
+ "version": "0.15.0",
4
4
  "main": "index.js",
5
5
  "license": "MIT",
6
6
  "files": [
@@ -1,6 +1,5 @@
1
1
  import preact from "preact";
2
2
  import { InputProps } from "./Input";
3
- import { URLParamStr } from "./URLParam";
4
3
  export type InputLabelProps = Omit<InputProps, "label" | "title"> & {
5
4
  label?: preact.ComponentChild;
6
5
  number?: boolean;
@@ -29,7 +28,9 @@ export declare class InputLabel extends preact.Component<InputLabelProps> {
29
28
  render(): preact.JSX.Element;
30
29
  }
31
30
  export declare class InputLabelURL extends preact.Component<InputLabelProps & {
32
- persisted: URLParamStr;
31
+ persisted: {
32
+ value: unknown;
33
+ };
33
34
  }> {
34
35
  render(): preact.JSX.Element;
35
36
  }
@@ -1,7 +1,6 @@
1
1
  import preact from "preact";
2
2
  import { Input, InputProps } from "./Input";
3
3
  import { css } from "typesafecss";
4
- import { URLParamStr } from "./URLParam";
5
4
  import { lazy } from "socket-function/src/caching";
6
5
  import { observer } from "./observer";
7
6
  import { observable } from "mobx";
@@ -83,29 +82,21 @@ export class InputLabel extends preact.Component<InputLabelProps> {
83
82
 
84
83
  if ((!props.type || props.type === "number") && props.useDateUI) {
85
84
  let value = String(props.value);
86
- if (
87
- value === "date-1be2200d-9742-4e90-a6c2-e8a790277414"
88
- || isJSNumber(value) && startGuessDateRange < +value && +value < endGuessDateRange
89
- // NOTE: Showing the date selector is a problem, as there is not an easy way to undo this. So if the
90
- // user typed in "2020", it would return the input into a selector and not let them keeping type.
91
- // || startGuessDateRange < +new Date(String(value)) && +new Date(String(value)) < endGuessDateRange
92
- ) {
93
- props.type = "datetime-local";
94
- props.edit = false;
95
- props.textarea = false;
96
- props.number = false;
97
- props.forceInputValueUpdatesWhenFocused = true;
98
- // NOTE: When using forceInputValueUpdatesWhenFocused we need hot, otherwise the user's updates
99
- // won't be visible.
100
- props.hot = true;
101
- if (isJSNumber(value)) {
102
- value = formatDateTimeForInput(+value);
103
- } else {
104
- value = "";
105
- }
106
- props.value = value;
107
- addValueMapping(value => (+new Date(value).getTime() || "") + "");
85
+ props.type = "datetime-local";
86
+ props.edit = false;
87
+ props.textarea = false;
88
+ props.number = false;
89
+ props.forceInputValueUpdatesWhenFocused = true;
90
+ // NOTE: When using forceInputValueUpdatesWhenFocused we need hot, otherwise the user's updates
91
+ // won't be visible.
92
+ props.hot = true;
93
+ if (isJSNumber(value)) {
94
+ value = formatDateTimeForInput(+value);
95
+ } else {
96
+ value = "";
108
97
  }
98
+ props.value = value;
99
+ addValueMapping(value => (+new Date(value).getTime() || "") + "");
109
100
  }
110
101
  if (props.fontSize !== undefined) {
111
102
  props.style = { ...props.style as any, fontSize: props.fontSize };
@@ -260,7 +251,7 @@ const pencilSVG = lazy(() => {
260
251
 
261
252
  @observer
262
253
  export class InputLabelURL extends preact.Component<InputLabelProps & {
263
- persisted: URLParamStr;
254
+ persisted: { value: unknown };
264
255
  }> {
265
256
  render() {
266
257
  this.props.persisted.value;