fubi 1.0.0-beta.1 → 1.0.0-beta.11

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.
Files changed (99) hide show
  1. package/dist/index.es.js +27035 -0
  2. package/dist/index.iife.js +5 -0
  3. package/package.json +33 -33
  4. package/.prettierignore +0 -9
  5. package/.prettierrc +0 -23
  6. package/.vscode/extensions.json +0 -3
  7. package/config.toml +0 -3
  8. package/demo/index.html +0 -64
  9. package/index.html +0 -19
  10. package/publish.js +0 -77
  11. package/src/components/App.svelte +0 -17
  12. package/src/components/Debug.svelte +0 -39
  13. package/src/components/Layout.svelte +0 -47
  14. package/src/components/comment/CommentCard.svelte +0 -49
  15. package/src/components/comment/CommentFocusElement.svelte +0 -14
  16. package/src/components/comment/CommentMenu.svelte +0 -18
  17. package/src/components/comment/CommentPriority.svelte +0 -31
  18. package/src/components/comment/CommentReplyButton.svelte +0 -35
  19. package/src/components/comment/CommentUser.svelte +0 -22
  20. package/src/components/icons/AlertIcon.svelte +0 -7
  21. package/src/components/icons/CancelIcon.svelte +0 -8
  22. package/src/components/icons/CheckCircle.svelte +0 -7
  23. package/src/components/icons/CheckIcon.svelte +0 -7
  24. package/src/components/icons/CheckSmallIcon.svelte +0 -7
  25. package/src/components/icons/ChevronLeftIcon.svelte +0 -7
  26. package/src/components/icons/ChevronRightIcon.svelte +0 -7
  27. package/src/components/icons/ChevronsUpIcon.svelte +0 -8
  28. package/src/components/icons/DotsIcon.svelte +0 -9
  29. package/src/components/icons/PlusIcon.svelte +0 -8
  30. package/src/components/index.ts +0 -0
  31. package/src/components/toolbar/ButtonToolbar.svelte +0 -29
  32. package/src/components/toolbar/Menu.svelte +0 -42
  33. package/src/components/toolbar/Toolbar.svelte +0 -55
  34. package/src/components/ui/Button.svelte +0 -33
  35. package/src/components/ui/ButtonIcon.svelte +0 -31
  36. package/src/components/ui/ButtonSmall.svelte +0 -12
  37. package/src/components/ui/ButtonTabbar.svelte +0 -35
  38. package/src/components/ui/Chip.svelte +0 -28
  39. package/src/components/ui/Icon.svelte +0 -38
  40. package/src/components/ui/Input.svelte +0 -9
  41. package/src/components/ui/List.svelte +0 -18
  42. package/src/components/ui/ListItem.svelte +0 -87
  43. package/src/components/ui/ListLoader.svelte +0 -20
  44. package/src/components/ui/Loader.svelte +0 -39
  45. package/src/components/ui/Logo.svelte +0 -20
  46. package/src/components/ui/Msg.svelte +0 -21
  47. package/src/components/ui/Navbar.svelte +0 -33
  48. package/src/components/ui/Page.svelte +0 -67
  49. package/src/components/ui/Tabbar.svelte +0 -60
  50. package/src/components/ui/Toggle.svelte +0 -8
  51. package/src/components/ui/Window.svelte +0 -61
  52. package/src/index.ts +0 -28
  53. package/src/lib/actions.ts +0 -58
  54. package/src/lib/haptics.ts +0 -94
  55. package/src/lib/logger.ts +0 -22
  56. package/src/lib/routes.ts +0 -37
  57. package/src/lib/utils.ts +0 -27
  58. package/src/modules/app.svelte.ts +0 -66
  59. package/src/modules/auth.svelte.ts +0 -80
  60. package/src/modules/collection.svelte.ts +0 -79
  61. package/src/modules/comments.svelte.ts +0 -34
  62. package/src/modules/dom.svelte.ts +0 -26
  63. package/src/modules/domains.svelte.ts +0 -91
  64. package/src/modules/environment.svelte.ts +0 -10
  65. package/src/modules/events.svelte.ts +0 -23
  66. package/src/modules/folders.svelte.ts +0 -64
  67. package/src/modules/home.svelte.ts +0 -29
  68. package/src/modules/hover.svelte.ts +0 -3
  69. package/src/modules/index.ts +0 -28
  70. package/src/modules/keys.svelte.ts +0 -72
  71. package/src/modules/module.svelte.ts +0 -47
  72. package/src/modules/navbar.svelte.ts +0 -11
  73. package/src/modules/pages.svelte.ts +0 -126
  74. package/src/modules/project.svelte.ts +0 -70
  75. package/src/modules/router.svelte.ts +0 -99
  76. package/src/modules/tabbar.svelte.ts +0 -21
  77. package/src/modules/teams.svelte.ts +0 -0
  78. package/src/modules/toolbar.svelte.ts +0 -34
  79. package/src/modules/watch.svelte.ts +0 -8
  80. package/src/modules/win.svelte.ts +0 -273
  81. package/src/pages/Comments.svelte +0 -32
  82. package/src/pages/Error.svelte +0 -25
  83. package/src/pages/Folders.svelte +0 -53
  84. package/src/pages/Login.svelte +0 -44
  85. package/src/pages/Pages.svelte +0 -75
  86. package/src/pages/Thread.svelte +0 -11
  87. package/src/styles/global.css +0 -16
  88. package/src/styles/router.css +0 -79
  89. package/src/styles/styles.css +0 -159
  90. package/src/styles/tailwind.css +0 -115
  91. package/src/styles/variables.css +0 -99
  92. package/src/test.ts +0 -22
  93. package/src/types/fubi.ts +0 -0
  94. package/src/types/msg.ts +0 -5
  95. package/src/types/pocketbase.ts +0 -7
  96. package/src/types/user.ts +0 -8
  97. package/svelte.config.js +0 -11
  98. package/tsconfig.json +0 -41
  99. package/vite.config.ts +0 -40
@@ -1,67 +0,0 @@
1
- <script lang="ts">
2
- import { ScrollState } from "runed";
3
- import { getModules } from "@modules";
4
-
5
- const { tabbar, navbar, win, router } = getModules();
6
-
7
- let { title = null, navbar: navbarVisible = true, tabbar: tabbarVisible = true, name = "", children } = $props();
8
-
9
- let current = $derived(router.current === name);
10
- let previous = $derived(router.previous === name);
11
- let next = $derived(router.next === name);
12
-
13
- let active = $derived(router.navigating
14
- ? router.direction === "forward"
15
- ? router.next === name
16
- : router.previous === name
17
- : router.current === name);
18
-
19
- let scrollContentEl = $state<HTMLElement>();
20
-
21
- const scrollContentState = new ScrollState({
22
- element: () => scrollContentEl,
23
- onScroll: (e) => {
24
- win.contentScroll = scrollContentState.y;
25
- }
26
- });
27
-
28
- $effect(() => {
29
- if (active) {
30
- navbar.title = title;
31
- navbar.visible = navbarVisible;
32
- tabbar.visible = tabbarVisible;
33
- }
34
- });
35
- </script>
36
-
37
-
38
- <div
39
- data-fubi-page={name}
40
- inert={!current}
41
- class={["page rim touch:rim-top flex min-w-full w-full flex-col rounded-(--fubi-rounded) mouse:rounded-(--fubi-rounded-mouse) touch:rounded-b-none h-full",
42
- "from-(--fubi-page-from-bg) to-(--fubi-page-to-bg)",
43
- "mouse:bg-gradient-to-b touch:bg-gradient-to-b from-(--fubi-page-from-bg) to-(--fubi-page-to-bg)",
44
- "transition-[translate,scale,opacity] ease-out-cubic duration-500 top-0",
45
- current && "page-current",
46
- previous && "page-previous",
47
- next && "page-next"
48
- ]}
49
- >
50
- <div
51
- tabindex="-1"
52
- class={[
53
- "page-content flex flex-col hide-scrollbar",
54
- "focus-visible:outline-none",
55
- win.dragging.active && "pointer-events-none select-none",
56
- "z-[8] relative overflow-auto h-full rounded-(--fubi-rounded) mouse:rounded-(--fubi-rounded-mouse)",
57
- "transition-[padding-top] duration-300 ease-out will-change-[padding-top]",
58
- navbarVisible && `pt-(--fubi-navbar-height) touch:pt-(--fubi-navbar-height-touch)`,
59
- tabbarVisible && `pb-(--fubi-tabbar-height) touch:pb-(--fubi-tabbar-height-touch)`,
60
- `px-12 touch:px-16`,
61
- ]}
62
- bind:this={scrollContentEl}
63
- >
64
- {@render children()}
65
- <!-- <div class="h-62 touch:h-76 w-full mouse:rounded-(--fubi-rounded-mouse) from-neutral-900/0 to-neutral-900 bg-gradient-to-b shrink-0 sticky bottom-0 left-0"></div> -->
66
- </div>
67
- </div>
@@ -1,60 +0,0 @@
1
- <script lang="ts">
2
- import { getModules } from "@modules";
3
-
4
- import { HugeiconsIcon } from "@hugeicons/svelte";
5
- import ButtonIcon from "./ButtonIcon.svelte";
6
- import ButtonTabbar from "@ui/ButtonTabbar.svelte";
7
-
8
- // icons
9
- import { Folder01Icon, FileEmpty02Icon, Comment03Icon } from "@hugeicons-pro/core-solid-rounded";
10
-
11
- const { tabbar, folders, router } = getModules();
12
-
13
- let tabs = $derived([
14
- { path: "folders", icon: Folder01Icon, alt: "Folders" },
15
- { path: "pages", icon: FileEmpty02Icon, alt: "Pages" },
16
- { path: "comments", icon: Comment03Icon, alt: "Comments" },
17
- ]);
18
-
19
- let tabActive = $derived(
20
- (router.navigating ? (router.direction === "forward" ? router.next : router.previous) : router.current) === "comments"
21
- ? "comments"
22
- : (router.navigating ? (router.direction === "forward" ? router.next : router.previous) : router.current) === "folders"
23
- ? "folders"
24
- : "pages",
25
- );
26
-
27
- let backLabel = $derived(tabActive === "comments" ? "pages" : tabActive === "folders" ? "folders" : "folders")
28
- </script>
29
-
30
- <div
31
- class={[
32
- "absolute w-full bottom-0 left-0 right-0 py-14 touch:py-16 mouse:justify-center touch:justify-center flex items-start gap-4 z-10","transition-[translate,opacity]",
33
- tabbar.visible ? "ease-out-cubic duration-400" : "translate-y-full pointer-events-none opacity-0 ease-in duration-200"
34
- ]}
35
- >
36
- <!-- <div class="touch:hidden">
37
- <ButtonIcon icon="chevron-left" alt="Back" label={backLabel} onclick={() => router.goto(backLabel, "back")} />
38
- </div> -->
39
-
40
- <div class="toolbar overflow-clip flex bg-neutral-500/10 glass backdrop-blur rounded-full p-4 relative z-10 pointer-events-auto">
41
- <div
42
- class={[
43
- "absolute right-4 top-1/2 -translate-y-1/2 flex h-32 w-36 touch:h-48 touch:w-56 rounded-full bg-slate-500/20 pointer-events-none",
44
- "transition-[translate,scale] duration-300 will-change-[transform]",
45
- tabActive === "pages" && "-translate-x-full",
46
- tabActive === "folders" && "-translate-x-[200%]",
47
- router.navigating ? "scale-140 glass ease-out" : "ease-out"
48
- ]}
49
- ></div>
50
-
51
- {#each tabs as { path, icon, alt }}
52
- {#if path === "folders" && folders.unlockedCount > 1 || path !== "folders"}
53
- <ButtonTabbar {path} {icon} {alt} active={tabActive === path} />
54
- {/if}
55
- {/each}
56
- </div>
57
- <!-- <div class="touch:hidden">
58
- <ButtonIcon icon="dots" alt="Menu" />
59
- </div> -->
60
- </div>
@@ -1,8 +0,0 @@
1
- <script>
2
- let { name, checked = $bindable() } = $props();
3
- </script>
4
-
5
- <label for={`fubi-checkbox-${name}`} class="block h-28 w-50 rounded-full bg-slate-500/20 hover:bg-slate-500/40 group/checkbox has-checked:bg-sky-500 relative transition-[background-color] ease-in-out-cubic duration-100 will-change-[background-color] focus-visible">
6
- <input name={`fubi-checkbox-${name}`} id={`fubi-checkbox-${name}`} type="checkbox" class="absolute inset-0 opacity-0" bind:checked switch />
7
- <div class="inset-4 rounded-full block absolute right-auto w-28 bg-white/80 shadow group-has-checked/checkbox:bg-white group-active/checkbox:w-32 transition-[width,background-color,translate] ease-out-cubic duration-100 group-has-checked/checkbox:translate-x-14 group-has-checked/checkbox:group-active/checkbox:translate-x-10 will-change-[width,background-color,translate]"></div>
8
- </label>
@@ -1,61 +0,0 @@
1
- <script lang="ts">
2
- import { getModules } from "@modules";
3
-
4
- import Tabbar from "@components/ui/Tabbar.svelte";
5
- import Navbar from "@ui/Navbar.svelte";
6
-
7
- const { win } = getModules();
8
-
9
- let { children = null } = $props();
10
- </script>
11
-
12
- <button
13
- class={[
14
- "fixed block inset-0 bg-transparent z-[1000] mouse:hidden",
15
- win.opened ? "touch:pointer-events-auto" : "pointer-events-none"
16
- ]}
17
- onclick={() => win.close()}>
18
- <span class="sr-only">Close Window</span>
19
- </button>
20
-
21
-
22
- <div
23
- bind:this={win.el}
24
- class={[
25
- // default styles
26
- "window fixed left-0 right-0 touch:overflow-clip touch:rounded-b-none z-[1001] text-white -bottom-0.5",
27
- "touch:rounded-t-(--fubi-rounded) touch:bg-neutral-900",
28
- // breakpoint styles
29
- "md:max-w-375",
30
- // styles for desktop
31
- "mouse:right-(--fubi-window-right) mouse:left-auto mouse:top-auto mouse:origin-bottom-right will-change-[translate,max-height,scale,opacity] mouse:bottom-(--fubi-window-bottom) mouse:w-300",
32
- "transition-[max-height,opacity,scale] will-change-[max-height,opacity,scale]",
33
- "mouse:max-h-1/2! mouse:min-h-[350px] h-[99dvh] mouse:h-500",
34
- // when dragging the window
35
- win.dragging.active
36
- ? "select-none duration-0"
37
- : win.opened
38
- ? "duration-300 mouse:duration-200 ease-out"
39
- : "duration-100 mouse:duration-200 ease",
40
-
41
- // when window opened or closed
42
- win.opened
43
- ? ""
44
- : "mouse:opacity-0 mouse:scale-90 pointer-events-none",
45
- ]}
46
- style={`max-height: ${win.dragging.active ? win.dragging.height : win.opened ? win.currentBreakpoint : 0}px;`}
47
- onpointerdown={(e) => win.drag.start(e)}
48
- onpointerup={(e) => win.drag.end(e)}
49
- onpointermove={(e) => win.drag.move(e)}
50
- onpointercancel={(e) => win.drag.end(e)}
51
- >
52
- <div class={["pointer-events-none rounded-full absolute left-1/2 -translate-x-1/2 z-[1001] mouse:hidden",
53
- "transition-[background-color,box-shadow,scale,width,height,top] duration-100 ease-out will-change-[background-color,box-shadow,scale,width,height,top]",
54
- "bg-slate-500/30 w-50 h-3 top-7"
55
- ]}></div>
56
- <Navbar />
57
- {#if children}
58
- {@render children()}
59
- {/if}
60
- <Tabbar />
61
- </div>
package/src/index.ts DELETED
@@ -1,28 +0,0 @@
1
- import { mount } from "svelte";
2
-
3
- import App from "./components/App.svelte";
4
-
5
- import styles from "./styles/styles.css?inline";
6
- import stylesGlobal from "./styles/global.css?inline";
7
-
8
- export const fubi = (projectId: string) => {
9
- // check if projectId provided if not throw error
10
- if (!projectId) {
11
- throw new Error("Please provide a projectId");
12
- }
13
-
14
- const root = document.createElement("div");
15
- root.id = "fubi-shadow-root";
16
-
17
- const shadowRoot = root.attachShadow({ mode: "open" });
18
- shadowRoot.innerHTML = `<style>${styles}</style>`;
19
-
20
- const wrapper = document.createElement("div");
21
- wrapper.id = "fubi-wrapper";
22
- shadowRoot.appendChild(wrapper);
23
-
24
- document.body.appendChild(root);
25
- document.head.insertAdjacentHTML("beforeend", `<style id="fubi-global-styles">${stylesGlobal}</style>`);
26
-
27
- mount(App, { target: wrapper, props: { projectId, wrapper } });
28
- };
@@ -1,58 +0,0 @@
1
- import { Action } from "svelte/action";
2
- import { animate, AnimationPlaybackControls, press as pressMotion } from "motion";
3
-
4
- type AnimateParams = Parameters<typeof animate>[1];
5
-
6
- interface PressActionParams {
7
- animate: AnimateParams;
8
- reset: AnimateParams;
9
- }
10
-
11
- interface SelectedActionParams {
12
- active: boolean;
13
- animate: AnimateParams;
14
- }
15
-
16
- export const press: Action<HTMLElement, PressActionParams> = (node, params) => {
17
- pressMotion(node, (pressedNode) => {
18
- animate(pressedNode, params.animate, { type: "spring", duration: 0.2 });
19
-
20
- return () => animate(pressedNode, params.reset);
21
- });
22
- };
23
-
24
- export const selected: Action<HTMLElement, SelectedActionParams> = (node, params) => {
25
- let animation: AnimationPlaybackControls;
26
-
27
- const updateAnimation = (isActive: boolean) => {
28
- // Cancel any existing animation first
29
- if (animation) animation.cancel();
30
-
31
- if (isActive) {
32
- // Animate TO the selected state
33
- animation = animate(node, params.animate, { duration: 0.3 });
34
- } else {
35
- // Animate BACK to the original state
36
- animation = animate(
37
- node,
38
- {
39
- scale: 1,
40
- borderRadius: 0,
41
- backgroundColor: "transparent",
42
- },
43
- { duration: 0.3 },
44
- );
45
- }
46
- };
47
-
48
- updateAnimation(params.active);
49
-
50
- return {
51
- update(newParams) {
52
- updateAnimation(newParams.active);
53
- },
54
- destroy() {
55
- if (animation) animation.cancel();
56
- },
57
- };
58
- };
@@ -1,94 +0,0 @@
1
- export const supportsHaptics = typeof window === "undefined" ? false : window.matchMedia("(pointer: coarse)").matches;
2
-
3
- function _haptic() {
4
- try {
5
- if (navigator.vibrate) {
6
- navigator.vibrate(50);
7
- return;
8
- }
9
-
10
- if (!supportsHaptics) return;
11
-
12
- const labelEl = document.createElement("label");
13
- labelEl.ariaHidden = "true";
14
- labelEl.style.display = "none";
15
-
16
- const inputEl = document.createElement("input");
17
- inputEl.type = "checkbox";
18
- inputEl.setAttribute("switch", "");
19
- labelEl.appendChild(inputEl);
20
-
21
- document.head.appendChild(labelEl);
22
- labelEl.click();
23
- document.head.removeChild(labelEl);
24
- } catch {
25
- // do nothing
26
- }
27
- }
28
-
29
- _haptic.confirm = () => {
30
- if (navigator.vibrate) {
31
- navigator.vibrate([50, 70, 50]);
32
- return;
33
- }
34
-
35
- _haptic();
36
- setTimeout(() => _haptic(), 120);
37
- };
38
-
39
- _haptic.error = () => {
40
- if (navigator.vibrate) {
41
- navigator.vibrate([50, 70, 50, 70, 50]);
42
- return;
43
- }
44
-
45
- _haptic();
46
- setTimeout(() => _haptic(), 120);
47
- setTimeout(() => _haptic(), 240);
48
- };
49
-
50
- // prevent intellisense from being unhelpful
51
- interface haptic {
52
- /** @deprecated */
53
- apply: never;
54
-
55
- /** @deprecated */
56
- arguments: never;
57
-
58
- /** @deprecated */
59
- bind: never;
60
-
61
- /** @deprecated */
62
- call: never;
63
-
64
- /** @deprecated */
65
- caller: never;
66
-
67
- /** @deprecated */
68
- length: never;
69
-
70
- /** @deprecated */
71
- name: never;
72
-
73
- /** @deprecated */
74
- prototype: never;
75
-
76
- /** @deprecated */
77
- toString: never;
78
-
79
- /** @deprecated */
80
- Symbol: never;
81
-
82
- /** a single haptic */
83
- (): void;
84
-
85
- /** two rapid haptics */
86
- confirm: () => void;
87
-
88
- /** three rapid haptics */
89
- error: () => void;
90
- }
91
-
92
- const __haptic = _haptic as haptic;
93
-
94
- export { __haptic as haptics };
package/src/lib/logger.ts DELETED
@@ -1,22 +0,0 @@
1
- const debug = true;
2
-
3
- export function logger(module: string) {
4
- return {
5
- info: (...params: any[]) => {
6
- if (debug) console.log(`%c➜ [${module}]`, "color: #38bdf8", ...params);
7
- },
8
- error: (...params: any[]) => {
9
- if (debug) console.error(`[${module}]`, ...params);
10
- },
11
- warn: (...params: any[]) => {
12
- if (debug) console.warn(`[${module}]`, ...params);
13
- },
14
- ok: (...params: any[]) => {
15
- if (debug) console.log(`%c✔ [${module}]`, "color: #22c55e", ...params);
16
- },
17
- group: (label?: string) => {
18
- label ? console.group(`%c—— ${label} ——`, "color: #6366f1") : console.group();
19
- },
20
- groupEnd: () => console.groupEnd(),
21
- };
22
- }
package/src/lib/routes.ts DELETED
@@ -1,37 +0,0 @@
1
- import { Component } from "svelte";
2
-
3
- import Login from "@pages/Login.svelte";
4
- import Folders from "@pages/Folders.svelte";
5
- import Pages from "@pages/Pages.svelte";
6
- import Comments from "@pages/Comments.svelte";
7
- import Thread from "@pages/Thread.svelte";
8
- import Error from "@pages/Error.svelte";
9
-
10
- export type RouteType = {
11
- component: Component;
12
- default?: boolean;
13
- direction?: "forward" | "back";
14
- };
15
-
16
- export const routes: Record<string, RouteType> = {
17
- login: {
18
- component: Login,
19
- },
20
- folders: {
21
- component: Folders,
22
- },
23
- pages: {
24
- component: Pages,
25
- },
26
- comments: {
27
- component: Comments,
28
- },
29
- thread: {
30
- component: Thread,
31
- },
32
- error: {
33
- component: Error,
34
- },
35
- };
36
-
37
- export type Routes = keyof typeof routes;
package/src/lib/utils.ts DELETED
@@ -1,27 +0,0 @@
1
- import { pocketbaseUrl } from "@modules/app.svelte";
2
- import { CollectionItem } from "@/types/pocketbase.js";
3
-
4
- export function hasFocus(): boolean {
5
- let activeEl = document.activeElement as HTMLElement | null;
6
- if (!activeEl) return false;
7
-
8
- while (activeEl?.shadowRoot?.activeElement) {
9
- activeEl = activeEl.shadowRoot.activeElement as HTMLElement;
10
- }
11
-
12
- if (activeEl.tagName === "INPUT" || activeEl.tagName === "TEXTAREA") return true;
13
-
14
- if (activeEl.contentEditable === "true") return true;
15
-
16
- return false;
17
- }
18
-
19
- export function getAssetUrl(record: CollectionItem, field: string): string | null {
20
- if (!record) return null;
21
-
22
- if (!record[field]) return null;
23
-
24
- const { id, collectionName } = record;
25
-
26
- return `${pocketbaseUrl}/api/files/${collectionName}/${id}/${record[field]}`;
27
- }
@@ -1,66 +0,0 @@
1
- import PocketBase from "pocketbase";
2
- import {
3
- Win,
4
- Navbar,
5
- Tabbar,
6
- Router,
7
- Auth,
8
- Project,
9
- Folders,
10
- Pages,
11
- Comments,
12
- Events,
13
- Toolbar,
14
- Keys,
15
- Dom,
16
- Environment,
17
- Hover,
18
- Domains,
19
- Home,
20
- } from ".";
21
-
22
- export const pocketbaseUrl = "https://fubi.wondermakers.dev";
23
-
24
- import { logger } from "@logger";
25
- const log = logger("app");
26
-
27
- export const createApp = (params: { projectId: string; wrapper: HTMLElement }) => {
28
- const { projectId, wrapper } = params;
29
-
30
- const modules = {
31
- win: new Win(),
32
- navbar: new Navbar(),
33
- tabbar: new Tabbar(),
34
- router: new Router(),
35
- auth: new Auth(),
36
- pb: new PocketBase(pocketbaseUrl),
37
- project: new Project(projectId),
38
- folders: new Folders(),
39
- pages: new Pages(),
40
- events: new Events(),
41
- comments: new Comments(),
42
- domains: new Domains(),
43
- toolbar: new Toolbar(wrapper),
44
- keys: new Keys(),
45
- dom: new Dom(),
46
- environment: new Environment(),
47
- hover: new Hover(),
48
- home: new Home(),
49
- };
50
-
51
- // init modules first
52
- Object.values(modules).forEach((m: any) => {
53
- if (m.init) m.init?.(modules);
54
- });
55
-
56
- log.ok(`All modules initialized.`);
57
-
58
- // start modules
59
- Object.values(modules).forEach((m: any) => {
60
- if (m.start) m.start?.();
61
- });
62
-
63
- return { modules };
64
- };
65
-
66
- export type App = ReturnType<typeof createApp>;
@@ -1,80 +0,0 @@
1
- import { MsgType } from "./../types/msg";
2
- import { Module } from "./module.svelte";
3
-
4
- import { logger } from "@logger";
5
- const log = logger("auth");
6
-
7
- import type { UserType } from "@/types/user";
8
- export class Auth extends Module {
9
- loading = $state(false);
10
- user = $state(null) as UserType | null;
11
- isAllowed = $derived(this.modules.project.isTeamMember || this.modules.folders.unlockedCount > 0);
12
-
13
- msg: MsgType = $state({
14
- type: "info",
15
- text: "",
16
- visible: false,
17
- });
18
-
19
- start() {
20
- this.user = this.modules.pb.authStore.record as UserType | null;
21
-
22
- this.modules.pb.authStore.onChange(async () => {
23
- this.user = this.modules.pb.authStore.record as UserType | null;
24
- await this.checkAccess();
25
- });
26
-
27
- this.checkAccess();
28
- }
29
-
30
- /**
31
- * Checks if user is allowed (team member/admin or has folder access) and routes accordingly
32
- */
33
- private async checkAccess() {
34
- if (this.user) {
35
- await this.modules.project.checkTeamMembership(this.user.id); // check if user is team member or admin
36
- await this.modules.folders.fetch(); // refetch the folders to update access
37
- if (this.isAllowed) await this.modules.domains.verify(); // verify domain by allowed user
38
- }
39
-
40
- // force logout user if not allowed
41
- if (!this.isAllowed && this.user) {
42
- this.msg = {
43
- type: "error",
44
- text: "You do not have access to this project or any of its folders.",
45
- visible: true,
46
- };
47
- this.track("warn", "User does not have access to project or any folders", { user: this.user.id });
48
- this.logout();
49
- }
50
-
51
- // navigate user based on access
52
- const nextRoute: Array<string> = this.modules.router.determineRoute(this.user, this.isAllowed);
53
- this.modules.router.goto(...(nextRoute as ["string", "forward" | "back"]));
54
- }
55
-
56
- async login(email: string, password: string) {
57
- this.loading = true;
58
- this.msg = { type: "info", text: "", visible: false };
59
-
60
- try {
61
- await this.modules.pb.collection("users").authWithPassword(email, password);
62
- this.user = this.modules.pb.authStore.record as UserType;
63
- this.track("ok", "User logged in");
64
- this.loading = false;
65
- return { success: true };
66
- } catch (error: any) {
67
- this.track("error", "Login failed", { error: error?.message || error, email });
68
- this.msg = { type: "error", text: error?.message || "Login failed", visible: true };
69
- this.loading = false;
70
- return { success: false, error: error?.message || error };
71
- }
72
- }
73
-
74
- async logout() {
75
- if (!this.user) return;
76
-
77
- this.modules.pb.authStore.clear();
78
- this.user = null;
79
- }
80
- }
@@ -1,79 +0,0 @@
1
- import type { AuthRecord, RecordService, RecordSubscription } from "pocketbase";
2
- import { CollectionItem } from "@/types/pocketbase";
3
- import { Module } from "./module.svelte";
4
- import { logger } from "@logger";
5
-
6
- const log = logger("collection-module");
7
-
8
- interface CollectionModuleOptions<T extends CollectionItem = CollectionItem> {
9
- name: string;
10
- filters?: string;
11
- sort?: string;
12
- expand?: string;
13
- page?: number;
14
- perPage?: number;
15
- query?: Record<string, any>;
16
- transform?: (record: T, authUser: AuthRecord | null) => T;
17
- onChange?: (e: RecordSubscription) => void;
18
- onFinish?: () => void;
19
- }
20
-
21
- export abstract class CollectionModule<T extends CollectionItem = CollectionItem> extends Module {
22
- data = $state<T[]>([]);
23
- loading = $state(false);
24
- error = $state<string | null>(null);
25
- filters = $state<string | undefined>(undefined);
26
- sort = $state<string | undefined>(undefined);
27
- page = $state<number>(1);
28
- perPage = $state<number>(50);
29
-
30
- empty = $derived(this.data.length === 0);
31
-
32
- protected collection!: RecordService<T>;
33
- private options!: CollectionModuleOptions<T>;
34
-
35
- protected initCollection(options: CollectionModuleOptions<T>) {
36
- this.options = options;
37
- this.filters = options.filters;
38
- this.sort = options.sort;
39
- this.page = options.page || 1;
40
- this.perPage = options.perPage || 50;
41
- this.collection = this.modules.pb.collection(options.name);
42
- this.collection.subscribe("*", (e) => {
43
- this.fetch();
44
- if (options.onChange) options.onChange(e);
45
- });
46
- }
47
-
48
- async fetch() {
49
- this.loading = true;
50
- this.error = null;
51
-
52
- try {
53
- const result = await this.collection.getList<T>(this.page, this.perPage, {
54
- filter: this.filters,
55
- sort: this.sort,
56
- expand: this.options.expand,
57
- ...this.options.query,
58
- });
59
-
60
- log.info("fetched:");
61
- console.log(result.items);
62
-
63
- this.data = result.items.map((record) =>
64
- this.options.transform ? this.options.transform(record, this.modules.pb.authStore.record) : record,
65
- );
66
-
67
- if (this.options.onFinish) this.options.onFinish();
68
- } catch (err: any) {
69
- this.error = err.message || "Failed to fetch";
70
- log.error(`Error fetching ${this.options.name}:`, err);
71
- } finally {
72
- this.loading = false;
73
- }
74
- }
75
-
76
- destroy() {
77
- this.collection?.unsubscribe("*");
78
- }
79
- }