@alstar/studio 0.0.0-beta.5 → 0.0.0-beta.7

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 (53) hide show
  1. package/api/api-key.ts +74 -0
  2. package/api/block.ts +82 -42
  3. package/api/index.ts +9 -1
  4. package/api/mcp.ts +53 -0
  5. package/components/AdminPanel.ts +74 -0
  6. package/components/BlockFieldRenderer.ts +121 -0
  7. package/components/BlockRenderer.ts +22 -0
  8. package/components/Entries.ts +5 -4
  9. package/components/Entry.ts +13 -21
  10. package/components/FieldRenderer.ts +35 -0
  11. package/components/Render.ts +46 -0
  12. package/components/Settings.ts +98 -0
  13. package/components/{layout.ts → SiteLayout.ts} +9 -13
  14. package/components/fields/Markdown.ts +44 -0
  15. package/components/fields/Text.ts +42 -0
  16. package/components/fields/index.ts +4 -0
  17. package/components/icons.ts +59 -0
  18. package/index.ts +52 -30
  19. package/package.json +3 -2
  20. package/pages/entry/[id].ts +17 -0
  21. package/{components → pages}/index.ts +7 -4
  22. package/pages/settings.ts +10 -0
  23. package/public/studio/admin-panel.css +103 -0
  24. package/public/studio/blocks.css +53 -0
  25. package/public/studio/main.css +162 -0
  26. package/public/studio/main.js +10 -0
  27. package/public/studio/markdown-editor.js +34 -0
  28. package/public/studio/settings.css +24 -0
  29. package/public/studio/sortable-list.js +40 -0
  30. package/queries/block-with-children.ts +74 -0
  31. package/queries/block.ts +31 -40
  32. package/queries/db-types.ts +15 -0
  33. package/queries/getBlockTrees-2.ts +0 -0
  34. package/queries/getBlockTrees.ts +316 -0
  35. package/queries/getBlocks.ts +214 -0
  36. package/queries/index.ts +1 -0
  37. package/queries/structure-types.ts +97 -0
  38. package/schemas.ts +14 -3
  39. package/types.ts +123 -6
  40. package/utils/buildBlocksTree.ts +4 -4
  41. package/utils/define.ts +31 -5
  42. package/utils/file-based-router.ts +1 -0
  43. package/utils/get-or-create-row.ts +41 -0
  44. package/utils/startup-log.ts +9 -0
  45. package/components/AdminPanel/AdminPanel.css +0 -78
  46. package/components/AdminPanel/AdminPanel.ts +0 -57
  47. package/components/Block.ts +0 -116
  48. package/components/Field.ts +0 -168
  49. package/components/Fields.ts +0 -43
  50. package/public/main.css +0 -95
  51. package/public/main.js +0 -44
  52. /package/{components/Entry.css → public/studio/entry.css} +0 -0
  53. /package/public/{favicon.svg → studio/favicon.svg} +0 -0
@@ -0,0 +1,46 @@
1
+ import { html } from 'hono/html'
2
+ import type { HtmlEscapedString } from 'hono/utils/html'
3
+ import FieldRenderer from './FieldRenderer.ts'
4
+ import BlockFieldRenderer from './BlockFieldRenderer.ts'
5
+ import BlockRenderer from './BlockRenderer.ts'
6
+ import {
7
+ BlockFieldInstance,
8
+ BlockInstance,
9
+ FieldInstance,
10
+ } from '../utils/define.ts'
11
+ import type {
12
+ BlockDefStructure,
13
+ BlocksFieldDefStructure,
14
+ FieldDefStructure,
15
+ } from '../types.ts'
16
+
17
+ export default (props: {
18
+ entryId: number
19
+ parentId: number
20
+ structure: BlockDefStructure | FieldDefStructure | BlocksFieldDefStructure
21
+ id?: number
22
+ name: string
23
+ sortOrder?: number
24
+ }): HtmlEscapedString | Promise<HtmlEscapedString> | string => {
25
+ const { entryId, parentId, structure, name, id } = props
26
+
27
+ if (!structure) return html`<p>No block</p>`
28
+
29
+ try {
30
+ switch (structure.instanceOf) {
31
+ case FieldInstance: {
32
+ return FieldRenderer({ entryId, parentId, id, structure, name })
33
+ }
34
+
35
+ case BlockFieldInstance: {
36
+ return BlockFieldRenderer({ entryId, parentId, id, structure, name })
37
+ }
38
+
39
+ case BlockInstance: {
40
+ return BlockRenderer({ entryId, parentId, structure, id })
41
+ }
42
+ }
43
+ } catch (error) {
44
+ return html`<p>Error rendering "${name}"</p>`
45
+ }
46
+ }
@@ -0,0 +1,98 @@
1
+ import { db } from '@alstar/db'
2
+ import { html } from 'hono/html'
3
+ import { sql } from '../utils/sql.ts'
4
+ import * as icons from './icons.ts'
5
+
6
+ export default () => {
7
+ const apiKeys = db.database
8
+ .prepare(sql`
9
+ select
10
+ *
11
+ from
12
+ api_keys
13
+ `)
14
+ .all()
15
+
16
+ return html`
17
+ <div id="settings" data-signals="{ apiKey: '', copied: false }">
18
+ <!-- <h1>Settings</h1> -->
19
+ <article>
20
+ <header>API Keys</header>
21
+
22
+ <ul>
23
+ ${apiKeys.map((apiKey) => {
24
+ return html`<li>
25
+ <p>${apiKey.name}</p>
26
+ <input type="text" disabled value="${apiKey.hint}" />
27
+ <form
28
+ data-on-submit="@delete('/admin/api/api-key', { contentType: 'form' })"
29
+ >
30
+ <button
31
+ data-tooltip="Delete API key"
32
+ data-placement="left"
33
+ type="submit"
34
+ class="ghost"
35
+ >
36
+ ${icons.trash}
37
+ </button>
38
+
39
+ <input type="hidden" name="value" value="${apiKey.value}" />
40
+ </form>
41
+ </li>`
42
+ })}
43
+ </ul>
44
+
45
+ <form
46
+ data-on-submit="@post('/admin/api/api-key', { contentType: 'form' })"
47
+ >
48
+ <label for="api_key_name">Generate API Key</label>
49
+
50
+ <input
51
+ data-bind="name"
52
+ type="text"
53
+ name="name"
54
+ id="api_key_name"
55
+ placeholder="Name"
56
+ />
57
+
58
+ <button type="submit" class="ghost">Generate key</button>
59
+ </form>
60
+
61
+ <dialog data-attr="{ open: $apiKey !== '' }">
62
+ <article>
63
+ <header>
64
+ <p>API Key</p>
65
+ </header>
66
+ <p>Be sure to save this key, as it wont be shown again.</p>
67
+
68
+ <div style="display: flex; gap: 1rem; align-items: center;">
69
+ <h3 style="margin: 0;">
70
+ <code data-text="$apiKey"></code>
71
+ </h3>
72
+
73
+ <button
74
+ style="display: flex; align-items: center;"
75
+ data-attr="{ id: $apiKey }"
76
+ data-on-click="navigator.clipboard.writeText($apiKey); $copied = true"
77
+ class="ghost"
78
+ aria-label="Copy key to clipboard"
79
+ >
80
+ ${icons.clipboard}
81
+ <span style="display: none; margin-left: 0.5rem; color: green;" data-style="{ display: $copied && 'block' }">Copied</span>
82
+ </button>
83
+ </div>
84
+
85
+ <footer>
86
+ <button
87
+ class="ghost"
88
+ data-on-click="$apiKey = ''; $copied = false; evt.target.closest('dialog')?.close()"
89
+ >
90
+ Close
91
+ </button>
92
+ </footer>
93
+ </article>
94
+ </dialog>
95
+ </article>
96
+ </div>
97
+ `
98
+ }
@@ -1,8 +1,7 @@
1
- import adminPanel from './AdminPanel/AdminPanel.ts'
1
+ import adminPanel from './AdminPanel.ts'
2
2
  import { html } from 'hono/html'
3
3
  import { type HtmlEscapedString } from 'hono/utils/html'
4
- import { rootdir, studioConfig } from '../index.ts'
5
- import { type Structure } from '../types.ts'
4
+ import { studioConfig } from '../index.ts'
6
5
 
7
6
  export default (props: {
8
7
  content:
@@ -10,7 +9,6 @@ export default (props: {
10
9
  | Promise<string>
11
10
  | HtmlEscapedString
12
11
  | Promise<HtmlEscapedString>
13
- structure: Structure
14
12
  }) => {
15
13
  return html`
16
14
  <!DOCTYPE html>
@@ -23,15 +21,11 @@ export default (props: {
23
21
  Studio
24
22
  </title>
25
23
 
26
- <link
27
- rel="icon"
28
- type="image/svg"
29
- href="${rootdir}/public/favicon.svg"
30
- />
24
+ <link rel="icon" type="image/svg" href="/studio/favicon.svg" />
31
25
 
32
26
  <meta name="color-scheme" content="light dark" />
33
27
 
34
- <link rel="stylesheet" href="${rootdir}/public/main.css" />
28
+ <link rel="stylesheet" href="/studio/main.css" />
35
29
 
36
30
  <script
37
31
  type="module"
@@ -47,18 +41,20 @@ export default (props: {
47
41
  }
48
42
  }
49
43
  </script>
44
+
45
+ <script src="/studio/markdown-editor.js" type="module"></script>
46
+ <script src="/studio/sortable-list.js" type="module"></script>
47
+ <script src="/studio/main.js" type="module"></script>
50
48
  </head>
51
49
 
52
50
  <body data-barba="wrapper">
53
- <section>${adminPanel(props.structure)}</section>
51
+ <section style="margin-bottom: 0;">${adminPanel()}</section>
54
52
 
55
53
  <main>
56
54
  <section data-barba="container" data-barba-namespace="default">
57
55
  ${props.content}
58
56
  </section>
59
57
  </main>
60
-
61
- <script src="${rootdir}/public/main.js" type="module"></script>
62
58
  </body>
63
59
  </html>
64
60
  `
@@ -0,0 +1,44 @@
1
+ import type { FieldDefStructure } from '../../types.ts'
2
+ import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
3
+ import { html } from '../../utils/html.ts'
4
+
5
+ export default (props: {
6
+ entryId: number
7
+ parentId: number
8
+ name: string
9
+ id?: number
10
+ structure: FieldDefStructure
11
+ }) => {
12
+ const { entryId, parentId, name, structure, id } = props
13
+
14
+ const data = getOrCreateRow({ parentId, name, field: structure, id })
15
+
16
+ if (!data) return html`<p>No block</p>`
17
+
18
+ return html`
19
+ <form
20
+ data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
21
+ >
22
+ <hgroup>
23
+ <label for="block-${data.id}">${structure.label}</label>
24
+ <p><small>${structure.description}</small></p>
25
+ </hgroup>
26
+
27
+ <markdown-editor
28
+ data-content="${data.value?.trim()}"
29
+ data-id="${data.id}"
30
+ >
31
+ <!-- <textarea id="block-{data.id}" name="value" class="markdown">
32
+ {data.value}
33
+ </textarea
34
+ > -->
35
+ </markdown-editor>
36
+
37
+ <input type="hidden" name="type" value="${structure.type}" />
38
+ <input type="hidden" name="id" value="${data.id}" />
39
+ <input type="hidden" name="entryId" value="${entryId}" />
40
+ <input type="hidden" name="parentId" value="${parentId}" />
41
+ <input type="hidden" name="name" value="${name}" />
42
+ </form>
43
+ `
44
+ }
@@ -0,0 +1,42 @@
1
+ import { getOrCreateRow } from '../../utils/get-or-create-row.ts'
2
+ import { html } from '../../utils/html.ts'
3
+ import type { FieldDefStructure } from '../../types.ts'
4
+
5
+ export default (props: {
6
+ entryId: number
7
+ parentId: number
8
+ name: string
9
+ id?: number
10
+ structure: FieldDefStructure
11
+ sortOrder?: number
12
+ }) => {
13
+ const { entryId, parentId, name, structure, sortOrder = 0, id } = props
14
+
15
+ const data = getOrCreateRow({ parentId, name, field: structure, sortOrder, id })
16
+
17
+ if (!data) return html`<p>No block</p>`
18
+
19
+ return html`
20
+ <form
21
+ data-on-input="@patch('/admin/api/block', { contentType: 'form' })"
22
+ >
23
+ <hgroup>
24
+ <label for="block-${data.id}">${structure.label}</label>
25
+ <p><small>${structure.description}</small></p>
26
+ </hgroup>
27
+
28
+ <input
29
+ id="block-${data.id}"
30
+ name="value"
31
+ type="text"
32
+ value="${data.value}"
33
+ />
34
+
35
+ <input type="hidden" name="type" value="${structure.type}" />
36
+ <input type="hidden" name="id" value="${data.id}" />
37
+ <input type="hidden" name="entryId" value="${entryId}" />
38
+ <input type="hidden" name="parentId" value="${parentId}" />
39
+ <input type="hidden" name="name" value="${name}" />
40
+ </form>
41
+ `
42
+ }
@@ -0,0 +1,4 @@
1
+ import Text from './Text.ts'
2
+ import Markdown from './Markdown.ts'
3
+
4
+ export const Field = { Text, Markdown }
@@ -135,3 +135,62 @@ export const trash = html`
135
135
  />
136
136
  </svg>
137
137
  `
138
+
139
+ export const x = html`
140
+ <svg
141
+ xmlns="http://www.w3.org/2000/svg"
142
+ width="26"
143
+ height="26"
144
+ viewBox="0 0 24 24"
145
+ >
146
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
147
+ <path
148
+ fill="none"
149
+ stroke="currentColor"
150
+ stroke-linecap="round"
151
+ stroke-linejoin="round"
152
+ stroke-width="1.5"
153
+ d="M6 18L18 6M6 6l12 12"
154
+ />
155
+ </svg>
156
+ `
157
+
158
+ export const cog = html`<svg
159
+ xmlns="http://www.w3.org/2000/svg"
160
+ width="16"
161
+ height="16"
162
+ viewBox="0 0 24 24"
163
+ >
164
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
165
+ <g
166
+ fill="none"
167
+ stroke="currentColor"
168
+ stroke-linecap="round"
169
+ stroke-linejoin="round"
170
+ stroke-width="1.5"
171
+ >
172
+ <path
173
+ d="M10.343 3.94c.09-.542.56-.94 1.11-.94h1.093c.55 0 1.02.398 1.11.94l.149.894c.07.424.384.764.78.93c.398.164.855.142 1.205-.108l.737-.527a1.125 1.125 0 0 1 1.45.12l.773.774c.39.389.44 1.002.12 1.45l-.527.737c-.25.35-.272.806-.107 1.204s.505.71.93.78l.893.15c.543.09.94.559.94 1.109v1.094c0 .55-.397 1.02-.94 1.11l-.894.149c-.424.07-.764.383-.929.78c-.165.398-.143.854.107 1.204l.527.738c.32.447.269 1.06-.12 1.45l-.774.773a1.125 1.125 0 0 1-1.449.12l-.738-.527c-.35-.25-.806-.272-1.203-.107c-.398.165-.71.505-.781.929l-.149.894c-.09.542-.56.94-1.11.94h-1.094c-.55 0-1.019-.398-1.11-.94l-.148-.894c-.071-.424-.384-.764-.781-.93c-.398-.164-.854-.142-1.204.108l-.738.527c-.447.32-1.06.269-1.45-.12l-.773-.774a1.125 1.125 0 0 1-.12-1.45l.527-.737c.25-.35.272-.806.108-1.204s-.506-.71-.93-.78l-.894-.15c-.542-.09-.94-.56-.94-1.109v-1.094c0-.55.398-1.02.94-1.11l.894-.149c.424-.07.765-.383.93-.78c.165-.398.143-.854-.108-1.204l-.526-.738a1.125 1.125 0 0 1 .12-1.45l.773-.773a1.125 1.125 0 0 1 1.45-.12l.737.527c.35.25.807.272 1.204.107s.71-.505.78-.929z"
174
+ />
175
+ <path d="M15 12a3 3 0 1 1-6 0a3 3 0 0 1 6 0" />
176
+ </g>
177
+ </svg>`
178
+
179
+ export const clipboard = html`
180
+ <svg
181
+ xmlns="http://www.w3.org/2000/svg"
182
+ width="16"
183
+ height="16"
184
+ viewBox="0 0 24 24"
185
+ >
186
+ <!-- Icon from HeroIcons by Refactoring UI Inc - https://github.com/tailwindlabs/heroicons/blob/master/LICENSE -->
187
+ <path
188
+ fill="none"
189
+ stroke="currentColor"
190
+ stroke-linecap="round"
191
+ stroke-linejoin="round"
192
+ stroke-width="1.5"
193
+ d="M15.666 3.888A2.25 2.25 0 0 0 13.5 2.25h-3c-1.03 0-1.9.693-2.166 1.638m7.332 0q.083.292.084.612v0a.75.75 0 0 1-.75.75H9a.75.75 0 0 1-.75-.75v0q.002-.32.084-.612m7.332 0q.969.073 1.927.184c1.1.128 1.907 1.077 1.907 2.185V19.5a2.25 2.25 0 0 1-2.25 2.25H6.75A2.25 2.25 0 0 1 4.5 19.5V6.257c0-1.108.806-2.057 1.907-2.185a48 48 0 0 1 1.927-.184"
194
+ />
195
+ </svg>
196
+ `
package/index.ts CHANGED
@@ -1,59 +1,73 @@
1
1
  import { Hono } from 'hono'
2
2
  import { loadDb } from '@alstar/db'
3
- import { sectionRoutes } from './api/index.ts'
4
- import { getConfig } from './utils/get-config.ts'
5
- import * as types from './types.ts'
6
-
7
- import Layout from './components/layout.ts'
8
- import IndexPage from './components/index.ts'
9
- import Entry from './components/Entry.ts'
10
-
11
3
  import { serve } from '@hono/node-server'
12
4
  import { serveStatic } from '@hono/node-server/serve-static'
5
+ import { createRefresher } from '@alstar/refresher'
6
+
7
+ import * as types from './types.ts'
13
8
  import { createStudioTables } from './utils/create-studio-tables.ts'
14
9
  import { fileBasedRouter } from './utils/file-based-router.ts'
10
+ import { getConfig } from './utils/get-config.ts'
11
+ import startupLog from './utils/startup-log.ts'
12
+ import { api } from './api/index.ts'
13
+ import mcp from './api/mcp.ts'
14
+ import path from 'path'
15
15
 
16
- export let structure: types.Structure
17
- export let rootdir = '/node_modules/@alstar/studio'
16
+ export let rootdir = './node_modules/@alstar/studio'
18
17
 
18
+ export let studioStructure: types.Structure = {}
19
19
  export let studioConfig: types.StudioConfig = {
20
20
  siteName: '',
21
+ port: 3000,
22
+ structure: {},
21
23
  }
22
24
 
23
- const createStudio = async (studioStructure?: types.Structure) => {
25
+ const createStudio = async (config: types.StudioConfig) => {
26
+ createRefresher({ rootdir: '.' })
27
+
24
28
  loadDb('./studio.db')
25
29
  createStudioTables()
26
30
 
27
- const configFile = await getConfig<types.StudioConfig>()
31
+ // const configFile = await getConfig<types.StudioConfig>()
32
+
33
+ if (config.structure) {
34
+ studioStructure = config.structure
35
+ }
28
36
 
29
- structure = studioStructure || []
30
- studioConfig = { ...studioConfig, ...configFile }
37
+ studioConfig = { ...studioConfig, ...config }
31
38
 
32
39
  const app = new Hono(studioConfig.honoConfig)
33
40
 
34
- app.use('*', serveStatic({ root: './' }))
41
+ /**
42
+ * Static folders
43
+ */
44
+ app.use('*', serveStatic({ root: path.join(rootdir, 'public') }))
35
45
  app.use('*', serveStatic({ root: './public' }))
36
46
 
37
- app.get('/admin', (c) => c.html(Layout({ structure, content: IndexPage() })))
47
+ /**
48
+ * Studio API routes
49
+ */
50
+ app.route('/admin/api', api(studioStructure))
51
+ app.route('/admin/mcp', mcp())
38
52
 
39
- app.route('/admin/api', sectionRoutes(structure))
53
+ /**
54
+ * Studio pages
55
+ */
56
+ const adminPages = await fileBasedRouter(path.join(rootdir, 'pages'))
40
57
 
41
- app.get('/admin/entry/:id', (c) => {
42
- return c.html(
43
- Layout({
44
- structure,
45
- content: Entry({ structure, entryId: c.req.param('id') }),
46
- }),
47
- )
48
- })
58
+ if (adminPages) app.route('/admin', adminPages)
49
59
 
60
+ /**
61
+ * User pages
62
+ */
50
63
  const pages = await fileBasedRouter('./pages')
51
64
 
52
- if (pages) {
53
- app.route('/', pages)
54
- }
65
+ if (pages) app.route('/', pages)
55
66
 
56
- const server = serve(app)
67
+ const server = serve({
68
+ fetch: app.fetch,
69
+ port: studioConfig.port,
70
+ })
57
71
 
58
72
  // graceful shutdown
59
73
  process.on('SIGINT', () => {
@@ -70,10 +84,18 @@ const createStudio = async (studioStructure?: types.Structure) => {
70
84
  })
71
85
  })
72
86
 
87
+ startupLog({ port: studioConfig.port || 3000 })
88
+
73
89
  return app
74
90
  }
75
91
 
76
- export { defineConfig, defineEntry, defineStructure } from './utils/define.ts'
92
+ export {
93
+ defineConfig,
94
+ defineEntry,
95
+ defineStructure,
96
+ defineBlock,
97
+ defineField,
98
+ } from './utils/define.ts'
77
99
  export { type RequestContext } from './types.ts'
78
100
  export { createStudio }
79
101
  export { html, type HtmlEscapedString } from './utils/html.ts'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alstar/studio",
3
- "version": "0.0.0-beta.5",
3
+ "version": "0.0.0-beta.7",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "dependencies": {
@@ -8,7 +8,8 @@
8
8
  "@starfederation/datastar-sdk": "1.0.0-RC.1",
9
9
  "hono": "^4.8.12",
10
10
  "@alstar/ui": "0.0.0-beta.1",
11
- "@alstar/db": "0.0.0-beta.1"
11
+ "@alstar/db": "0.0.0-beta.1",
12
+ "@alstar/refresher": "0.0.0-beta.2"
12
13
  },
13
14
  "devDependencies": {
14
15
  "@types/node": "^24.1.0",
@@ -0,0 +1,17 @@
1
+ import { html } from 'hono/html'
2
+ import { defineEntry } from '../../utils/define.ts'
3
+
4
+ import SiteLayout from '../../components/SiteLayout.ts'
5
+ import Entry from '../../components/Entry.ts'
6
+
7
+ export default defineEntry((c) => {
8
+ const id = c.req.param('id')
9
+
10
+ if (!id) {
11
+ return html`<p>Entry page url needs an ID param: "${id}"</p>`
12
+ }
13
+
14
+ return SiteLayout({
15
+ content: Entry({ entryId: parseInt(id) }),
16
+ })
17
+ })
@@ -1,5 +1,8 @@
1
1
  import { html } from 'hono/html'
2
- import { structure } from '../index.ts'
2
+ import { defineEntry } from '../utils/define.ts'
3
+
4
+ import SiteLayout from '../components/SiteLayout.ts'
5
+ import { studioStructure } from '../index.ts'
3
6
 
4
7
  const codeBlock = html`<code><span style="color: #c678dd;">await</span> <span style="color: #61aeee;">createStudio</span>([
5
8
  {
@@ -16,7 +19,7 @@ const codeBlock = html`<code><span style="color: #c678dd;">await</span> <span st
16
19
  }
17
20
  ])</code>`
18
21
 
19
- export default () => {
22
+ export default defineEntry(() => {
20
23
  const Discamer = html`
21
24
  <div class="disclamer">
22
25
  <article>
@@ -26,5 +29,5 @@ export default () => {
26
29
  </article>
27
30
  </div>`
28
31
 
29
- return html`${!structure.length ? Discamer : ''}`
30
- }
32
+ return SiteLayout({ content: !Object.values(studioStructure).length ? Discamer : '' })
33
+ })
@@ -0,0 +1,10 @@
1
+ import { defineEntry } from '../utils/define.ts'
2
+
3
+ import SiteLayout from '../components/SiteLayout.ts'
4
+ import Settings from '../components/Settings.ts'
5
+
6
+ export default defineEntry(() => {
7
+ return SiteLayout({
8
+ content: Settings(),
9
+ })
10
+ })
@@ -0,0 +1,103 @@
1
+ .admin-panel {
2
+ /* background: hsla(0, 0%, 0%, 0.1); */
3
+ padding: 40px;
4
+ margin-bottom: 0;
5
+ display: flex;
6
+ flex-direction: column;
7
+ align-items: flex-start;
8
+
9
+ height: 100%;
10
+ min-height: inherit;
11
+
12
+ min-width: 250px;
13
+
14
+ > h1 {
15
+ padding-bottom: 1rem;
16
+
17
+ a {
18
+ display: flex;
19
+ }
20
+
21
+ svg {
22
+ height: 1.6rem;
23
+ }
24
+ }
25
+
26
+ form {
27
+ padding-bottom: 1rem;
28
+
29
+ button {
30
+ margin: 10px 0px 20px;
31
+ }
32
+
33
+ p {
34
+ margin: 0;
35
+
36
+ small {
37
+ text-transform: uppercase;
38
+ letter-spacing: 0.4px;
39
+ font-weight: 600;
40
+ opacity: 0.6;
41
+ }
42
+ }
43
+ }
44
+
45
+ #entries {
46
+ width: 100%;
47
+ user-select: none;
48
+
49
+ ul {
50
+ padding: 0;
51
+ margin-inline: -1rem;
52
+
53
+ form {
54
+ display: flex;
55
+ padding-bottom: 0;
56
+
57
+ button {
58
+ margin: 0;
59
+ }
60
+ }
61
+
62
+ > li {
63
+ margin-bottom: 0px;
64
+ border-radius: 8px;
65
+ display: flex;
66
+ justify-content: space-between;
67
+ align-items: stretch;
68
+ list-style: none;
69
+ padding: 0;
70
+
71
+ a {
72
+ text-decoration: none;
73
+ width: 100%;
74
+ padding: 0.5rem 1rem;
75
+ }
76
+
77
+ button {
78
+ border-radius: 7px;
79
+ opacity: 0;
80
+ transition: opacity 100px;
81
+
82
+ svg {
83
+ margin: 0.5rem 1rem;
84
+ }
85
+ }
86
+ }
87
+ }
88
+ }
89
+
90
+ > footer {
91
+ margin-top: auto;
92
+ }
93
+ }
94
+
95
+ #entries ul {
96
+ > li:hover {
97
+ background-color: var(--pico-form-element-background-color);
98
+
99
+ button {
100
+ opacity: 1;
101
+ }
102
+ }
103
+ }