diff --git a/src/components/B4ACodeTree/B4ACodeTree.scss b/src/components/B4ACodeTree/B4ACodeTree.scss
index 5a1ec26ce6..fc618226a6 100644
--- a/src/components/B4ACodeTree/B4ACodeTree.scss
+++ b/src/components/B4ACodeTree/B4ACodeTree.scss
@@ -35,12 +35,123 @@
position: absolute;
top: $toolbar-height; // toolbar height
width: 100%;
- height: calc(100% - $toolbar-height); // toolbar height
+ min-height: calc(100% - $toolbar-height); // toolbar height
display: flex;
flex-wrap: wrap;
flex: 1;
}
+.cloudCodeSampleModal{
+ @include modalAnimation();
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ width: 70vw;
+ padding: 30px;
+ max-height: 60vh;
+ background: #253348;
+ border-radius: 0.5rem;
+ overflow: hidden;
+ transition: width 0.5s cubic-bezier(1, 0, 0, 1);
+ overflow: auto;
+ & .cloudCodeSampleModalTitle {
+ h1{
+ user-select: none
+ }
+ @include SoraFont();
+ padding-bottom: 20px;
+ display: flex;
+ justify-content: space-between;
+ & .closeIcon {
+ cursor: pointer;
+ }
+ }
+
+ & .docsLink {
+ padding-top: 20px;
+ a{
+ cursor: pointer;
+ color: $blue;
+ }
+ }
+}
+
+.codeBlockCloudSample {
+ position: relative;
+ padding-top: 0.5rem;
+ background: $regal-blue;
+ border-radius: 4px;
+
+ & .codeBlockHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: 0rem 1.5rem;
+ }
+
+ .languageLabel {
+ user-select: none;
+ }
+
+ pre {
+ background: #111214 !important;
+ border-radius: 4px;
+ padding: 1.175rem 1.5rem 1.5rem 0!important;
+ margin: 1rem 0;
+ overflow-x: auto;
+ margin: none !important;
+ border-top-left-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ font-family: 'Roboto Mono', monospace !important;
+ padding-left: 3.5em !important;
+
+ pre.line-numbers:before {
+ background: transparent !important;
+ }
+
+ code {
+ font-family: 'Roboto Mono', monospace !important;
+ font-size: 13px;
+ background: #111214 !important;
+ }
+ }
+
+ code {
+ font-family: 'Roboto Mono', monospace !important;
+ font-size: 13px;
+ background: #111214 !important;
+ }
+
+ .copyButtonCloudSample {
+ .copyTooltipCloudSample {
+ position: absolute;
+ bottom: calc(100% + 8px);
+ left: calc(100% - 110px);
+ transform: translateX(50%);
+ background: $dark-grey;
+ color: $white;
+ border-radius: 5px;
+ padding: .625rem 1rem;
+ font-size: 12px;
+ white-space: nowrap;
+ box-shadow: 0px 6px 16px 0px #0000001A;
+ animation: fadeIn 0.2s ease-in-out;
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 55%;
+ bottom: -4px;
+ transform: translateX(-50%) rotate(45deg);
+ width: 8px;
+ height: 8px;
+ background: $dark-grey;
+ }
+ }
+ }
+
+}
+
.files-box{
background-color: $dark-blue;
padding-top: 1.88rem;
diff --git a/src/components/B4ACodeTree/CloudCodeSampleModal.react.js b/src/components/B4ACodeTree/CloudCodeSampleModal.react.js
new file mode 100644
index 0000000000..f927441bbe
--- /dev/null
+++ b/src/components/B4ACodeTree/CloudCodeSampleModal.react.js
@@ -0,0 +1,221 @@
+import React, { useEffect, useRef, Suspense, lazy } from 'react';
+import ReactMarkdown from 'react-markdown';
+import Icon from 'components/Icon/Icon.react';
+import styles from 'components/B4ACodeTree/B4ACodeTree.scss';
+import Popover from 'components/Popover/Popover.react';
+import Position from 'lib/Position';
+
+const CodeBlock = lazy(() => import('components/CodeBlock/CodeBlock.react'));
+
+const getCloudCodeSample = (currentApp) => {
+ return {
+ 'js-browser': {
+ icon: 'js-icon',
+ name: 'JavaScript (Browser)',
+ iconColor: '#f7df1c',
+ blocks: [
+ {
+ title: '
Cloud Functions: Are custom functions that allow to execute logic on the backend.',
+ content: `
+~~~javascript
+Parse.Cloud.define("hello", async (request) => {
+ console.log("Hello from Cloud Code!");
+ return "Hello from Cloud Code!";
+});
+~~~`
+ },
+ {
+ title: 'Here is how you have to call it via REST API.',
+ content: String.raw`
+~~~bash
+curl -X POST \
+ -H "X-Parse-Application-Id: ${currentApp.applicationId}" \
+ -H "X-Parse-REST-API-Key: ${currentApp.restKey}" \
+ ${currentApp.serverURL}/functions/hello
+~~~`
+ },
+ {
+ title: 'This example creates an object in your class.',
+ content: `
+~~~javascript
+Parse.Cloud.define("createObject", async (request) => {
+ const b4aClass = new Parse.Object("B4aSampleClass");
+ b4aClass.set("name", request.params.name);
+ b4aClass.set("value", request.params.value);
+ await b4aClass.save(null, { useMasterKey: true });
+ return "Object created successfully!";
+});
+~~~`
+ },
+ {
+ title: '
Cloud Triggers: Is a function that automatically runs when certain events happen in your database classes. — such as when an object is saved, updated, deleted, or queried.',
+ content: `
+~~~javascript
+Parse.Cloud.beforeSave("B4aSampleClass", (request) => {
+ if (request.object.get("value") === undefined) {
+ request.object.set("value", 0);
+ }
+});
+~~~`
+ },
+ {
+ title: 'You can use the createObject function created earlier and omit the value property to see the trigger in action.',
+ content: String.raw`
+~~~bash
+curl -X POST \
+ -H "X-Parse-Application-Id: ${currentApp.applicationId}" \
+ -H "X-Parse-REST-API-Key: ${currentApp.restKey}" \
+ -H "Content-Type: application/json" \
+ -d '{"name":"b4aObject"}' \
+ ${currentApp.serverURL}/functions/createObject
+~~~`
+ },
+ {
+ title: 'Now we can retrieve the object created with this function:',
+ content: `
+~~~javascript
+Parse.Cloud.define("getObjects", async (request) => {
+ const query = new Parse.Query("B4aSampleClass");
+ const objects = await query.find({ useMasterKey: true });
+
+ return objects.map(obj => ({
+ id: obj.id,
+ name: obj.get("name"),
+ value: obj.get("value"),
+ }));
+});
+~~~`
+ },
+ {
+ title: 'Here is how you have to call it via REST API.',
+ content: String.raw`
+~~~bash
+curl -X POST \
+ -H "X-Parse-Application-Id: ${currentApp.applicationId}" \
+ -H "X-Parse-REST-API-Key: ${currentApp.restKey}" \
+ -H "Content-Type: application/json" \
+ ${currentApp.serverURL}/functions/getObjects
+~~~`
+ },
+ {
+ title: `
Cloud Jobs: Are background routines that can be scheduled or triggered to run automatically, ideal for long-running or maintenance tasks.`,
+ content: `
+~~~javascript
+Parse.Cloud.job("activeAllObjects", async (request) => {
+ const query = new Parse.Query("B4aSampleClass");
+ const objects = await query.find({ useMasterKey: true });
+
+ for (const obj of objects) {
+ obj.set("isActive", true);
+ await obj.save(null, { useMasterKey: true });
+ }
+});
+~~~`
+ },
+ {
+ title: 'Here is how you have to call it. Jobs can be only excute with the Master Key.',
+ content: String.raw`
+~~~bash
+curl -X POST \
+ -H "X-Parse-Application-Id: ${currentApp.applicationId}" \
+ -H "X-Parse-Master-Key: ${currentApp.masterKey}" \
+ ${currentApp.serverURL}/jobs/activeAllObjects
+~~~`
+ },
+ ]
+ }
+ }
+}
+
+const origin = new Position(0, 0)
+
+const CloudCodeSampleModal = ({ closeModal, currentApp }) => {
+ const sample = getCloudCodeSample(currentApp)['js-browser'];
+
+ const startRef = useRef(null);
+ const overlayRef = useRef(null);
+
+ const handlePointerDown = (e) => {
+ startRef.current = { x: e.clientX, y: e.clientY };
+ };
+
+ const handleClick = (e) => {
+ if (!overlayRef.current) return;
+
+ const dx = e.clientX - startRef.current.x;
+ const dy = e.clientY - startRef.current.y;
+ const distance = Math.sqrt(dx * dx + dy * dy);
+
+ if (distance < 5 && e.target === overlayRef.current) {
+ closeModal();
+ }
+ };
+
+ useEffect(() => {
+ const toolbar = document.querySelector('#toolbar');
+ const sidebar = document.querySelector('#sidebar');
+ const codeContainer = document.querySelector('#codeContainer');
+ const navbar = document.querySelector('nav');
+
+ if (toolbar) toolbar.style.userSelect = 'none';
+ if (sidebar) sidebar.style.userSelect = 'none';
+ if (codeContainer) codeContainer.style.userSelect = 'none';
+ if (navbar) navbar.style.userSelect = 'none'
+
+ return () => {
+ if (toolbar) toolbar.style.userSelect = '';
+ if (sidebar) sidebar.style.userSelect = '';
+ if (codeContainer) codeContainer.style.userSelect = '';
+ if (navbar) navbar.style.userSelect = '';
+ };
+ }, []);
+
+ return (
+
+
+
+
+
The examples below show you what Cloud Code looks like.
+
+
+
+
+
Loading... }>
+ {sample.blocks.map((block, i) => (
+
(
+
+ )
+ }}
+ />
+ ))}
+
+
+
+
+
+ );
+};
+
+export default CloudCodeSampleModal;
diff --git a/src/components/B4aCloudEmpty/B4aCloudEmpty.react.js b/src/components/B4aCloudEmpty/B4aCloudEmpty.react.js
new file mode 100644
index 0000000000..44088ebe65
--- /dev/null
+++ b/src/components/B4aCloudEmpty/B4aCloudEmpty.react.js
@@ -0,0 +1,130 @@
+import React, { useState, Suspense, lazy } from 'react';
+import ghostImg from './ghost.png';
+import styles from 'components/B4aCloudEmpty/B4aCloudEmpty.scss';
+
+import Icon from 'components/Icon/Icon.react';
+import ReactMarkdown from 'react-markdown';
+import Button from 'components/Button/Button.react';
+
+const LazyCloudCodeSampleModal = lazy(() => import('../B4ACodeTree/CloudCodeSampleModal.react'));
+const CodeBlock = lazy(() => import('components/CodeBlock/CodeBlock.react'));
+
+const B4aCloudEmpty = ({ imgSrc = ghostImg, dark = true, selectMainJs, currentApp, hasDeployed }) => {
+ const [openCloudCodeSample, setOpenCloudCodeSample] = useState(false);
+
+ const handleCloudCodeSample = () => {
+ if(!openCloudCodeSample) {
+ import('../B4ACodeTree/CloudCodeSampleModal.react')
+ }
+ setOpenCloudCodeSample(prev => !prev);
+ }
+
+ return (
+ <>
+
+
+
+
Cloud Code — Extend Your App Backend with JavaScript
+ Cloud Code lets you run JavaScript functions on the server, together with your app's backend. Use it for backend logic, database triggers, and integrations with external services.
+
+ { !hasDeployed && (
+ <>
+
+
How it works
+
+
+ 1
+
+
+ Write your function — Use the Cloud Code syntax. handleCloudCodeSample()} className={styles.mainJsText}>See examples →
+
+
+
+ (
+
+ ),
+ }}
+ >
+{`\`\`\`js
+Parse.Cloud.define("hello", () => {
+ return "Hello from Cloud Code!";
+});
+`}
+
+
+
+ {/*
Parse.Cloud.define("hello", () = "Hello from Cloud Code!"); */}
+
+
+
+
+ 2
+
+
Add it to selectMainJs()} className={styles.mainJsText}>main.js and Deploy — All Cloud Code must be defined in selectMainJs()} className={styles.mainJsText}>main.js . If you use other files, import them into selectMainJs()} className={styles.mainJsText}>main.js , then click Deploy.
+
+
+
+
+ 3
+
+
Call it via API or SDK — After deployment, your function is live and callable:
+
+
+ (
+
+ ),
+ }}
+ >
+{`\`\`\`bash
+curl -X POST ${currentApp.serverURL}/functions/hello \\
+ -H "X-Parse-Application-Id: ${currentApp && currentApp.applicationId ? currentApp.applicationId : 'YOUR_APP_ID'}" \\
+ -H "X-Parse-REST-API-Key: ${currentApp && currentApp.restKey ? currentApp.restKey : 'YOUR_REST_KEY'}"
+`}
+
+
+
+
+
+
+
+
+
+ selectMainJs()}
+ />
+
+ >
+ )}
+
+ handleCloudCodeSample()}>View Cloud Code examples →
+
+
+ {
+ openCloudCodeSample && (
+
+ handleCloudCodeSample()}
+ currentApp={currentApp}
+ />
+
+ )
+ }
+ >
+ )
+}
+
+export default B4aCloudEmpty;
diff --git a/src/components/B4aCloudEmpty/B4aCloudEmpty.scss b/src/components/B4aCloudEmpty/B4aCloudEmpty.scss
new file mode 100644
index 0000000000..686d704c09
--- /dev/null
+++ b/src/components/B4aCloudEmpty/B4aCloudEmpty.scss
@@ -0,0 +1,157 @@
+@import 'stylesheets/globals.scss';
+@import 'stylesheets/back4app.scss';
+
+.content {
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ justify-content: center;
+ align-items: center;
+ margin: 46px 0 0 0;
+ .titleSection{
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ margin: 15px 0 25px 0;
+ gap: 30px;
+ .title {
+ color: $white;
+ font-family: 'Sora', sans-serif;
+ font-size: 1.375rem;
+ font-weight: 600;
+ line-height: 140%;
+ text-align: center;
+ }
+ .description {
+ color: $light-blue;
+ font-family: 'Inter', sans-serif;
+ line-height: 140%;
+ text-align: center;
+ max-width: 70%;
+ }
+ }
+ .cardSection{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ max-width: 70%;
+ padding: 28px 32px;
+ border: 1px solid #34506f66;
+ border-radius: 0.375rem;
+ h1{
+ font-size: 1.375rem;
+ font-weight: 600;
+ }
+ .cardList{
+ li{
+ display: grid;
+ grid-template-columns: 32px 1fr;
+ grid-template-rows: auto 1fr;
+ column-gap: 1.175rem;
+ row-gap: 0.5rem;
+ align-items: start;
+ margin: 1.175rem 0;
+ min-width: 0;
+ .numberList{
+ display: flex;
+ font-weight: 700;
+ justify-content: center;
+ align-items: center;
+ border: 4px solid $blue;
+ height: 40px;
+ width: 40px;
+ border-radius: 100%;
+ }
+ .contentList{
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ min-width: 0;
+ }
+ .mainJsText{
+ color: $blue;
+ font-weight: 500;
+ cursor: pointer;
+ }
+ }
+ li:last-child{
+ margin-bottom: 0 !important;
+ }
+ }
+ }
+
+ .openMainButton{
+ margin: 0.375rem 0;
+ }
+
+ .viewCloudCodeSamples{
+ span{
+ padding-bottom: 1.375rem;
+ color: $blue;
+ font-weight: 500;
+ cursor: pointer;
+ }
+ }
+
+ .filesPublicButton{
+ margin-bottom: 25px;
+ }
+
+}
+
+.filesPublicButton{
+ margin: 0.375rem 0;
+}
+
+.mainJsButton{
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ height: 2.350rem;
+ padding: 0 20px;
+ font-size: 0.875rem;
+ font-weight: 600;
+ color: $white;
+ border: 1px solid $regal-blue;
+ border-radius: 8px;
+ .globeIcon{
+ stroke: #F9F9F9 !important;
+ }
+ span{
+ white-space: nowrap;
+ }
+}
+.mainJsButton:hover{
+ background-color: $dark-blue;
+ color: $white;
+}
+
+pre.line-numbers:before{
+ border-radius: 0.3rem 0 0 0;
+}
+
+@media screen and (max-width: 1280px){
+ .content{
+ .titleSection{
+ .description{
+ max-width: 90%;
+ }
+ }
+ .cardSection{
+ max-width: 90%;
+ }
+ }
+}
+
+@media screen and (max-width: 1514px){
+ .content{
+ .titleSection{
+ .description{
+ max-width: 90%;
+ }
+ }
+ .cardSection{
+ max-width: 90%;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/B4aCloudEmpty/B4aCloudPublicEmpty.react.js b/src/components/B4aCloudEmpty/B4aCloudPublicEmpty.react.js
new file mode 100644
index 0000000000..90f0799fa5
--- /dev/null
+++ b/src/components/B4aCloudEmpty/B4aCloudPublicEmpty.react.js
@@ -0,0 +1,121 @@
+import React, { useEffect, Suspense, lazy } from 'react';
+import { useParams } from 'react-router-dom';
+// import Icon from 'components/Icon/Icon.react';
+import ghostImg from './ghost.png';
+import styles from 'components/B4aCloudEmpty/B4aCloudEmpty.scss';
+import Icon from 'components/Icon/Icon.react';
+import ReactMarkdown from 'react-markdown';
+
+// Import Prism Line Numbers plugin
+import 'prismjs/plugins/line-numbers/prism-line-numbers';
+import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
+
+import 'prismjs/components/prism-markup-templating.js';
+import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-bash';
+
+// eslint-disable-next-line no-unused-vars
+import customPrisma from 'stylesheets/b4a-prisma.css';
+
+const CodeBlock = lazy(() => import('components/CodeBlock/CodeBlock.react'));
+
+const B4aCloudPublicEmpty = ({ imgSrc = ghostImg, dark = true, selectIndex, hasDeployed }) => {
+ const { appId } = useParams();
+ return (
+
+
+
+
Web Hosting — Deploy Static Sites Instantly
+ Deploy your static websites, HTML pages, JavaScript apps, and assets directly to Back4app. Your files are served globally with automatic HTTPS and custom domain support.
+
+ { !hasDeployed && (
+ <>
+
+
How it works
+
+
+ 1
+
+
+ Upload your files — Drop your HTML, CSS, JavaScript, images, and other static assets into the selectIndex()} className={styles.mainJsText}>public folder. You can organize files in subdirectories as needed.
+
+
+ (
+
+ ),
+ }}
+ >
+{`\`\`\`text
+ public/
+ ├── index.html
+ ├── login.html
+ └── styles.css`}
+
+
+
+
+
+ 2
+
+
Enable your hosting URL — After uploading files, click Deploy and enable your web hosting URL. Your site will be available instantly at a unique Back4app subdomain:
+
+
+ (
+
+ ),
+ }}
+ >
+ {`\`\`\`bash
+ https://your-app.b4a.app
+ `}
+
+
+
+
+ window.open(
+ `${b4aSettings.BACKEND_DASHBOARD_PATH}/apps/${appId}/domain-settings`,
+ '_blank'
+ )}
+ >
+
+ Enable Web Hosting
+
+
+
+
+
+
+
+ selectIndex()}
+ >
+ {/* */}
+ {'> Open index.html'}
+
+
+ >
+ )}
+
+ )
+}
+
+export default B4aCloudPublicEmpty;
diff --git a/src/components/B4aCloudEmpty/ghost.png b/src/components/B4aCloudEmpty/ghost.png
new file mode 100644
index 0000000000..c39874dd47
Binary files /dev/null and b/src/components/B4aCloudEmpty/ghost.png differ
diff --git a/src/components/CodeBlock/CodeBlock.react.js b/src/components/CodeBlock/CodeBlock.react.js
new file mode 100644
index 0000000000..6948d66eab
--- /dev/null
+++ b/src/components/CodeBlock/CodeBlock.react.js
@@ -0,0 +1,111 @@
+import React, { useEffect, useState, useRef } from 'react';
+import Prism from 'prismjs';
+import Icon from 'components/Icon/Icon.react';
+import styles from './CodeBlock.scss';
+
+// Plugins e linguagens suportadas (iguais ao original)
+import 'prismjs/plugins/line-numbers/prism-line-numbers';
+import 'prismjs/plugins/line-numbers/prism-line-numbers.css';
+
+import 'prismjs/components/prism-markup-templating.js';
+import 'prismjs/components/prism-javascript';
+import 'prismjs/components/prism-bash';
+import 'prismjs/components/prism-graphql';
+import 'prismjs/components/prism-java';
+import 'prismjs/components/prism-php';
+import 'prismjs/components/prism-dart';
+import 'prismjs/components/prism-kotlin';
+import 'prismjs/components/prism-swift';
+
+// Mantém o mesmo CSS global
+import 'stylesheets/b4a-prisma.css';
+
+const CodeBlock = ({ language, value, title, content, hasTitle = true, hideCopyButton = false, hideLineNumbers = false }) => {
+ const [copied, setCopied] = useState(false);
+ const codeRef = useRef(null);
+
+ const codeText = value ?? content ?? '';
+ const lang = language || 'javascript';
+ const isCloudSample = Boolean(title);
+
+ useEffect(() => {
+ if (typeof Prism !== 'undefined') {
+ Prism.highlightAll();
+ }
+ }, [codeText, language]);
+
+ const copyToClipboard = async () => {
+ try {
+ await navigator.clipboard.writeText(codeText.trim());
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error('Failed to copy text: ', err);
+ }
+ };
+
+ return (
+
+ { hasTitle && (
+
+ {isCloudSample ? (
+
+ ) : (
+
{lang}
+ )}
+
+ {copied && (
+
+ Copied!
+
+ )}
+
+
+
+
+
+ )}
+
+ {codeText.trim()}
+
+ { !hasTitle && !hideCopyButton && (
+
+ {copied &&
Copied!
}
+
+
+
+
+ )}
+
+ );
+};
+
+export default CodeBlock;
diff --git a/src/components/CodeBlock/CodeBlock.scss b/src/components/CodeBlock/CodeBlock.scss
new file mode 100644
index 0000000000..a1542af02d
--- /dev/null
+++ b/src/components/CodeBlock/CodeBlock.scss
@@ -0,0 +1,131 @@
+@import 'stylesheets/globals.scss';
+@import 'stylesheets/back4app.scss';
+.codeBlockHasNoTitle {
+ border-radius: 0.3rem 0 0 0.3rem !important;
+}
+
+.codeBlockContainer {
+ position: relative;
+ padding-top: 0.5rem;
+ background: $regal-blue;
+ margin-bottom: 1rem !important;
+ border-radius: 0.3rem;
+
+ & .codeBlockHeader {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin: 0rem 1.5rem 0.5rem 1.5rem;
+ }
+
+ .copyButtonWrapper {
+ position: relative;
+ display: flex;
+ align-items: center;
+ .copyTooltip {
+ position: absolute;
+ right: 50%;
+ bottom: calc(100% + 8px);
+ transform: translateX(50%);
+ background: $dark-grey;
+ color: $white;
+ border-radius: 5px;
+ padding: .625rem 1rem;
+ font-size: 12px;
+ white-space: nowrap;
+ box-shadow: 0px 6px 16px 0px #0000001A;
+ animation: fadeIn 0.2s ease-in-out;
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ bottom: -4px;
+ transform: translateX(-50%) rotate(45deg);
+ width: 8px;
+ height: 8px;
+ background: $dark-grey;
+ }
+ }
+ }
+
+
+ h1, h2, h3 {
+ margin: .8rem 0;
+ font-weight: 600;
+ }
+ h4 {
+ font-weight: 500;
+ margin-bottom: .5rem;
+ }
+
+ p {
+ margin-bottom: .5rem;
+ font-size: 14px;
+ }
+
+ .hideLineNumbers {
+ padding-left: 1rem !important;
+ border-radius: 0 0.3rem 0.3rem 0.3rem !important;
+ }
+
+ pre {
+ background: #111214 !important;
+ // padding: 0rem 1.5rem 1.5rem 0!important;
+ margin: 0 !important;
+ overflow-x: auto;
+ margin: none !important;
+ border-top-left-radius: 0 !important;
+ border-top-right-radius: 0 !important;
+ font-family: 'Roboto Mono', monospace !important;
+
+ pre.line-numbers:before {
+ background: transparent !important;
+ }
+
+ code {
+ font-family: 'Roboto Mono', monospace !important;
+ font-size: 13px;
+ background: #111214 !important;
+ }
+ }
+
+ code {
+ font-family: 'Roboto Mono', monospace !important;
+ font-size: 13px;
+ background: #111214 !important;
+ }
+}
+
+.copyButtonCloudEmpty {
+ display: flex;
+ align-items: center;
+ padding: 0 10px;
+ background: #111214 !important;
+ border-radius: 0 0.3rem 0.3rem 0;
+ .copyTooltipCloudEmpty {
+ position: absolute;
+ bottom: calc(100% + 8px);
+ left: calc(100% - 100px);
+ transform: translateX(50%);
+ background: $dark-grey;
+ color: $white;
+ border-radius: 5px;
+ padding: .625rem 1rem;
+ font-size: 12px;
+ white-space: nowrap;
+ box-shadow: 0px 6px 16px 0px #0000001A;
+ animation: fadeIn 0.2s ease-in-out;
+
+ &::after {
+ content: '';
+ position: absolute;
+ left: 55%;
+ bottom: -4px;
+ transform: translateX(-50%) rotate(45deg);
+ width: 8px;
+ height: 8px;
+ background: $dark-grey;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/components/CodeSnippet/CodeSnippet.css b/src/components/CodeSnippet/CodeSnippet.css
index 43665d7e58..60f0f588e9 100644
--- a/src/components/CodeSnippet/CodeSnippet.css
+++ b/src/components/CodeSnippet/CodeSnippet.css
@@ -168,7 +168,7 @@ pre.line-numbers:before {
}
pre.line-numbers > code {
- position: relative;
+ position: sticky !important;
top: 10px;
}
diff --git a/src/dashboard/Data/AppOverview/AppOverview.scss b/src/dashboard/Data/AppOverview/AppOverview.scss
index c5e26054d5..e100330fdc 100644
--- a/src/dashboard/Data/AppOverview/AppOverview.scss
+++ b/src/dashboard/Data/AppOverview/AppOverview.scss
@@ -1139,7 +1139,7 @@
pre {
background: #111214 !important;
border-radius: 4px;
- padding: 0rem 1.5rem 1.5rem 0!important;
+ // padding: 0rem 1.5rem 1.5rem 0!important;
margin: 1rem 0;
overflow-x: auto;
margin: none !important;
diff --git a/src/dashboard/Data/AppOverview/ConnectAppModal.react.js b/src/dashboard/Data/AppOverview/ConnectAppModal.react.js
index 7aac13eddc..5a3c7c298b 100644
--- a/src/dashboard/Data/AppOverview/ConnectAppModal.react.js
+++ b/src/dashboard/Data/AppOverview/ConnectAppModal.react.js
@@ -1,4 +1,4 @@
-import React, { useEffect, useState } from 'react';
+import React, { useEffect, useState, Suspense, lazy } from 'react';
import Popover from 'components/Popover/Popover.react';
import Position from 'lib/Position';
import styles from 'dashboard/Data/AppOverview/AppOverview.scss';
@@ -25,6 +25,7 @@ import 'prismjs/plugins/line-numbers/prism-line-numbers.css'
// eslint-disable-next-line no-unused-vars
import customPrisma from 'stylesheets/b4a-prisma.css';
+const CodeBlock = lazy(() => import('components/CodeBlock/CodeBlock.react'));
const LanguageDocMap = {
rest: {
@@ -873,52 +874,6 @@ function deleteObject($objectId) {
};
const origin = new Position(0, 0);
-const CodeBlock = ({ language, value }) => {
- const [copied, setCopied] = useState(false);
-
- useEffect(() => {
- if (typeof Prism !== 'undefined') {
- Prism.highlightAll();
- }
- }, [value, language]);
-
- const copyToClipboard = async () => {
- try {
- await navigator.clipboard.writeText(value);
- setCopied(true);
- setTimeout(() => setCopied(false), 2000);
- } catch (err) {
- console.error('Failed to copy text: ', err);
- }
- };
-
- return (
-
-
-
{language}
-
- {copied && (
-
- Copied!
-
- )}
-
-
-
-
-
-
- {value}
-
-
- );
-};
-
const ConnectAppModal = ({ closeModal }) => {
const [selectedLanguage, setSelectedLanguage] = useState(LanguageDocMap['js-browser']);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
@@ -970,12 +925,16 @@ const ConnectAppModal = ({ closeModal }) => {