@davidsouther/jiffies 1.0.0-beta.1 → 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/build/components/button_bar.js +24 -13
- package/build/components/select.d.ts +1 -4
- package/build/display.js +9 -1
- package/build/dom/dom.js +1 -0
- package/build/dom/form/form.d.ts +9 -8
- package/build/dom/form/form.js +14 -5
- package/build/dom/provide.d.ts +3 -0
- package/build/dom/provide.js +7 -0
- package/build/equal.d.ts +5 -4
- package/build/equal.js +19 -4
- package/build/fs.d.ts +48 -0
- package/build/fs.js +144 -0
- package/build/{components/index.d.ts → fs.test.d.ts} +0 -0
- package/build/fs.test.js +43 -0
- package/build/log.js +16 -4
- package/build/result.d.ts +11 -11
- package/build/result.js +2 -2
- package/build/scope/execute.js +1 -1
- package/build/scope/expect.d.ts +1 -1
- package/build/scope/expect.js +4 -3
- package/build/server/http/css.d.ts +5 -0
- package/build/server/http/css.js +47 -0
- package/build/server/http/index.js +4 -2
- package/build/server/http/response.js +6 -3
- package/build/test_all.d.ts +7 -1
- package/build/test_all.js +7 -8
- package/package.json +5 -2
- package/src/components/button_bar.ts +32 -26
- package/src/display.ts +8 -2
- package/src/dom/dom.ts +1 -0
- package/src/dom/form/form.ts +30 -7
- package/src/dom/provide.ts +11 -0
- package/src/equal.ts +22 -11
- package/src/fs.test.ts +53 -0
- package/src/fs.ts +180 -0
- package/src/index.html +4 -4
- package/src/log.ts +8 -4
- package/src/pico/_variables.scss +66 -0
- package/src/pico/components/_accordion.scss +112 -0
- package/src/pico/components/_button-group.scss +51 -0
- package/src/pico/components/_card.scss +47 -0
- package/src/pico/components/_dropdown.scss +203 -0
- package/src/pico/components/_modal.scss +181 -0
- package/src/pico/components/_nav.scss +79 -0
- package/src/pico/components/_progress.scss +70 -0
- package/src/pico/components/_property.scss +34 -0
- package/src/pico/content/_button.scss +152 -0
- package/src/pico/content/_code.scss +63 -0
- package/src/pico/content/_embedded.scss +0 -0
- package/src/pico/content/_form-alt.scss +276 -0
- package/src/pico/content/_form.scss +259 -0
- package/src/pico/content/_misc.scss +0 -0
- package/src/pico/content/_table.scss +28 -0
- package/src/pico/content/_toggle.scss +132 -0
- package/src/pico/content/_typography.scss +232 -0
- package/src/pico/layout/_container.scss +40 -0
- package/src/pico/layout/_document.scss +0 -0
- package/src/pico/layout/_flex.scss +46 -0
- package/src/pico/layout/_grid.scss +24 -0
- package/src/pico/layout/_scroller.scss +16 -0
- package/src/pico/layout/_section.scss +8 -0
- package/src/pico/layout/_sectioning.scss +53 -0
- package/src/pico/pico.scss +60 -0
- package/src/pico/reset/_accessibility.scss +34 -0
- package/src/pico/reset/_button.scss +17 -0
- package/src/pico/reset/_code.scss +15 -0
- package/src/pico/reset/_document.scss +48 -0
- package/src/pico/reset/_embedded.scss +39 -0
- package/src/pico/reset/_form.scss +97 -0
- package/src/pico/reset/_misc.scss +23 -0
- package/src/pico/reset/_nav.scss +5 -0
- package/src/pico/reset/_progress.scss +4 -0
- package/src/pico/reset/_table.scss +8 -0
- package/src/pico/reset/_typography.scss +25 -0
- package/src/pico/themes/default/_colors.scss +65 -0
- package/src/pico/themes/default/_dark.scss +148 -0
- package/src/pico/themes/default/_light.scss +149 -0
- package/src/pico/themes/default/_styles.scss +272 -0
- package/src/pico/themes/default.scss +34 -0
- package/src/pico/utilities/_accessibility.scss +3 -0
- package/src/pico/utilities/_loading.scss +52 -0
- package/src/pico/utilities/_reduce-motion.scss +27 -0
- package/src/pico/utilities/_tooltip.scss +101 -0
- package/src/result.ts +16 -20
- package/src/scope/execute.ts +1 -1
- package/src/scope/expect.ts +10 -9
- package/src/server/http/css.ts +63 -0
- package/src/server/http/index.ts +4 -2
- package/src/server/http/response.ts +7 -4
- package/src/test_all.ts +7 -8
- package/src/zip/spec.txt +3260 -0
- package/build/components/index.js +0 -1
- package/build/index.d.ts +0 -13
- package/build/index.js +0 -13
- package/build/parcel_resolver.d.ts +0 -3
- package/build/parcel_resolver.js +0 -19
|
@@ -14,7 +14,9 @@ const MIME_TYPES = {
|
|
|
14
14
|
woff2: "application/font-woff2",
|
|
15
15
|
};
|
|
16
16
|
const mime = (basename) => {
|
|
17
|
-
const extension = basename
|
|
17
|
+
const extension = basename
|
|
18
|
+
.substring(basename.lastIndexOf(".") + 1)
|
|
19
|
+
.toLowerCase();
|
|
18
20
|
return MIME_TYPES[extension] ?? "application/octet-stream";
|
|
19
21
|
};
|
|
20
22
|
export const fileResponse = (filename, stat, status = 200) => async () => {
|
|
@@ -26,11 +28,12 @@ export const fileResponse = (filename, stat, status = 200) => async () => {
|
|
|
26
28
|
const contentLength = stat.size;
|
|
27
29
|
return { status, contentType, contentLength, content };
|
|
28
30
|
};
|
|
31
|
+
const CHARSET = "utf-8";
|
|
29
32
|
export const contentResponse = (content, contentType, status = 200) => async () => {
|
|
30
|
-
const contentBuffer = Buffer.from(content,
|
|
33
|
+
const contentBuffer = Buffer.from(content, CHARSET);
|
|
31
34
|
return {
|
|
32
35
|
content: contentBuffer,
|
|
33
|
-
contentType,
|
|
36
|
+
contentType: contentType.split(";")[0] + "; charset=" + CHARSET,
|
|
34
37
|
status,
|
|
35
38
|
contentLength: contentBuffer.length,
|
|
36
39
|
};
|
package/build/test_all.d.ts
CHANGED
package/build/test_all.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// This file must be .js for imports to run. Unused imports in .ts files are
|
|
2
2
|
// discarded during transpilation.
|
|
3
3
|
import { describe, expect, it } from "./scope/index.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
]);
|
|
4
|
+
import "./context.test.js";
|
|
5
|
+
import "./equal.test.js";
|
|
6
|
+
import "./flags.test.js";
|
|
7
|
+
import "./fs.test.js";
|
|
8
|
+
import "./generator.test.js";
|
|
9
|
+
import "./lock.test.js";
|
|
10
|
+
import "./result.test.js";
|
|
12
11
|
describe("Test executor", () => {
|
|
13
12
|
it("matches equality", () => {
|
|
14
13
|
expect(1).toBe(1);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@davidsouther/jiffies",
|
|
3
|
-
"version": "1.0.0
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"displayName": "JEFRi Jiffies",
|
|
6
6
|
"type": "module",
|
|
@@ -31,6 +31,9 @@
|
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^17.0.24",
|
|
34
|
-
"typescript": "
|
|
34
|
+
"typescript": "4.7.1-rc"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"sass": "^1.49.9"
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { display } from "../display.js";
|
|
2
2
|
import { FC } from "../dom/fc.js";
|
|
3
|
-
import {
|
|
3
|
+
import { fieldset, input, label } from "../dom/html.js";
|
|
4
|
+
|
|
5
|
+
let buttonBarId = 1;
|
|
6
|
+
let nextId = () => buttonBarId++;
|
|
4
7
|
|
|
5
8
|
const ButtonBar = FC<{
|
|
6
9
|
// T extends Display
|
|
@@ -10,29 +13,32 @@ const ButtonBar = FC<{
|
|
|
10
13
|
values: T[];
|
|
11
14
|
// @ts-ignore TODO(TFC)
|
|
12
15
|
events: { onSelect: (current: T) => void };
|
|
13
|
-
}>("button-bar", (el, { value, values, events }) =>
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
16
|
+
}>("button-bar", (el, { value, values, events }) => {
|
|
17
|
+
const name = `button-bar-${nextId()}`;
|
|
18
|
+
return fieldset(
|
|
19
|
+
{ class: "input-group" },
|
|
20
|
+
...values
|
|
21
|
+
.map((option) => {
|
|
22
|
+
const opt = `${option}`.replace(/\s+/g, "_").toLowerCase();
|
|
23
|
+
const id = `${name}-${opt}`;
|
|
24
|
+
return [
|
|
25
|
+
label(
|
|
26
|
+
{ role: "button", htmlFor: id },
|
|
27
|
+
input({
|
|
28
|
+
type: "radio",
|
|
29
|
+
id,
|
|
30
|
+
name,
|
|
31
|
+
value: option,
|
|
32
|
+
checked: option === value,
|
|
33
|
+
events: {
|
|
34
|
+
change: () => events.onSelect(option),
|
|
28
35
|
},
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
)
|
|
36
|
-
);
|
|
37
|
-
|
|
36
|
+
}),
|
|
37
|
+
display(option)
|
|
38
|
+
),
|
|
39
|
+
];
|
|
40
|
+
})
|
|
41
|
+
.flat()
|
|
42
|
+
);
|
|
43
|
+
});
|
|
38
44
|
export default ButtonBar;
|
package/src/display.ts
CHANGED
|
@@ -8,5 +8,11 @@ export const isDisplay = (/** @type unknown */ a: unknown): a is Display =>
|
|
|
8
8
|
typeof (a as Display).toString === "function" ||
|
|
9
9
|
typeof (a as Display) === "string";
|
|
10
10
|
|
|
11
|
-
export const display = (a: unknown | Display): string =>
|
|
12
|
-
isDisplay(a)
|
|
11
|
+
export const display = (a: unknown | Display): string => {
|
|
12
|
+
if (isDisplay(a)) {
|
|
13
|
+
const str = a.toString();
|
|
14
|
+
if (str === "[object Object]") return JSON.stringify(a);
|
|
15
|
+
return str;
|
|
16
|
+
}
|
|
17
|
+
return JSON.stringify(a);
|
|
18
|
+
};
|
package/src/dom/dom.ts
CHANGED
package/src/dom/form/form.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { form, input, label, option, select } from "../html.js";
|
|
|
3
3
|
import {
|
|
4
4
|
FormAttributes,
|
|
5
5
|
InputAttributes,
|
|
6
|
+
LabelAttributes,
|
|
6
7
|
OptionAttributes,
|
|
7
8
|
SelectAttributes,
|
|
8
9
|
} from "../types/html.js";
|
|
@@ -19,8 +20,18 @@ export const Form = (attrs: FormAttributes, ...children: DenormChildren[]) => {
|
|
|
19
20
|
};
|
|
20
21
|
export const Input = (attrs: InputAttributes, ...children: DenormChildren[]) =>
|
|
21
22
|
label(input(attrs as Attrs<HTMLInputElement>), ...children);
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
|
|
24
|
+
export const Select = (
|
|
25
|
+
attrs: { options: string[] | {}; selected?: string } & SelectAttributes &
|
|
26
|
+
LabelAttributes
|
|
27
|
+
) =>
|
|
28
|
+
label(
|
|
29
|
+
{ style: attrs.style ?? {} },
|
|
30
|
+
select(
|
|
31
|
+
{ events: attrs.events ?? {} },
|
|
32
|
+
...prepareOptions(attrs.options, attrs.selected).map(Option)
|
|
33
|
+
)
|
|
34
|
+
);
|
|
24
35
|
export const Button = () => {};
|
|
25
36
|
|
|
26
37
|
const prepareOptions = (
|
|
@@ -29,19 +40,31 @@ const prepareOptions = (
|
|
|
29
40
|
| Record<
|
|
30
41
|
string,
|
|
31
42
|
string | { label: string; disabled?: boolean; selected?: boolean }
|
|
32
|
-
|
|
43
|
+
>,
|
|
44
|
+
selected?: string
|
|
33
45
|
): Parameters<typeof Option>[0][] =>
|
|
34
46
|
Array.isArray(attrs)
|
|
35
|
-
? attrs.map((value) => ({
|
|
47
|
+
? attrs.map((value) => ({
|
|
48
|
+
value,
|
|
49
|
+
label: value,
|
|
50
|
+
selected: selected == value,
|
|
51
|
+
}))
|
|
36
52
|
: Object.entries(attrs).map(([value, label]) =>
|
|
37
|
-
typeof label === "string"
|
|
53
|
+
typeof label === "string"
|
|
54
|
+
? { value, label, selected: selected === value }
|
|
55
|
+
: { value, ...label }
|
|
38
56
|
);
|
|
39
57
|
export const Option = (attrs: OptionAttributes) =>
|
|
40
58
|
option(attrs as Attrs<HTMLOptionElement>);
|
|
41
59
|
|
|
42
60
|
export const Dropdown = (
|
|
43
|
-
attrs: SelectAttributes | {
|
|
44
|
-
|
|
61
|
+
attrs: SelectAttributes | { selected?: string },
|
|
62
|
+
...options: Parameters<typeof prepareOptions>[0][]
|
|
63
|
+
) =>
|
|
64
|
+
Select({
|
|
65
|
+
...attrs,
|
|
66
|
+
options: typeof options[0] == "string" ? options : options[0],
|
|
67
|
+
});
|
|
45
68
|
export const Radios = () => {};
|
|
46
69
|
export const Checks = () => {};
|
|
47
70
|
export const Switches = () => {};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Option } from "../result.js";
|
|
2
|
+
|
|
3
|
+
let registry: Record<string, unknown> = {};
|
|
4
|
+
|
|
5
|
+
export function provide(items: Record<string, unknown>) {
|
|
6
|
+
registry = { ...registry, ...items };
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function retrieve<T>(key: string): Option<T> {
|
|
10
|
+
return registry[key] as T;
|
|
11
|
+
}
|
package/src/equal.ts
CHANGED
|
@@ -1,31 +1,42 @@
|
|
|
1
1
|
export function compareArrays<T>(
|
|
2
|
-
equal: (a: T, b: T) => boolean
|
|
3
|
-
): (A: T[], B: T[]) => boolean {
|
|
4
|
-
return (a: T[], b: T[]): boolean =>
|
|
5
|
-
a.length === b.length && a.every((e, i) => equal(e, b[i]));
|
|
2
|
+
equal: (a: T, b: T, partial: boolean) => boolean
|
|
3
|
+
): (A: T[], B: T[], partial?: boolean) => boolean {
|
|
4
|
+
return (a: T[], b: T[], partial = false): boolean =>
|
|
5
|
+
a.length === b.length && a.every((e, i) => equal(e, b[i], partial));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
export const equalArrays = compareArrays(Object.is);
|
|
9
9
|
|
|
10
|
-
export const matchArrays: <A>(a: A[], b: A[]) => boolean =
|
|
10
|
+
export const matchArrays: <A>(a: A[], b: A[], partial?: boolean) => boolean =
|
|
11
11
|
compareArrays(equals);
|
|
12
12
|
|
|
13
13
|
function asArray(a: Record<string, unknown>): [string, unknown][] {
|
|
14
14
|
return Object.entries(a).sort((a, b) => a[0].localeCompare(b[0]));
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
export
|
|
17
|
+
export const matchObjects = (a: {}, b: {}, partial = true) => {
|
|
18
|
+
for (const [k, v] of Object.entries(a)) {
|
|
19
|
+
if (!b.hasOwnProperty(k) && partial) continue;
|
|
20
|
+
// @ts-ignore
|
|
21
|
+
if (!equals(v, b[k], partial)) return false;
|
|
22
|
+
}
|
|
23
|
+
return true;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export function equals<A>(a: A | A[], b: A | A[], partial = false): boolean {
|
|
18
27
|
// runtime type checking
|
|
19
28
|
switch (typeof a) {
|
|
20
29
|
case "object":
|
|
30
|
+
if (b === undefined) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
21
33
|
if (a instanceof Array && b instanceof Array) {
|
|
22
|
-
return matchArrays(a, b);
|
|
34
|
+
return matchArrays(a, b, partial);
|
|
23
35
|
} else {
|
|
24
|
-
return
|
|
25
|
-
asArray(a as Record<string, unknown>),
|
|
26
|
-
asArray(b as Record<string, unknown>)
|
|
27
|
-
);
|
|
36
|
+
return matchObjects(a, b, partial);
|
|
28
37
|
}
|
|
38
|
+
case "function":
|
|
39
|
+
return a.name == (b as unknown as Function).name;
|
|
29
40
|
default:
|
|
30
41
|
return Object.is(a, b);
|
|
31
42
|
}
|
package/src/fs.test.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { FileSystem, ObjectFileSystemAdapter } from "./fs.js";
|
|
2
|
+
import { describe, it, expect } from "./scope/index.js";
|
|
3
|
+
|
|
4
|
+
describe("FileSystem", () => {
|
|
5
|
+
describe("Writing", () => {
|
|
6
|
+
it("Writes files", async () => {
|
|
7
|
+
const fsObj = {};
|
|
8
|
+
const fs = new FileSystem(new ObjectFileSystemAdapter(fsObj));
|
|
9
|
+
await fs.writeFile("hello", "world");
|
|
10
|
+
|
|
11
|
+
expect(fsObj).toEqual({ "/hello": "world" });
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it("Writes deep files", async () => {
|
|
15
|
+
const fsObj = {};
|
|
16
|
+
const fs = new FileSystem(new ObjectFileSystemAdapter(fsObj));
|
|
17
|
+
await fs.writeFile("deep/hello", "world");
|
|
18
|
+
|
|
19
|
+
expect(fsObj).toEqual({ "/deep/hello": "world" });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it("Writes deep files from root", async () => {
|
|
23
|
+
const fsObj = {};
|
|
24
|
+
const fs = new FileSystem(new ObjectFileSystemAdapter(fsObj));
|
|
25
|
+
await fs.writeFile("/root/deep/hello", "world");
|
|
26
|
+
|
|
27
|
+
expect(fsObj).toEqual({ "/root/deep/hello": "world" });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("Writes files after cd", async () => {
|
|
31
|
+
const fsObj = {};
|
|
32
|
+
const fs = new FileSystem(new ObjectFileSystemAdapter(fsObj));
|
|
33
|
+
await fs.cd("deep");
|
|
34
|
+
await fs.writeFile("hello", "world");
|
|
35
|
+
|
|
36
|
+
expect(fsObj).toEqual({ "/deep/hello": "world" });
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
describe("directory", () => {
|
|
41
|
+
it("returns directory listing", async () => {
|
|
42
|
+
const fsObj = {
|
|
43
|
+
"/deep/hello": "world",
|
|
44
|
+
"/deep/bonjour": "monde",
|
|
45
|
+
"/other/file": "text",
|
|
46
|
+
};
|
|
47
|
+
const fs = new FileSystem(new ObjectFileSystemAdapter(fsObj));
|
|
48
|
+
|
|
49
|
+
const dir = await fs.readdir("deep");
|
|
50
|
+
expect(dir.sort()).toEqual(["bonjour", "hello"]);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
package/src/fs.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
// Treat localstorage as a file system
|
|
2
|
+
export type PathLike = string;
|
|
3
|
+
export interface Stats {
|
|
4
|
+
isDirectory(): boolean;
|
|
5
|
+
isFile(): boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function join(...paths: string[]): string {
|
|
9
|
+
const pathParts: string[] = [];
|
|
10
|
+
for (const path of paths) {
|
|
11
|
+
for (const part of path.split("/")) {
|
|
12
|
+
switch (part) {
|
|
13
|
+
case "":
|
|
14
|
+
case ".":
|
|
15
|
+
break;
|
|
16
|
+
case "..":
|
|
17
|
+
pathParts.pop();
|
|
18
|
+
break;
|
|
19
|
+
default:
|
|
20
|
+
pathParts.push(part);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return "/" + pathParts.join("/");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface FileSystemAdapter {
|
|
28
|
+
stat(path: PathLike): Promise<Stats>;
|
|
29
|
+
readdir(path: PathLike): Promise<string[]>;
|
|
30
|
+
copyFile(from: PathLike, to: PathLike): Promise<void>;
|
|
31
|
+
readFile(path: PathLike): Promise<string>;
|
|
32
|
+
writeFile(path: PathLike, contents: string): Promise<void>;
|
|
33
|
+
rm(path: PathLike): Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export class FileSystem implements FileSystemAdapter {
|
|
37
|
+
protected wd = "/";
|
|
38
|
+
protected stack: string[] = [];
|
|
39
|
+
|
|
40
|
+
constructor(protected adapter = new ObjectFileSystemAdapter()) {}
|
|
41
|
+
|
|
42
|
+
cwd(): string {
|
|
43
|
+
return this.wd;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
cd(dir: string) {
|
|
47
|
+
this.wd = this.p(dir);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
pushd(dir: string) {
|
|
51
|
+
this.stack.push(this.wd);
|
|
52
|
+
this.cd(dir);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
popd() {
|
|
56
|
+
if (this.stack.length > 0) {
|
|
57
|
+
this.wd = this.stack.pop()!;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
stat(path: PathLike): Promise<Stats> {
|
|
62
|
+
return this.adapter.stat(join(this.cwd(), path));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
readdir(path: PathLike): Promise<string[]> {
|
|
66
|
+
return this.adapter.readdir(this.p(path) + "/");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
copyFile(from: PathLike, to: PathLike): Promise<void> {
|
|
70
|
+
return this.adapter.copyFile(this.p(from), this.p(to));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
readFile(path: PathLike): Promise<string> {
|
|
74
|
+
return this.adapter.readFile(this.p(path));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
writeFile(path: PathLike, contents: string): Promise<void> {
|
|
78
|
+
return this.adapter.writeFile(this.p(path), contents);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
rm(path: PathLike): Promise<void> {
|
|
82
|
+
return this.adapter.rm(this.p(path));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private p(path: PathLike): string {
|
|
86
|
+
return path[0] == "/" ? path : join(this.cwd(), path);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export class ObjectFileSystemAdapter implements FileSystemAdapter {
|
|
91
|
+
constructor(private fs: Record<string, string> = {}) {}
|
|
92
|
+
|
|
93
|
+
stat(path: PathLike): Promise<Stats> {
|
|
94
|
+
return new Promise((resolve, reject) => {
|
|
95
|
+
if (this.fs[path] != null) {
|
|
96
|
+
resolve({
|
|
97
|
+
isDirectory() {
|
|
98
|
+
return false;
|
|
99
|
+
},
|
|
100
|
+
isFile() {
|
|
101
|
+
return true;
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
} else {
|
|
105
|
+
reject();
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
readdir(path: PathLike): Promise<string[]> {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
let dir: string[] = [];
|
|
113
|
+
for (const filename of Object.keys(this.fs)) {
|
|
114
|
+
if (filename.startsWith(path)) {
|
|
115
|
+
const end = filename.indexOf("/", path.length + 1);
|
|
116
|
+
const basename = filename.substring(
|
|
117
|
+
path.length,
|
|
118
|
+
end == -1 ? undefined : end
|
|
119
|
+
);
|
|
120
|
+
dir.push(basename);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return resolve(dir);
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
copyFile(from: PathLike, to: PathLike): Promise<void> {
|
|
128
|
+
return new Promise((resolve) => {
|
|
129
|
+
this.fs[to] = this.fs[from];
|
|
130
|
+
resolve();
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
readFile(path: PathLike): Promise<string> {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
let file = this.fs[path];
|
|
137
|
+
if (file === undefined) {
|
|
138
|
+
reject();
|
|
139
|
+
} else {
|
|
140
|
+
resolve(file);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
writeFile(path: PathLike, contents: string): Promise<void> {
|
|
146
|
+
return new Promise((resolve) => {
|
|
147
|
+
this.fs[path] = contents;
|
|
148
|
+
resolve();
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
rm(path: PathLike): Promise<void> {
|
|
153
|
+
return new Promise((resolve) => {
|
|
154
|
+
delete this.fs[path];
|
|
155
|
+
resolve();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export class LocalStorageFileSystemAdapter extends ObjectFileSystemAdapter {
|
|
161
|
+
constructor() {
|
|
162
|
+
super(window.localStorage);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export interface Tree {
|
|
167
|
+
[k: string]: string | Tree;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function reset(fs: FileSystem, tree: Tree): Promise<void> {
|
|
171
|
+
for (const [path, file] of Object.entries(tree)) {
|
|
172
|
+
if (typeof file == "string") {
|
|
173
|
+
await fs.writeFile(path, file);
|
|
174
|
+
} else {
|
|
175
|
+
fs.cd(path);
|
|
176
|
+
await reset(fs, file);
|
|
177
|
+
fs.cd("..");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
package/src/index.html
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
<head>
|
|
4
4
|
<title>Jiffies Tests</title>
|
|
5
5
|
<base href="/" />
|
|
6
|
-
<link rel="stylesheet" href="/pico/pico
|
|
6
|
+
<link rel="stylesheet" href="/pico/pico.css?$enable-viewport=none" />
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
|
9
|
-
<header
|
|
9
|
+
<header></header>
|
|
10
10
|
|
|
11
|
-
<main
|
|
11
|
+
<main>
|
|
12
12
|
<article id="test_output"></article>
|
|
13
13
|
</main>
|
|
14
14
|
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
}));
|
|
68
68
|
|
|
69
69
|
document
|
|
70
|
-
.
|
|
70
|
+
.querySelector("body > header")
|
|
71
71
|
.appendChild(
|
|
72
72
|
nav(
|
|
73
73
|
ul(li(strong("Jiffy Apps"))),
|
package/src/log.ts
CHANGED
|
@@ -45,17 +45,21 @@ export function getLogger(name: string): Logger {
|
|
|
45
45
|
export const DEFAULT_LOGGER = getLogger("default");
|
|
46
46
|
|
|
47
47
|
export function debug(message: Display, data?: {}) {
|
|
48
|
-
DEFAULT_LOGGER.debug(message, data);
|
|
48
|
+
if (data) DEFAULT_LOGGER.debug(message, data);
|
|
49
|
+
else DEFAULT_LOGGER.debug(message);
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
export function info(message: Display, data?: {}) {
|
|
52
|
-
DEFAULT_LOGGER.info(message, data);
|
|
53
|
+
if (data) DEFAULT_LOGGER.info(message, data);
|
|
54
|
+
else DEFAULT_LOGGER.info(message);
|
|
53
55
|
}
|
|
54
56
|
|
|
55
57
|
export function warn(message: Display, data?: {}) {
|
|
56
|
-
DEFAULT_LOGGER.warn(message, data);
|
|
58
|
+
if (data) DEFAULT_LOGGER.warn(message, data);
|
|
59
|
+
else DEFAULT_LOGGER.warn(message);
|
|
57
60
|
}
|
|
58
61
|
|
|
59
62
|
export function error(message: Display, data?: {}) {
|
|
60
|
-
DEFAULT_LOGGER.error(message, data);
|
|
63
|
+
if (data) DEFAULT_LOGGER.error(message, data);
|
|
64
|
+
else DEFAULT_LOGGER.error(message);
|
|
61
65
|
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Config
|
|
2
|
+
// ––––––––––––––––––––
|
|
3
|
+
|
|
4
|
+
// Enable <header>, <main>, <footer> inside <body> as a container
|
|
5
|
+
$enable-semantic-container: true !default;
|
|
6
|
+
|
|
7
|
+
// Enable .container and .container-fluid
|
|
8
|
+
$enable-class-container: false !default;
|
|
9
|
+
|
|
10
|
+
// Enable a centered viewport for <header>, <main>, <footer> inside <body>
|
|
11
|
+
// Options are "all" to center all, "main" to only center main, and "none" to treat all as fluid.
|
|
12
|
+
$enable-viewport: "main" !default;
|
|
13
|
+
|
|
14
|
+
// Enable responsive spacings for <header>, <main>, <footer>, <section>, <article>
|
|
15
|
+
// Fixed spacings if disabled
|
|
16
|
+
$enable-responsive-spacings: true !default;
|
|
17
|
+
|
|
18
|
+
// Enable responsive typography
|
|
19
|
+
// Fixed root element size if disabled
|
|
20
|
+
$enable-responsive-typography: true !default;
|
|
21
|
+
|
|
22
|
+
// Enable .classes
|
|
23
|
+
// .classless version if disabled
|
|
24
|
+
$enable-classes: true !default;
|
|
25
|
+
|
|
26
|
+
// Enable .grid class
|
|
27
|
+
$enable-grid: true !default;
|
|
28
|
+
|
|
29
|
+
// Enable transitions
|
|
30
|
+
$enable-transitions: true !default;
|
|
31
|
+
|
|
32
|
+
// Enable overriding with !important
|
|
33
|
+
$enable-important: true !default;
|
|
34
|
+
|
|
35
|
+
// Responsive
|
|
36
|
+
// ––––––––––––––––––––
|
|
37
|
+
|
|
38
|
+
// xs: Extra small (portrait phones)
|
|
39
|
+
// sm: Small (landscape phones)
|
|
40
|
+
// md: Medium (tablets)
|
|
41
|
+
// lg: Large (desktops)
|
|
42
|
+
// xl: Extra large (large desktops, TVs)
|
|
43
|
+
|
|
44
|
+
// NOTE:
|
|
45
|
+
// To provide an easy and fine styling on each breakpoint
|
|
46
|
+
// we didn't use @each, @mixin or @include.
|
|
47
|
+
// That means you need to edit each CSS selector file to add a breakpoint
|
|
48
|
+
|
|
49
|
+
// Breakpoints
|
|
50
|
+
// 'null' disable the breakpoint
|
|
51
|
+
$breakpoints: (
|
|
52
|
+
xs: 0,
|
|
53
|
+
sm: 576px,
|
|
54
|
+
md: 768px,
|
|
55
|
+
lg: 992px,
|
|
56
|
+
xl: 1200px,
|
|
57
|
+
) !default;
|
|
58
|
+
|
|
59
|
+
// Viewports
|
|
60
|
+
$viewports: (
|
|
61
|
+
// 'null' disable the viewport on a breakpoint
|
|
62
|
+
sm: 510px,
|
|
63
|
+
md: 700px,
|
|
64
|
+
lg: 920px,
|
|
65
|
+
xl: 1130px
|
|
66
|
+
) !default;
|