@jxsuite/studio 0.19.0 → 0.21.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/dist/studio.css +98 -98
- package/dist/studio.js +13476 -12330
- package/dist/studio.js.map +152 -148
- package/dist/workers/editor.worker.js +79 -79
- package/dist/workers/json.worker.js +109 -109
- package/dist/workers/ts.worker.js +82 -82
- package/package.json +19 -7
- package/src/browse/browse-modal.js +16 -13
- package/src/browse/browse.js +44 -35
- package/src/canvas/canvas-diff.js +24 -16
- package/src/canvas/canvas-helpers.js +24 -19
- package/src/canvas/canvas-live-render.js +113 -39
- package/src/canvas/canvas-render.js +119 -73
- package/src/canvas/canvas-utils.js +51 -21
- package/src/canvas/nested-site-style.js +50 -0
- package/src/editor/component-inline-edit.js +14 -13
- package/src/editor/content-inline-edit.js +22 -10
- package/src/editor/context-menu.js +28 -14
- package/src/editor/convert-targets.js +60 -0
- package/src/editor/convert-to-component.js +20 -19
- package/src/editor/convert-to-repeater.js +226 -0
- package/src/editor/inline-edit.js +69 -28
- package/src/editor/insertion-helper.js +16 -12
- package/src/editor/shortcuts.js +14 -4
- package/src/editor/slash-menu.js +40 -19
- package/src/files/components.js +1 -1
- package/src/files/file-ops.js +37 -16
- package/src/files/files.js +322 -68
- package/src/github/github-auth.js +122 -0
- package/src/github/github-publish.js +126 -0
- package/src/markdown/md-convert.js +172 -107
- package/src/new-project/new-project-modal.js +204 -0
- package/src/panels/activity-bar.js +27 -25
- package/src/panels/ai-panel.js +406 -0
- package/src/panels/block-action-bar.js +77 -28
- package/src/panels/canvas-dnd.js +8 -2
- package/src/panels/data-explorer.js +19 -13
- package/src/panels/dnd.js +144 -92
- package/src/panels/editors.js +55 -35
- package/src/panels/elements-panel.js +33 -19
- package/src/panels/events-panel.js +15 -10
- package/src/panels/git-panel.js +475 -99
- package/src/panels/head-panel.js +121 -81
- package/src/panels/imports-panel.js +94 -65
- package/src/panels/layers-panel.js +64 -43
- package/src/panels/left-panel.js +105 -48
- package/src/panels/overlays.js +1 -0
- package/src/panels/panel-events.js +45 -30
- package/src/panels/preview-render.js +23 -18
- package/src/panels/properties-panel.js +378 -257
- package/src/panels/pseudo-preview.js +2 -2
- package/src/panels/quick-search.js +11 -9
- package/src/panels/right-panel.js +42 -16
- package/src/panels/shared.js +4 -4
- package/src/panels/signals-panel.js +415 -166
- package/src/panels/statusbar.js +10 -6
- package/src/panels/style-inputs.js +67 -35
- package/src/panels/style-panel.js +239 -134
- package/src/panels/style-utils.js +34 -16
- package/src/panels/stylebook-layers-panel.js +12 -10
- package/src/panels/stylebook-panel.js +134 -66
- package/src/panels/tab-strip.js +5 -2
- package/src/panels/toolbar.js +333 -109
- package/src/panels/welcome-screen.js +121 -0
- package/src/platform.js +2 -4
- package/src/platforms/devserver.js +113 -7
- package/src/resize-edges.js +98 -0
- package/src/services/cem-export.js +12 -7
- package/src/services/code-services.js +30 -12
- package/src/settings/content-types-editor.js +8 -8
- package/src/settings/css-vars-editor.js +30 -24
- package/src/settings/defs-editor.js +12 -7
- package/src/settings/general-settings.js +85 -3
- package/src/settings/head-editor.js +30 -24
- package/src/settings/schema-field-ui.js +58 -39
- package/src/settings/settings-modal.js +12 -9
- package/src/site-context.js +41 -31
- package/src/state.js +70 -29
- package/src/store.js +36 -35
- package/src/studio.js +120 -90
- package/src/tabs/tab.js +67 -43
- package/src/tabs/transact.js +23 -13
- package/src/ui/button-group.js +24 -24
- package/src/ui/color-selector.js +28 -23
- package/src/ui/field-row.js +3 -3
- package/src/ui/icons.js +2 -2
- package/src/ui/layers.js +31 -1
- package/src/ui/media-picker.js +7 -7
- package/src/ui/spectrum.js +5 -1
- package/src/ui/unit-selector.js +18 -16
- package/src/ui/value-selector.js +21 -15
- package/src/ui/widgets.js +22 -19
- package/src/utils/canvas-media.js +5 -4
- package/src/utils/edit-display.js +32 -26
- package/src/utils/google-fonts.js +11 -11
- package/src/utils/inherited-style.js +9 -8
- package/src/utils/studio-utils.js +18 -18
- package/src/view.js +58 -1
- package/src/workspace/workspace.js +29 -7
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GitHub OAuth Device Flow authentication for Jx Studio.
|
|
3
|
+
*
|
|
4
|
+
* Uses the Device Flow so the user authenticates in their browser without needing a server-side
|
|
5
|
+
* redirect endpoint.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { html } from "lit-html";
|
|
9
|
+
import { showDialog } from "../ui/layers.js";
|
|
10
|
+
|
|
11
|
+
const CLIENT_ID = "Ov23liYVlMFpgjOEPXJH";
|
|
12
|
+
const STORAGE_KEY = "jx_github_token";
|
|
13
|
+
|
|
14
|
+
export function getGithubToken() {
|
|
15
|
+
return localStorage.getItem(STORAGE_KEY);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function clearGithubToken() {
|
|
19
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Authenticate with GitHub via the Device Flow. Shows a dialog with the user code and polls until
|
|
24
|
+
* the user completes authorization or cancels.
|
|
25
|
+
*
|
|
26
|
+
* @returns {Promise<string | null>} The access token, or null if cancelled.
|
|
27
|
+
*/
|
|
28
|
+
export async function authenticateGithub() {
|
|
29
|
+
const existing = getGithubToken();
|
|
30
|
+
if (existing) return existing;
|
|
31
|
+
|
|
32
|
+
const codeRes = await fetch("https://github.com/login/device/code", {
|
|
33
|
+
method: "POST",
|
|
34
|
+
headers: {
|
|
35
|
+
"Content-Type": "application/json",
|
|
36
|
+
Accept: "application/json",
|
|
37
|
+
},
|
|
38
|
+
body: JSON.stringify({ client_id: CLIENT_ID, scope: "repo" }),
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (!codeRes.ok) throw new Error("Failed to initiate GitHub device flow");
|
|
42
|
+
const codeData = await codeRes.json();
|
|
43
|
+
|
|
44
|
+
const { device_code, user_code, verification_uri, interval = 5 } = codeData;
|
|
45
|
+
|
|
46
|
+
return showDialog((done) => {
|
|
47
|
+
let cancelled = false;
|
|
48
|
+
/** @type {ReturnType<typeof setTimeout> | null} */
|
|
49
|
+
let pollTimer = null;
|
|
50
|
+
|
|
51
|
+
const poll = async () => {
|
|
52
|
+
if (cancelled) return;
|
|
53
|
+
try {
|
|
54
|
+
const tokenRes = await fetch("https://github.com/login/oauth/access_token", {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Accept: "application/json",
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify({
|
|
61
|
+
client_id: CLIENT_ID,
|
|
62
|
+
device_code,
|
|
63
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
64
|
+
}),
|
|
65
|
+
});
|
|
66
|
+
const tokenData = await tokenRes.json();
|
|
67
|
+
|
|
68
|
+
if (tokenData.access_token) {
|
|
69
|
+
localStorage.setItem(STORAGE_KEY, tokenData.access_token);
|
|
70
|
+
done(tokenData.access_token);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (tokenData.error === "authorization_pending") {
|
|
75
|
+
pollTimer = setTimeout(poll, interval * 1000);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (tokenData.error === "slow_down") {
|
|
80
|
+
pollTimer = setTimeout(poll, (interval + 5) * 1000);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// expired_token or access_denied
|
|
85
|
+
done(null);
|
|
86
|
+
} catch {
|
|
87
|
+
pollTimer = setTimeout(poll, interval * 1000);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
pollTimer = setTimeout(poll, interval * 1000);
|
|
92
|
+
|
|
93
|
+
return html`
|
|
94
|
+
<sp-dialog-wrapper
|
|
95
|
+
open
|
|
96
|
+
headline="Sign in to GitHub"
|
|
97
|
+
cancel-label="Cancel"
|
|
98
|
+
@cancel=${() => {
|
|
99
|
+
cancelled = true;
|
|
100
|
+
if (pollTimer) clearTimeout(pollTimer);
|
|
101
|
+
done(null);
|
|
102
|
+
}}
|
|
103
|
+
@close=${() => {
|
|
104
|
+
cancelled = true;
|
|
105
|
+
if (pollTimer) clearTimeout(pollTimer);
|
|
106
|
+
done(null);
|
|
107
|
+
}}
|
|
108
|
+
>
|
|
109
|
+
<div class="github-auth-dialog">
|
|
110
|
+
<p>Enter this code on GitHub to authorize Jx Studio:</p>
|
|
111
|
+
<div class="github-auth-code">${user_code}</div>
|
|
112
|
+
<p>
|
|
113
|
+
<a href="${verification_uri}" target="_blank" rel="noopener"
|
|
114
|
+
>Open ${verification_uri}</a
|
|
115
|
+
>
|
|
116
|
+
</p>
|
|
117
|
+
<p class="github-auth-waiting">Waiting for authorization…</p>
|
|
118
|
+
</div>
|
|
119
|
+
</sp-dialog-wrapper>
|
|
120
|
+
`;
|
|
121
|
+
});
|
|
122
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/** Publish a local project to GitHub — creates a new repo and pushes. */
|
|
2
|
+
|
|
3
|
+
import { html } from "lit-html";
|
|
4
|
+
import { ref } from "lit-html/directives/ref.js";
|
|
5
|
+
import { showDialog } from "../ui/layers.js";
|
|
6
|
+
import { authenticateGithub } from "./github-auth.js";
|
|
7
|
+
import { getPlatform } from "../platform.js";
|
|
8
|
+
import { refreshGitStatus } from "../panels/git-panel.js";
|
|
9
|
+
import { statusMessage } from "../panels/statusbar.js";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Full "Publish to GitHub" flow: 1. Authenticate (or reuse stored token) 2. Prompt for repo name /
|
|
13
|
+
* visibility 3. Create the repo via GitHub API 4. Add remote + push
|
|
14
|
+
*
|
|
15
|
+
* @param {{ projectName: string }} opts
|
|
16
|
+
* @returns {Promise<boolean>} True if published successfully
|
|
17
|
+
*/
|
|
18
|
+
export async function publishToGithub({ projectName }) {
|
|
19
|
+
const token = await authenticateGithub();
|
|
20
|
+
if (!token) return false;
|
|
21
|
+
|
|
22
|
+
const repoOpts = await showDialog((done) => {
|
|
23
|
+
/** @type {HTMLInputElement | null} */
|
|
24
|
+
let _nameInput = null;
|
|
25
|
+
/** @type {HTMLInputElement | null} */
|
|
26
|
+
let _descInput = null;
|
|
27
|
+
/** @type {HTMLInputElement | null} */
|
|
28
|
+
let _privateToggle = null;
|
|
29
|
+
|
|
30
|
+
return html`
|
|
31
|
+
<sp-dialog-wrapper
|
|
32
|
+
open
|
|
33
|
+
headline="Publish to GitHub"
|
|
34
|
+
confirm-label="Create Repository"
|
|
35
|
+
cancel-label="Cancel"
|
|
36
|
+
@confirm=${() => {
|
|
37
|
+
done({
|
|
38
|
+
name: _nameInput?.value || projectName,
|
|
39
|
+
description: _descInput?.value || "",
|
|
40
|
+
isPrivate: _privateToggle?.checked ?? true,
|
|
41
|
+
});
|
|
42
|
+
}}
|
|
43
|
+
@cancel=${() => done(null)}
|
|
44
|
+
@close=${() => done(null)}
|
|
45
|
+
>
|
|
46
|
+
<div class="github-publish-dialog">
|
|
47
|
+
<sp-field-label for="repo-name">Repository name</sp-field-label>
|
|
48
|
+
<sp-textfield
|
|
49
|
+
id="repo-name"
|
|
50
|
+
name="repo-name"
|
|
51
|
+
value="${projectName}"
|
|
52
|
+
placeholder="my-project"
|
|
53
|
+
${ref((el) => {
|
|
54
|
+
_nameInput = /** @type {HTMLInputElement | null} */ (el || null);
|
|
55
|
+
})}
|
|
56
|
+
></sp-textfield>
|
|
57
|
+
|
|
58
|
+
<sp-field-label for="repo-desc">Description (optional)</sp-field-label>
|
|
59
|
+
<sp-textfield
|
|
60
|
+
id="repo-desc"
|
|
61
|
+
name="repo-desc"
|
|
62
|
+
placeholder="A brief description"
|
|
63
|
+
${ref((el) => {
|
|
64
|
+
_descInput = /** @type {HTMLInputElement | null} */ (el || null);
|
|
65
|
+
})}
|
|
66
|
+
></sp-textfield>
|
|
67
|
+
|
|
68
|
+
<sp-field-label>Visibility</sp-field-label>
|
|
69
|
+
<sp-switch
|
|
70
|
+
name="repo-private"
|
|
71
|
+
checked
|
|
72
|
+
${ref((el) => {
|
|
73
|
+
_privateToggle = /** @type {HTMLInputElement | null} */ (el || null);
|
|
74
|
+
})}
|
|
75
|
+
>Private repository</sp-switch
|
|
76
|
+
>
|
|
77
|
+
</div>
|
|
78
|
+
</sp-dialog-wrapper>
|
|
79
|
+
`;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (!repoOpts) return false;
|
|
83
|
+
|
|
84
|
+
statusMessage("Creating GitHub repository…");
|
|
85
|
+
|
|
86
|
+
const createRes = await fetch("https://api.github.com/user/repos", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
headers: {
|
|
89
|
+
Authorization: `Bearer ${token}`,
|
|
90
|
+
Accept: "application/vnd.github+json",
|
|
91
|
+
"Content-Type": "application/json",
|
|
92
|
+
},
|
|
93
|
+
body: JSON.stringify({
|
|
94
|
+
name: repoOpts.name,
|
|
95
|
+
description: repoOpts.description,
|
|
96
|
+
private: repoOpts.isPrivate,
|
|
97
|
+
auto_init: false,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (!createRes.ok) {
|
|
102
|
+
const err = await createRes.json();
|
|
103
|
+
const msg = err.errors?.[0]?.message || err.message || "Failed to create repository";
|
|
104
|
+
statusMessage(`Error: ${msg}`);
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const repo = await createRes.json();
|
|
109
|
+
const platform = getPlatform();
|
|
110
|
+
|
|
111
|
+
statusMessage("Setting remote and pushing…");
|
|
112
|
+
|
|
113
|
+
await platform.gitAddRemote("origin", repo.clone_url);
|
|
114
|
+
|
|
115
|
+
statusMessage("Pushing to GitHub…");
|
|
116
|
+
try {
|
|
117
|
+
await platform.gitPush({ setUpstream: true });
|
|
118
|
+
} catch (/** @type {unknown} */ e) {
|
|
119
|
+
statusMessage(`Push failed: ${/** @type {Error} */ (e).message}`);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await refreshGitStatus();
|
|
124
|
+
statusMessage(`Published to GitHub: ${repo.html_url}`);
|
|
125
|
+
return true;
|
|
126
|
+
}
|