create-switch-framework-app 0.1.0 → 0.2.2

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 (37) hide show
  1. package/README.md +25 -25
  2. package/bin/create-switch-framework-app.js +48 -35
  3. package/package.json +1 -1
  4. package/templates/electron/base/app/(tabs)/+not-found.js +157 -157
  5. package/templates/electron/base/app/(tabs)/_layout.js +57 -93
  6. package/templates/electron/base/app/(tabs)/explore.js +55 -44
  7. package/templates/electron/base/app/(tabs)/index.js +10 -24
  8. package/templates/electron/base/app/+not-found.js +148 -158
  9. package/templates/electron/base/app/_layout.js +24 -44
  10. package/templates/electron/base/app/index.js +16 -30
  11. package/templates/electron/base/assets/logo.svg +5 -5
  12. package/templates/electron/base/components/SwSplashScreen.js +1 -1
  13. package/templates/electron/base/components/SwStarterSplashScreen.js +130 -140
  14. package/templates/electron/base/components/SwTabBar.js +146 -153
  15. package/templates/electron/base/electron/electron-builder.json +19 -19
  16. package/templates/electron/base/electron/main.js +30 -30
  17. package/templates/electron/base/electron/preload.js +5 -5
  18. package/templates/electron/base/index.js +2 -3
  19. package/templates/electron/base/main.js +1 -1
  20. package/templates/electron/base/preload.js +1 -1
  21. package/templates/electron/base/server.js +27 -42
  22. package/templates/web/base/app/(tabs)/+not-found.js +157 -157
  23. package/templates/web/base/app/(tabs)/_layout.js +57 -93
  24. package/templates/web/base/app/(tabs)/explore.js +55 -44
  25. package/templates/web/base/app/(tabs)/index.js +10 -24
  26. package/templates/web/base/app/+not-found.js +148 -158
  27. package/templates/web/base/app/_layout.js +24 -44
  28. package/templates/web/base/app/index.js +16 -30
  29. package/templates/web/base/assets/logo.svg +5 -5
  30. package/templates/web/base/components/SwSplashScreen.js +1 -1
  31. package/templates/web/base/components/SwStarterSplashScreen.js +130 -140
  32. package/templates/web/base/components/SwTabBar.js +146 -153
  33. package/templates/web/base/index.js +2 -3
  34. package/templates/electron/base/app/(tabs)/register.js +0 -12
  35. package/templates/electron/base/app/register.js +0 -12
  36. package/templates/web/base/app/(tabs)/register.js +0 -12
  37. package/templates/web/base/app/register.js +0 -12
@@ -1,153 +1,146 @@
1
- export class SwTabBar extends HTMLElement {
2
- constructor() {
3
- super();
4
- this.attachShadow({ mode: 'open' });
5
- this._unsub = null;
6
- }
7
-
8
- connectedCallback() {
9
- this.render();
10
- this.updateActive();
11
-
12
- if (globalStates?.subscribe) {
13
- this._unsub = globalStates.subscribe(() => this.updateActive());
14
- }
15
-
16
- this.shadowRoot.addEventListener('click', (e) => {
17
- const btn = e.target?.closest?.('button[data-route]');
18
- if (!btn) return;
19
- const route = btn.getAttribute('data-route');
20
- const navigate = globalStates?.getState ? globalStates.getState('navigate') : null;
21
- if (typeof navigate === 'function') navigate(route);
22
- });
23
- }
24
-
25
- disconnectedCallback() {
26
- if (this._unsub) this._unsub();
27
- }
28
-
29
- getTabs() {
30
- const layout = globalStates?.getState ? globalStates.getState('tabsLayout') : null;
31
- const tabs = Array.isArray(layout?.tabs) ? layout.tabs : [];
32
- return tabs;
33
- }
34
-
35
- updateActive() {
36
- const activeRoute = globalStates?.getState ? globalStates.getState('activeRoute') : '';
37
- const tabs = this.getTabs();
38
-
39
- tabs.forEach((t) => {
40
- const el = this.shadowRoot.getElementById(`tab-${t.name}`);
41
- if (!el) return;
42
- const isActive = String(activeRoute || '').startsWith(String(t.name));
43
- el.classList.toggle('active', isActive);
44
- });
45
- }
46
-
47
- render() {
48
- const tabs = this.getTabs();
49
-
50
- this.shadowRoot.innerHTML = `
51
- ${this.styleSheet()}
52
- <nav class="bar" aria-label="Bottom tabs">
53
- ${tabs.map((t) => `
54
- <button class="tab" id="tab-${t.name}" data-route="${t.name}" type="button">
55
- <span class="icon">${this.getIcon(t.icon)}</span>
56
- <span class="label">${t.title || t.name}</span>
57
- </button>
58
- `).join('')}
59
- </nav>
60
- `;
61
- }
62
-
63
- getIcon(name) {
64
- const map = {
65
- home: `<span class='switch_icon_house'></span>`,
66
- compass: `<span class='switch_icon_compass'></span>`,
67
- settings: `<span class='switch_icon_gear'></span>`
68
- };
69
- return map[name] || map.home;
70
- }
71
-
72
- styleSheet() {
73
- return `
74
- <style>
75
- @import '/assets/icons/style.css';
76
-
77
- :host {
78
- display: block;
79
- width: 100%;
80
- background: transparent;
81
- }
82
-
83
- * {
84
- box-sizing: border-box;
85
- font-family: var(--font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
86
- }
87
-
88
- .bar {
89
- display: grid;
90
- grid-template-columns: repeat(2, 1fr);
91
- gap: 12px;
92
- padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
93
- border-top: 1px solid var(--border_color, #e5e5e5);
94
- background: rgba(255, 255, 255, 0.95);
95
- backdrop-filter: saturate(180%) blur(10px);
96
- }
97
-
98
- :root[data-theme="dark"] .bar {
99
- background: rgba(11, 15, 20, 0.95);
100
- border-top-color: rgba(255, 255, 255, 0.1);
101
- }
102
-
103
- .tab {
104
- appearance: none;
105
- border: none;
106
- background: transparent;
107
- border-radius: 20px;
108
- padding: 12px 14px;
109
- cursor: pointer;
110
- display: flex;
111
- flex-direction: column;
112
- align-items: center;
113
- gap: 8px;
114
- color: var(--sub_text, #666);
115
- font-weight: 600;
116
- transition: all 0.2s ease;
117
- }
118
-
119
- .tab:active {
120
- transform: scale(0.95);
121
- }
122
-
123
- .tab.active {
124
- background: rgba(0, 0, 0, 0.06);
125
- color: var(--main_text, #000);
126
- }
127
-
128
- :root[data-theme="dark"] .tab.active {
129
- background: rgba(255, 255, 255, 0.1);
130
- }
131
-
132
- .icon {
133
- display: flex;
134
- align-items: center;
135
- justify-content: center;
136
- font-size: 24px;
137
- line-height: 1;
138
- }
139
-
140
- .label {
141
- display: block;
142
- text-align: center;
143
- font-size: 11px;
144
- font-weight: 600;
145
- }
146
- </style>
147
- `;
148
- }
149
- }
150
-
151
- if (!customElements.get('sw-tab-bar')) {
152
- customElements.define('sw-tab-bar', SwTabBar);
153
- }
1
+ import { SwitchComponent } from 'switch-framework';
2
+ import { navigate, useRouteChangesSubscriber, getActiveRoute } from 'switch-framework/router';
3
+
4
+ export class SwTabBar extends SwitchComponent {
5
+ static tag = 'sw-tab-bar';
6
+
7
+ connected() {
8
+ this.updateActive();
9
+ this._unsub = useRouteChangesSubscriber(() => this.updateActive());
10
+
11
+ this.shadowRoot.addEventListener('click', (e) => {
12
+ const btn = e.target?.closest?.('button[data-route]');
13
+ if (!btn) return;
14
+ const route = btn.getAttribute('data-route');
15
+ navigate(route);
16
+ });
17
+ }
18
+
19
+ disconnected() {
20
+ if (this._unsub) this._unsub();
21
+ }
22
+
23
+ getTabs() {
24
+ const layout = globalStates?.getState ? globalStates.getState('tabsLayout') : null;
25
+ const tabs = Array.isArray(layout?.tabs) ? layout.tabs : [];
26
+ return tabs;
27
+ }
28
+
29
+ updateActive() {
30
+ const activeRoute = getActiveRoute();
31
+ const tabs = this.getTabs();
32
+
33
+ tabs.forEach((t) => {
34
+ const el = this.shadowRoot?.getElementById?.(`tab-${t.name}`);
35
+ if (!el) return;
36
+ const matchList = Array.isArray(t?.match) ? t.match : [t.name].filter(Boolean);
37
+ const isActive = matchList.some((m) => String(activeRoute || '').startsWith(String(m)) || String(activeRoute || '') === String(m));
38
+ el.classList.toggle('active', isActive);
39
+ });
40
+ }
41
+
42
+ getIcon(name) {
43
+ const map = {
44
+ home: `<span class='switch_icon_house'></span>`,
45
+ compass: `<span class='switch_icon_compass'></span>`,
46
+ settings: `<span class='switch_icon_gear'></span>`
47
+ };
48
+ return map[name] || map.home;
49
+ }
50
+
51
+ render() {
52
+ const tabs = this.getTabs();
53
+
54
+ return `
55
+ <nav class="bar" aria-label="Bottom tabs">
56
+ ${tabs.map((t) => {
57
+ const route = t.initialRoute || (t.path ? t.path.replace(/^\//, '') : t.name);
58
+ return `
59
+ <button class="tab" id="tab-${t.name}" data-route="${route || t.name}" type="button">
60
+ <span class="icon">${this.getIcon(t.icon)}</span>
61
+ <span class="label">${t.title || t.name}</span>
62
+ </button>
63
+ `;
64
+ }).join('')}
65
+ </nav>
66
+ `;
67
+ }
68
+
69
+ styleSheet() {
70
+ return `
71
+ <style>
72
+ @import '/assets/icons/style.css';
73
+
74
+ :host {
75
+ display: block;
76
+ width: 100%;
77
+ background: transparent;
78
+ }
79
+
80
+ * {
81
+ box-sizing: border-box;
82
+ font-family: var(--font, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif);
83
+ }
84
+
85
+ .bar {
86
+ display: grid;
87
+ grid-template-columns: repeat(2, 1fr);
88
+ gap: 12px;
89
+ padding: 12px 16px calc(12px + env(safe-area-inset-bottom, 0px));
90
+ border-top: 1px solid var(--border_color, #e5e5e5);
91
+ background: rgba(255, 255, 255, 0.95);
92
+ backdrop-filter: saturate(180%) blur(10px);
93
+ }
94
+
95
+ :root[data-theme="dark"] .bar {
96
+ background: rgba(11, 15, 20, 0.95);
97
+ border-top-color: rgba(255, 255, 255, 0.1);
98
+ }
99
+
100
+ .tab {
101
+ appearance: none;
102
+ border: none;
103
+ background: transparent;
104
+ border-radius: 20px;
105
+ padding: 12px 14px;
106
+ cursor: pointer;
107
+ display: flex;
108
+ flex-direction: column;
109
+ align-items: center;
110
+ gap: 8px;
111
+ color: var(--sub_text, #666);
112
+ font-weight: 600;
113
+ transition: all 0.2s ease;
114
+ }
115
+
116
+ .tab:active {
117
+ transform: scale(0.95);
118
+ }
119
+
120
+ .tab.active {
121
+ background: rgba(0, 0, 0, 0.06);
122
+ color: var(--main_text, #000);
123
+ }
124
+
125
+ :root[data-theme="dark"] .tab.active {
126
+ background: rgba(255, 255, 255, 0.1);
127
+ }
128
+
129
+ .icon {
130
+ display: flex;
131
+ align-items: center;
132
+ justify-content: center;
133
+ font-size: 24px;
134
+ line-height: 1;
135
+ }
136
+
137
+ .label {
138
+ display: block;
139
+ text-align: center;
140
+ font-size: 11px;
141
+ font-weight: 600;
142
+ }
143
+ </style>
144
+ `;
145
+ }
146
+ }
@@ -1,19 +1,19 @@
1
- {
2
- "appId": "com.switchframework.app",
3
- "productName": "Switch Framework App",
4
- "directories": {
5
- "output": "dist"
6
- },
7
- "files": [
8
- "**/*"
9
- ],
10
- "win": {
11
- "target": "nsis"
12
- },
13
- "mac": {
14
- "target": "dmg"
15
- },
16
- "linux": {
17
- "target": "AppImage"
18
- }
19
- }
1
+ {
2
+ "appId": "com.switchframework.app",
3
+ "productName": "Switch Framework App",
4
+ "directories": {
5
+ "output": "dist"
6
+ },
7
+ "files": [
8
+ "**/*"
9
+ ],
10
+ "win": {
11
+ "target": "nsis"
12
+ },
13
+ "mac": {
14
+ "target": "dmg"
15
+ },
16
+ "linux": {
17
+ "target": "AppImage"
18
+ }
19
+ }
@@ -1,30 +1,30 @@
1
- const { app, BrowserWindow } = require('electron');
2
- const path = require('node:path');
3
-
4
- const port = process.env.PORT ? Number(process.env.PORT) : 3000;
5
- let mainWindow;
6
-
7
- function createWindow() {
8
- mainWindow = new BrowserWindow({
9
- width: 1200,
10
- height: 800,
11
- webPreferences: {
12
- preload: path.join(__dirname, 'preload.js')
13
- }
14
- });
15
-
16
- mainWindow.loadURL(`http://localhost:${port}`);
17
- }
18
-
19
- app.whenReady().then(() => {
20
- require('../server.js');
21
- setTimeout(createWindow, 350);
22
-
23
- app.on('activate', () => {
24
- if (BrowserWindow.getAllWindows().length === 0) createWindow();
25
- });
26
- });
27
-
28
- app.on('window-all-closed', () => {
29
- if (process.platform !== 'darwin') app.quit();
30
- });
1
+ const { app, BrowserWindow } = require('electron');
2
+ const path = require('node:path');
3
+
4
+ const port = process.env.PORT ? Number(process.env.PORT) : 3000;
5
+ let mainWindow;
6
+
7
+ function createWindow() {
8
+ mainWindow = new BrowserWindow({
9
+ width: 1200,
10
+ height: 800,
11
+ webPreferences: {
12
+ preload: path.join(__dirname, 'preload.js')
13
+ }
14
+ });
15
+
16
+ mainWindow.loadURL(`http://localhost:${port}`);
17
+ }
18
+
19
+ app.whenReady().then(() => {
20
+ require('../server.js');
21
+ setTimeout(createWindow, 350);
22
+
23
+ app.on('activate', () => {
24
+ if (BrowserWindow.getAllWindows().length === 0) createWindow();
25
+ });
26
+ });
27
+
28
+ app.on('window-all-closed', () => {
29
+ if (process.platform !== 'darwin') app.quit();
30
+ });
@@ -1,5 +1,5 @@
1
- const { contextBridge } = require('electron');
2
-
3
- contextBridge.exposeInMainWorld('switchApp', {
4
- ping: () => 'pong'
5
- });
1
+ const { contextBridge } = require('electron');
2
+
3
+ contextBridge.exposeInMainWorld('switchApp', {
4
+ ping: () => 'pong'
5
+ });
@@ -1,5 +1,4 @@
1
- import { startApp } from '/switch-framework/index.js';
1
+ import { startApp } from 'switch-framework';
2
2
  import layout from './app/_layout.js';
3
- import { appRegisters } from './app/register.js';
4
3
 
5
- startApp(layout, appRegisters);
4
+ startApp(layout);
@@ -1 +1 @@
1
- module.exports = require('./electron/main.js');
1
+ module.exports = require('./electron/main.js');
@@ -1 +1 @@
1
- module.exports = require('./electron/preload.js');
1
+ module.exports = require('./electron/preload.js');
@@ -1,42 +1,27 @@
1
- const express = require('express');
2
- const session = require('express-session');
3
- const path = require('node:path');
4
- require('dotenv').config();
5
-
6
- const { checkRestrict } = require('switch-framework-backend/middleware');
7
-
8
- const app = express();
9
- const PORT = process.env.PORT ? Number(process.env.PORT) : 3000;
10
-
11
- app.use(express.json({ limit: '25mb' }));
12
-
13
- app.use(session({
14
- secret: process.env.SESSION_SECRET || 'dev-secret',
15
- resave: false,
16
- saveUninitialized: false
17
- }));
18
-
19
- // Serve switch-framework to the browser from node_modules
20
- app.use('/switch-framework', express.static(path.join(__dirname, 'node_modules', 'switch-framework')));
21
-
22
- // Serve project files
23
- app.use(express.static(path.join(__dirname, '.')));
24
-
25
- const restrictConfig = {
26
- public: ['/', '/login'],
27
- rules: [
28
- { prefix: '/admin', roles: ['admin'] },
29
- { prefix: '/billing', roles: ['billing', 'admin'] },
30
- { path: '/login', roles: ['*'] }
31
- ]
32
- };
33
-
34
- app.use(checkRestrict(restrictConfig));
35
-
36
- app.get('*', (req, res) => {
37
- res.sendFile(path.join(__dirname, '.', 'index.html'));
38
- });
39
-
40
- app.listen(PORT, () => {
41
- console.log('Switch Framework app running at http://localhost:' + PORT);
42
- });
1
+ require('dotenv').config();
2
+
3
+ const switchFrameworkBackend = require('switch-framework-backend');
4
+
5
+ switchFrameworkBackend.config({
6
+ PORT: process.env.PORT ? Number(process.env.PORT) : 3000,
7
+ staticRoot: __dirname,
8
+ session: {
9
+ secret: process.env.SESSION_SECRET || 'dev-secret',
10
+ resave: false,
11
+ saveUninitialized: false
12
+ }
13
+ });
14
+
15
+ const app = switchFrameworkBackend();
16
+
17
+ app.initServer((server) => {
18
+ const restrictConfig = {
19
+ public: ['/', '/login'],
20
+ rules: [
21
+ { prefix: '/admin', roles: ['admin'] },
22
+ { prefix: '/billing', roles: ['billing', 'admin'] },
23
+ { path: '/login', roles: ['*'] }
24
+ ]
25
+ };
26
+ server.use(switchFrameworkBackend.checkRestrict(restrictConfig));
27
+ });