snapshot of https://github.com/RocketChat/Rocket.Chat.git (develop, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
Some checks failed
Create issues from TODOs / todos (push) Failing after 3s
Close inactive issues / close-issues (push) Has been cancelled
Deploy GitHub Pages / deploy-preview (push) Has been cancelled
CI / ⚙️ Variables Setup (push) Has been cancelled
CI / ⚙️ Test Guard (push) Has been cancelled
CI / 🚀 Notify external services - draft (push) Has been cancelled
CI / 📦 Build Packages (push) Has been cancelled
CI / 📦 Meteor Build (coverage) (push) Has been cancelled
CI / 📦 Meteor Build (production) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [account-service presence-service omnichannel-transcript-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [authorization-service queue-worker-service ddp-streamer-service], fips) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Build Docker (amd64, [rocketchat], fips) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [account-service presence-service omnichannel-transcript-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [authorization-service queue-worker-service ddp-streamer-service], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], ${{ (github.event_name != 'release' && github.ref != 'refs/heads/develop') && 'coverage' || 'production' }}) (push) Has been cancelled
CI / 🚢 Build Docker (arm64, [rocketchat], coverage) (push) Has been cancelled
CI / 🚢 Publish Docker Images (ghcr.io) (push) Has been cancelled
CI / 📦 Track Image Sizes (push) Has been cancelled
CI / 🔎 Code Check - Action Lint (push) Has been cancelled
CI / 🔎 Code Check (push) Has been cancelled
CI / 🔨 Test Storybook (push) Has been cancelled
CI / 🔨 Test Unit (push) Has been cancelled
CI / 🔨 Test API (CE) (push) Has been cancelled
CI / 🔨 Test API Livechat (CE) (push) Has been cancelled
CI / 🔨 Test UI (CE) (push) Has been cancelled
CI / 🔨 Test API (EE) (push) Has been cancelled
CI / 🔨 Test API Livechat (EE) (push) Has been cancelled
CI / 🔨 Test API Apps (deno-runtime - EE) (push) Has been cancelled
CI / 🔨 Test UI (EE) (push) Has been cancelled
CI / 🔨 Test API (FIPS) (push) Has been cancelled
CI / 🔨 Test API Livechat (FIPS) (push) Has been cancelled
CI / 🔨 Test UI (FIPS) (push) Has been cancelled
CI / 🔨 Test Federation Matrix (push) Has been cancelled
CI / 📊 Report Coverage (push) Has been cancelled
CI / ✅ Tests Done (push) Has been cancelled
CI / 🚀 Publish build assets (push) Has been cancelled
CI / 🚀 Publish Docker Images (DockerHub) (push) Has been cancelled
CI / 🚀 Notify external services (push) Has been cancelled
CI / Update Version Durability (push) Has been cancelled
Release / Release (push) Has been cancelled
Auto-close duplicate issues / auto-close-duplicates (push) Failing after 1s
Code scanning - action / CodeQL-Build (push) Failing after 1s
This commit is contained in:
24
apps/uikit-playground/.gitignore
vendored
Normal file
24
apps/uikit-playground/.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
3226
apps/uikit-playground/CHANGELOG.md
Normal file
3226
apps/uikit-playground/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
13
apps/uikit-playground/index.html
Normal file
13
apps/uikit-playground/index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>UiKit-Playground</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
56
apps/uikit-playground/package.json
Normal file
56
apps/uikit-playground/package.json
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"name": "@rocket.chat/uikit-playground",
|
||||
"version": "0.7.16",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
".:build-preview-move": "mkdir -p ../../.preview/ && cp -r ./dist ../../.preview/uikit-playground",
|
||||
"build-preview": "tsc && vite build",
|
||||
"dev": "vite",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codemirror/lang-javascript": "^6.2.5",
|
||||
"@codemirror/lang-json": "^6.0.2",
|
||||
"@hello-pangea/dnd": "^17.0.0",
|
||||
"@lezer/highlight": "^1.2.3",
|
||||
"@rocket.chat/core-typings": "workspace:^",
|
||||
"@rocket.chat/css-in-js": "^0.33.1",
|
||||
"@rocket.chat/fuselage": "^0.89.0",
|
||||
"@rocket.chat/fuselage-hooks": "^0.43.1",
|
||||
"@rocket.chat/fuselage-toastbar": "~0.36.1",
|
||||
"@rocket.chat/fuselage-tokens": "^0.34.0",
|
||||
"@rocket.chat/fuselage-ui-kit": "workspace:~",
|
||||
"@rocket.chat/icons": "^0.49.0",
|
||||
"@rocket.chat/logo": "^0.33.2",
|
||||
"@rocket.chat/styled": "~0.34.1",
|
||||
"@rocket.chat/ui-avatar": "workspace:^",
|
||||
"@rocket.chat/ui-contexts": "workspace:~",
|
||||
"codemirror": "^6.0.2",
|
||||
"eslint4b-prebuilt": "^6.7.2",
|
||||
"prettier": "~3.9.6",
|
||||
"rc-scrollbars": "^1.1.6",
|
||||
"react": "~19.2.8",
|
||||
"react-dom": "~19.2.8",
|
||||
"react-router-dom": "^6.30.6",
|
||||
"react-split-pane": "^3.2.0",
|
||||
"react-virtuoso": "~4.18.12",
|
||||
"reactflow": "^11.11.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rocket.chat/emitter": "workspace:~",
|
||||
"@rocket.chat/tsconfig": "workspace:*",
|
||||
"@types/lodash": "~4.17.25",
|
||||
"@types/react": "~19.2.18",
|
||||
"@types/react-dom": "~19.2.5",
|
||||
"@vitejs/plugin-react": "~6.0.5",
|
||||
"eslint": "~9.39.5",
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^8.0.16"
|
||||
},
|
||||
"volta": {
|
||||
"extends": "../../package.json"
|
||||
}
|
||||
}
|
||||
1
apps/uikit-playground/postcss.config.js
Normal file
1
apps/uikit-playground/postcss.config.js
Normal file
@@ -0,0 +1 @@
|
||||
export default {};
|
||||
1
apps/uikit-playground/public/vite.svg
Normal file
1
apps/uikit-playground/public/vite.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
8
apps/uikit-playground/src/App.css
Normal file
8
apps/uikit-playground/src/App.css
Normal file
@@ -0,0 +1,8 @@
|
||||
.App {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#toastBarRoot{
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
}
|
||||
58
apps/uikit-playground/src/App.tsx
Normal file
58
apps/uikit-playground/src/App.tsx
Normal file
@@ -0,0 +1,58 @@
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useMediaQueries } from '@rocket.chat/fuselage-hooks';
|
||||
import { ToastBarProvider } from '@rocket.chat/fuselage-toastbar';
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { BrowserRouter, Route, Routes } from 'react-router-dom';
|
||||
|
||||
import { HomeLayout } from './Components/Routes/HomeLayout';
|
||||
import { ProjectSpecificLayout } from './Components/Routes/ProjectSpecificLayout';
|
||||
import { context, isMobileAction, isTabletAction } from './Context';
|
||||
import FlowDiagram from './Pages/FlowDiagram';
|
||||
import Home from './Pages/Home';
|
||||
import Playground from './Pages/Playground';
|
||||
import Prototype from './Pages/Prototype';
|
||||
import SignInToWorkspace from './Pages/SignInSignUp';
|
||||
import routes from './Routes/Routes';
|
||||
|
||||
import './App.css';
|
||||
import './_global.css';
|
||||
import './cssVariables.css';
|
||||
|
||||
function App() {
|
||||
const { dispatch } = useContext(context);
|
||||
|
||||
const [isMobile, isTablet] = useMediaQueries('(max-width: 630px)', '(max-width: 1050px)');
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(isMobileAction(isMobile));
|
||||
}, [isMobile, dispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(isTabletAction(isTablet));
|
||||
}, [isTablet, dispatch]);
|
||||
return (
|
||||
<Box width='100vw' height='100vh' display='flex' flexDirection='column'>
|
||||
<ToastBarProvider>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route element={<HomeLayout />}>
|
||||
<Route path={routes.login} element={<SignInToWorkspace route={routes.login} />} />
|
||||
<Route path={routes.signup} element={<SignInToWorkspace route={routes.signup} />} />
|
||||
</Route>
|
||||
{/* <Route element={<ProtectedLayout />}> */}
|
||||
<Route path={routes.home} element={<Home />} />
|
||||
<Route path={routes.projectId} element={<ProjectSpecificLayout />}>
|
||||
<Route path={routes.flow} element={<FlowDiagram />} />
|
||||
<Route path={routes.project} element={<Playground />} />
|
||||
<Route path={routes.prototype} element={<Prototype />} />
|
||||
</Route>
|
||||
<Route path='*' element={<Home />} />
|
||||
{/* </Route> */}
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</ToastBarProvider>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,55 @@
|
||||
/* eslint-disable react-hooks/exhaustive-deps */
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
|
||||
import { useEffect, useContext } from 'react';
|
||||
|
||||
import { updatePayloadAction, context } from '../../Context';
|
||||
import type { IPayload } from '../../Context/initialState';
|
||||
import useCodeMirror from '../../hooks/useCodeMirror';
|
||||
import useFormatCodeMirrorValue from '../../hooks/useFormatCodeMirrorValue';
|
||||
import intendCode from '../../utils/intendCode';
|
||||
|
||||
type CodeMirrorProps = {
|
||||
extensions?: Extension[];
|
||||
};
|
||||
|
||||
const BlockEditor = ({ extensions }: CodeMirrorProps) => {
|
||||
const {
|
||||
state: { screens, activeScreen },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
|
||||
const { editor, changes, setValue } = useCodeMirror(extensions, intendCode(screens[activeScreen]?.payload));
|
||||
const debounceValue = useDebouncedValue(changes, 1500);
|
||||
|
||||
useFormatCodeMirrorValue((parsedCode: IPayload, prettifiedCode: { formatted: string; cursorOffset: number }) => {
|
||||
dispatch(
|
||||
updatePayloadAction({
|
||||
blocks: parsedCode.blocks,
|
||||
surface: parsedCode.surface,
|
||||
}),
|
||||
);
|
||||
setValue(prettifiedCode.formatted, {
|
||||
cursor: prettifiedCode.cursorOffset,
|
||||
});
|
||||
}, debounceValue);
|
||||
|
||||
useEffect(() => {
|
||||
if (!screens[activeScreen]?.changedByEditor) {
|
||||
setValue(intendCode(screens[activeScreen]?.payload), {});
|
||||
}
|
||||
}, [screens[activeScreen]?.payload.blocks, screens[activeScreen]?.payload.surface, activeScreen]);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(intendCode(screens[activeScreen]?.payload), {});
|
||||
}, [activeScreen]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display='grid' height='100%' width='100%' ref={editor} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default BlockEditor;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { json } from '@codemirror/lang-json';
|
||||
import { EditorView } from 'codemirror';
|
||||
|
||||
import highlightStyle from './HighlightStyle';
|
||||
import basicSetup from './basicSetup';
|
||||
import jsonLinter from './jsonLinter';
|
||||
// import payloadLinter from './payloadLinter';
|
||||
import theme from './theme';
|
||||
|
||||
export const actionBlockExtensions = [
|
||||
highlightStyle,
|
||||
json(),
|
||||
jsonLinter,
|
||||
basicSetup,
|
||||
// payloadLinter,
|
||||
...theme,
|
||||
];
|
||||
|
||||
export const actionPreviewExtensions = [
|
||||
EditorView.contentAttributes.of({ contenteditable: 'false' }),
|
||||
highlightStyle,
|
||||
json(),
|
||||
basicSetup,
|
||||
...theme,
|
||||
];
|
||||
@@ -0,0 +1,15 @@
|
||||
import { HighlightStyle, syntaxHighlighting } from '@codemirror/language';
|
||||
import { tags as t } from '@lezer/highlight';
|
||||
|
||||
const highLightStyle = () => {
|
||||
const style = HighlightStyle.define([
|
||||
{ tag: t.literal, color: 'var(--RCPG-primary-color)' },
|
||||
{ tag: t.bool, color: 'var(--RCPG-tertary-color)' },
|
||||
{ tag: t.number, color: 'var(--RCPG-secondary-color)' },
|
||||
{ tag: t.null, color: 'var(--RCPG-tertary-color)' },
|
||||
]);
|
||||
|
||||
return syntaxHighlighting(style);
|
||||
};
|
||||
|
||||
export default highLightStyle();
|
||||
@@ -0,0 +1,35 @@
|
||||
import { completionKeymap, closeBrackets, closeBracketsKeymap } from '@codemirror/autocomplete';
|
||||
import { defaultKeymap, history, historyKeymap, indentWithTab } from '@codemirror/commands';
|
||||
import { defaultHighlightStyle, syntaxHighlighting, indentOnInput, bracketMatching, foldGutter, foldKeymap } from '@codemirror/language';
|
||||
import { lintKeymap } from '@codemirror/lint';
|
||||
import { searchKeymap, highlightSelectionMatches } from '@codemirror/search';
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { keymap, drawSelection, dropCursor, rectangularSelection, crosshairCursor, lineNumbers, EditorView } from '@codemirror/view';
|
||||
|
||||
const basicSetup: Extension = (() => [
|
||||
lineNumbers(),
|
||||
history(),
|
||||
foldGutter(),
|
||||
drawSelection(),
|
||||
dropCursor(),
|
||||
indentOnInput(),
|
||||
EditorView.lineWrapping,
|
||||
syntaxHighlighting(defaultHighlightStyle, { fallback: true }),
|
||||
bracketMatching(),
|
||||
closeBrackets(),
|
||||
rectangularSelection(),
|
||||
crosshairCursor(),
|
||||
highlightSelectionMatches(),
|
||||
keymap.of([
|
||||
...closeBracketsKeymap,
|
||||
...defaultKeymap,
|
||||
...searchKeymap,
|
||||
...historyKeymap,
|
||||
...foldKeymap,
|
||||
...completionKeymap,
|
||||
...lintKeymap,
|
||||
indentWithTab,
|
||||
]),
|
||||
])();
|
||||
|
||||
export default basicSetup;
|
||||
@@ -0,0 +1,9 @@
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
|
||||
import highlightStyle from './HighlightStyle';
|
||||
import basicSetup from './basicSetup';
|
||||
import theme from './theme';
|
||||
|
||||
const extensions = [highlightStyle, javascript(), basicSetup, ...theme];
|
||||
|
||||
export default extensions;
|
||||
@@ -0,0 +1,4 @@
|
||||
import { jsonParseLinter } from '@codemirror/lang-json';
|
||||
import { lintGutter, linter } from '@codemirror/lint';
|
||||
|
||||
export default [lintGutter(), linter(jsonParseLinter())];
|
||||
@@ -0,0 +1,60 @@
|
||||
import { syntaxTree } from '@codemirror/language';
|
||||
import type { Diagnostic } from '@codemirror/lint';
|
||||
import { linter } from '@codemirror/lint';
|
||||
import type { EditorView } from 'codemirror';
|
||||
|
||||
import parsePayload from '../Parser';
|
||||
|
||||
const payloadLinter = linter((view: EditorView) => {
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const tree = syntaxTree(view.state);
|
||||
let head = tree.topNode.firstChild;
|
||||
if (!head?.matchContext(['Script'])) {
|
||||
diagnostics.push({
|
||||
from: 0,
|
||||
to: 0,
|
||||
message: 'Expecting a Script',
|
||||
severity: 'error',
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
head = head.firstChild;
|
||||
if (!head?.matchContext(['ExpressionStatement'])) {
|
||||
diagnostics.push({
|
||||
from: 0,
|
||||
to: 0,
|
||||
message: 'Expecting an expression statement',
|
||||
severity: 'error',
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
head = head.firstChild;
|
||||
if (!head?.matchContext(['ArrayExpression'])) {
|
||||
diagnostics.push({
|
||||
from: 0,
|
||||
to: 0,
|
||||
message: 'Expecting an array expression',
|
||||
severity: 'error',
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
// while (head.nextSibling && head.nextSibling.name !== ']') {
|
||||
// }
|
||||
do {
|
||||
if (head.name !== '[' && head.name !== ',' && head.name !== ']' && head.name !== 'ObjectExpression') {
|
||||
diagnostics.push({
|
||||
from: head.from,
|
||||
to: head.to,
|
||||
message: 'Expecting an Object expression',
|
||||
severity: 'error',
|
||||
});
|
||||
return diagnostics;
|
||||
}
|
||||
if (head.name === 'ObjectExpression') parsePayload(head, view);
|
||||
head = head.nextSibling;
|
||||
} while (head);
|
||||
|
||||
return diagnostics;
|
||||
});
|
||||
|
||||
export default payloadLinter;
|
||||
@@ -0,0 +1,41 @@
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { EditorView } from '@codemirror/view';
|
||||
|
||||
const gutters: Extension = EditorView.theme({
|
||||
'.cm-gutters': {
|
||||
backgroundColor: 'transparent',
|
||||
border: 'none',
|
||||
userSelect: 'none',
|
||||
minWidth: '32px',
|
||||
display: 'flex',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
|
||||
'.cm-activeLineGutter': {
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
});
|
||||
|
||||
const selection: Extension = EditorView.theme({
|
||||
'.cm-selectionBackground': {
|
||||
backgroundColor: 'var(--RCPG-secondary-color) !important',
|
||||
opacity: 0.3,
|
||||
},
|
||||
|
||||
'.cm-selectionMatch': {
|
||||
backgroundColor: '#74808930 !important',
|
||||
},
|
||||
|
||||
'.cm-matchingBracket': {
|
||||
backgroundColor: 'transparent !important',
|
||||
border: '1px solid #1d74f580',
|
||||
},
|
||||
});
|
||||
|
||||
const line: Extension = EditorView.theme({
|
||||
'.cm-activeLine': {
|
||||
backgroundColor: 'transparent !important',
|
||||
},
|
||||
});
|
||||
|
||||
export default [gutters, selection, line] as const;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './parsePayload';
|
||||
@@ -0,0 +1,7 @@
|
||||
import type { EditorView } from 'codemirror';
|
||||
|
||||
const parsePayload = (head: { from: number; to: number }, view: EditorView) => {
|
||||
JSON.parse(view.state.doc.toString().slice(head.from, head.to));
|
||||
};
|
||||
|
||||
export default parsePayload;
|
||||
@@ -0,0 +1,31 @@
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useEffect, useContext } from 'react';
|
||||
|
||||
import { context } from '../../Context';
|
||||
import useCodeMirror from '../../hooks/useCodeMirror';
|
||||
import intendCode from '../../utils/intendCode';
|
||||
|
||||
type CodeMirrorProps = {
|
||||
extensions?: Extension[];
|
||||
};
|
||||
|
||||
const PreviewEditor = ({ extensions }: CodeMirrorProps) => {
|
||||
const {
|
||||
state: { screens, activeScreen },
|
||||
} = useContext(context);
|
||||
const { editor, setValue } = useCodeMirror(extensions, intendCode(screens[activeScreen]?.actionPreview));
|
||||
|
||||
useEffect(() => {
|
||||
setValue(intendCode(screens[activeScreen]?.actionPreview), {});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [screens[activeScreen]?.actionPreview]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display='grid' height='100%' width='100%' ref={editor} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default PreviewEditor;
|
||||
68
apps/uikit-playground/src/Components/CodeEditor/index.tsx
Normal file
68
apps/uikit-playground/src/Components/CodeEditor/index.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { Extension } from '@codemirror/state';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useDebouncedValue } from '@rocket.chat/fuselage-hooks';
|
||||
import json5 from 'json5';
|
||||
import { useEffect, useContext } from 'react';
|
||||
|
||||
import { updatePayloadAction, context } from '../../Context';
|
||||
import type { ILayoutBlock } from '../../Context/initialState';
|
||||
import useCodeMirror from '../../hooks/useCodeMirror';
|
||||
import codePrettier from '../../utils/codePrettier';
|
||||
|
||||
type CodeMirrorProps = {
|
||||
extensions?: Extension[];
|
||||
};
|
||||
|
||||
const CodeEditor = ({ extensions }: CodeMirrorProps) => {
|
||||
const {
|
||||
state: { screens, activeScreen },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
const { editor, changes, setValue } = useCodeMirror(extensions, json5.stringify(screens[activeScreen].payload, undefined, 4));
|
||||
const debounceValue = useDebouncedValue(changes?.value, 1500);
|
||||
|
||||
useEffect(() => {
|
||||
if (!changes?.isDispatch) {
|
||||
try {
|
||||
const parsedCode: ILayoutBlock[] = json5.parse(changes.value);
|
||||
dispatch(
|
||||
updatePayloadAction({
|
||||
blocks: parsedCode,
|
||||
changedByEditor: false,
|
||||
}),
|
||||
);
|
||||
|
||||
dispatch(updatePayloadAction({ blocks: parsedCode }));
|
||||
} catch (e) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [changes?.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!changes?.isDispatch) {
|
||||
void codePrettier(changes.value, changes.cursor || 0).then((prettierCode) => {
|
||||
setValue(prettierCode.formatted, {
|
||||
cursor: prettierCode.cursorOffset,
|
||||
});
|
||||
});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [debounceValue]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!screens[activeScreen].changedByEditor) {
|
||||
setValue(JSON.stringify(screens[activeScreen].payload, undefined, 4), {});
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [screens[activeScreen].payload]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display='grid' height='100%' width='100%' ref={editor} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CodeEditor;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Scrollable, Box } from '@rocket.chat/fuselage';
|
||||
|
||||
import BlocksTree from '../../Payload/actionBlock/BlocksTree';
|
||||
import DropDown from '../DropDown';
|
||||
|
||||
const ScrollableSideBar = () => (
|
||||
<Scrollable vertical>
|
||||
<Box
|
||||
width='100%'
|
||||
marginBlockStart='39px'
|
||||
borderBlockStart='var(--default-border)'
|
||||
backgroundColor='var(--primaryBackgroundColor)'
|
||||
className={css`
|
||||
box-shadow: var(--dropdown-box-shadow);
|
||||
`}
|
||||
>
|
||||
<DropDown blocksTree={BlocksTree} />
|
||||
</Box>
|
||||
</Scrollable>
|
||||
);
|
||||
|
||||
export default ScrollableSideBar;
|
||||
@@ -0,0 +1,37 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useEffect, useContext } from 'react';
|
||||
|
||||
import ScrollableSideBar from './ScrollableSideBar';
|
||||
import SliderBtn from './SliderBtn';
|
||||
import { context, sidebarToggleAction } from '../../Context';
|
||||
|
||||
const SideBar = () => {
|
||||
const { state, dispatch } = useContext(context);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(sidebarToggleAction(false));
|
||||
}, [state?.isMobile, dispatch]);
|
||||
|
||||
const slide = state?.isMobile
|
||||
? css`
|
||||
width: 100%;
|
||||
user-select: none;
|
||||
transform: translateX(${state?.sideBarToggle ? '0' : '-100%'});
|
||||
transition: var(--animation-default);
|
||||
`
|
||||
: css`
|
||||
width: var(--sidebar-width);
|
||||
user-select: none;
|
||||
transition: var(--animation-default);
|
||||
`;
|
||||
|
||||
return (
|
||||
<Box position='absolute' height='100%' display='flex' zIndex={4} backgroundColor='#fff' className={[slide]}>
|
||||
<SliderBtn />
|
||||
<ScrollableSideBar />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SideBar;
|
||||
@@ -0,0 +1,90 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box, Label } from '@rocket.chat/fuselage';
|
||||
import { useContext, useMemo } from 'react';
|
||||
|
||||
import { context, sidebarToggleAction } from '../../Context';
|
||||
|
||||
const SliderBtn = () => {
|
||||
const {
|
||||
state: { sideBarToggle, isMobile },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
const slideBtnAnimation = sideBarToggle
|
||||
? css`
|
||||
clip-path: polygon(10% 0, 50% 40%, 90% 0, 100% 10%, 60% 50%, 100% 90%, 90% 100%, 50% 60%, 10% 100%, 0 90%, 40% 50%, 0 10%);
|
||||
cursor: pointer;
|
||||
transition: var(--animation-default);
|
||||
`
|
||||
: css`
|
||||
clip-path: polygon(32% 35%, 32% 35%, 79% 0, 87% 10%, 32% 50%, 87% 90%, 79% 100%, 32% 64%, 32% 65%, 13% 50%, 13% 50%, 13% 50%);
|
||||
transform: rotate(180deg);
|
||||
transition: var(--animation-default);
|
||||
`;
|
||||
|
||||
const toggleStyle = useMemo(() => {
|
||||
if (!isMobile) {
|
||||
return css`
|
||||
left: 0px;
|
||||
`;
|
||||
}
|
||||
|
||||
if (sideBarToggle) {
|
||||
return css`
|
||||
right: 0;
|
||||
transition: var(--animation-default);
|
||||
`;
|
||||
}
|
||||
|
||||
return css`
|
||||
right: 0;
|
||||
transform: translateX(100%);
|
||||
cursor: pointer;
|
||||
transition: var(--animation-default);
|
||||
`;
|
||||
}, [isMobile, sideBarToggle]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
position='absolute'
|
||||
width={sideBarToggle ? '100%' : '130px'}
|
||||
paddingInlineStart='20px'
|
||||
height='40px'
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
onClick={() => !sideBarToggle && dispatch(sidebarToggleAction(!sideBarToggle))}
|
||||
zIndex={1}
|
||||
className={toggleStyle}
|
||||
>
|
||||
<Label
|
||||
width='80px'
|
||||
className={
|
||||
isMobile && !sideBarToggle
|
||||
? css`
|
||||
cursor: pointer;
|
||||
`
|
||||
: ''
|
||||
}
|
||||
>
|
||||
Components
|
||||
</Label>
|
||||
{isMobile && (
|
||||
<Box
|
||||
width='40px'
|
||||
height='40px'
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
justifyContent='center'
|
||||
onClick={() => sideBarToggle && dispatch(sidebarToggleAction(!sideBarToggle))}
|
||||
className={css`
|
||||
cursor: pointer;
|
||||
`}
|
||||
>
|
||||
<Box size='50%' backgroundColor='info' className={slideBtnAnimation} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SliderBtn;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './SideBar';
|
||||
@@ -0,0 +1,103 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box, Button, Icon, Scrollable } from '@rocket.chat/fuselage';
|
||||
import { useOutsideClick, useMergedRefs } from '@rocket.chat/fuselage-hooks';
|
||||
import { useContext, useRef } from 'react';
|
||||
|
||||
import ScreenThumbnail from './ScreenThumbnail';
|
||||
import { context } from '../../Context';
|
||||
import { openCreateNewScreenAction, createNewScreenAction } from '../../Context/action';
|
||||
import { useHorizontalScroll } from '../../hooks/useHorizontalScroll';
|
||||
|
||||
const CreateNewScreenContainer = () => {
|
||||
const {
|
||||
state: { projects, screens, activeProject, openCreateNewScreen },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
const ref = useRef(null);
|
||||
|
||||
const onClosehandler = () => {
|
||||
dispatch(openCreateNewScreenAction(false));
|
||||
};
|
||||
useOutsideClick([ref], onClosehandler);
|
||||
|
||||
const scrollRef = useHorizontalScroll();
|
||||
|
||||
const mergedRef = useMergedRefs(scrollRef, ref);
|
||||
const createNewScreenhandler = () => {
|
||||
dispatch(createNewScreenAction());
|
||||
};
|
||||
|
||||
return (
|
||||
<Scrollable horizontal>
|
||||
<Box
|
||||
ref={mergedRef}
|
||||
width='100%'
|
||||
height='250px'
|
||||
borderBlockEnd='var(--default-border)'
|
||||
position='fixed'
|
||||
paddingInline='40px'
|
||||
className={css`
|
||||
top: -255px;
|
||||
left: 0;
|
||||
z-index: 10;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
background-color: var(--primaryBackgroundColor);
|
||||
box-shadow: var(--elements-box-shadow);
|
||||
transform: translateY(${openCreateNewScreen ? '255px' : '0px'});
|
||||
transition: var(--animation-default);
|
||||
`}
|
||||
>
|
||||
<Button
|
||||
position='fixed'
|
||||
square
|
||||
tiny
|
||||
className={css`
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
`}
|
||||
onClick={onClosehandler}
|
||||
>
|
||||
<Icon name='cross' size='x15' />
|
||||
</Button>
|
||||
{openCreateNewScreen && (
|
||||
<Box
|
||||
width='max-content'
|
||||
display='flex'
|
||||
className={css`
|
||||
gap: 50px;
|
||||
align-items: center;
|
||||
`}
|
||||
>
|
||||
{projects[activeProject]?.screens
|
||||
.map((id) => screens[id])
|
||||
.map((screen, i) => (
|
||||
<ScreenThumbnail
|
||||
screen={screen}
|
||||
key={i}
|
||||
disableDelete={projects[activeProject]?.screens.map((id) => screens[id]).length <= 1}
|
||||
/>
|
||||
))}
|
||||
<Icon
|
||||
onClick={createNewScreenhandler}
|
||||
size='60px'
|
||||
height='60px'
|
||||
name='plus'
|
||||
className={css`
|
||||
cursor: pointer;
|
||||
transition: var(--animation-default);
|
||||
&:hover {
|
||||
scale: 1.1;
|
||||
transition: var(--animation-default);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
</Scrollable>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateNewScreenContainer;
|
||||
@@ -0,0 +1,73 @@
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
|
||||
import type { ChangeEvent, MouseEvent } from 'react';
|
||||
import { useContext, useState } from 'react';
|
||||
|
||||
import { context, renameScreenAction } from '../../Context';
|
||||
import { activeScreenAction } from '../../Context/action/activeScreenAction';
|
||||
import { deleteScreenAction } from '../../Context/action/deleteScreenAction';
|
||||
import { duplicateScreenAction } from '../../Context/action/duplicateScreenAction';
|
||||
import type { ScreenType } from '../../Context/initialState';
|
||||
import renderPayload from '../RenderPayload/RenderPayload';
|
||||
import EditMenu from '../ScreenThumbnail/EditMenu/EditMenu';
|
||||
import ScreenThumbnailWrapper from '../ScreenThumbnail/ScreenThumbnailWrapper';
|
||||
import Thumbnail from '../ScreenThumbnail/Thumbnail';
|
||||
|
||||
export type ScreenThumbnailProps = { screen: ScreenType; disableDelete: boolean };
|
||||
|
||||
const ScreenThumbnail = ({ screen, disableDelete }: ScreenThumbnailProps) => {
|
||||
const { dispatch } = useContext(context);
|
||||
const [name, setName] = useState<string>(screen?.name);
|
||||
const toast = useToastBarDispatch();
|
||||
|
||||
const activateScreenHandler = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
dispatch(activeScreenAction(screen?.id));
|
||||
};
|
||||
|
||||
const duplicateScreenHandler = () => {
|
||||
dispatch(duplicateScreenAction({ id: screen?.id }));
|
||||
};
|
||||
|
||||
const onChangeNameHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setName(e.currentTarget.value);
|
||||
};
|
||||
|
||||
const nameSaveHandler = () => {
|
||||
if (!name.trim()) {
|
||||
setName(screen.name);
|
||||
return toast({
|
||||
type: 'error',
|
||||
message: 'Cannot rename screen to empty name.',
|
||||
});
|
||||
}
|
||||
dispatch(renameScreenAction({ id: screen.id, name }));
|
||||
};
|
||||
|
||||
const deleteScreenHandler = () => {
|
||||
if (disableDelete)
|
||||
return toast({
|
||||
type: 'info',
|
||||
message: 'Cannot delete last screen.',
|
||||
});
|
||||
dispatch(deleteScreenAction(screen?.id));
|
||||
};
|
||||
return (
|
||||
<Box position='relative'>
|
||||
<EditMenu
|
||||
name={name}
|
||||
date={screen.date}
|
||||
onChange={onChangeNameHandler}
|
||||
onDuplicate={duplicateScreenHandler}
|
||||
onDelete={deleteScreenHandler}
|
||||
onBlur={nameSaveHandler}
|
||||
labelProps={{ fontScale: 'h5' }}
|
||||
/>
|
||||
<ScreenThumbnailWrapper onClick={activateScreenHandler}>
|
||||
<Thumbnail of={renderPayload({ blocks: screen.payload.blocks })} />
|
||||
</ScreenThumbnailWrapper>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScreenThumbnail;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './CreateNewScreenContainer';
|
||||
@@ -0,0 +1,35 @@
|
||||
import type { OnDragEndResponder } from '@hello-pangea/dnd';
|
||||
import { DragDropContext, Droppable } from '@hello-pangea/dnd';
|
||||
import { memo } from 'react';
|
||||
|
||||
import DraggableListItem from './DraggableListItem';
|
||||
import type { ILayoutBlock } from '../../Context/initialState';
|
||||
import { SurfaceOptions } from '../Preview/Display/Surface/constant';
|
||||
|
||||
export type Block = {
|
||||
id: string;
|
||||
payload: ILayoutBlock;
|
||||
};
|
||||
|
||||
export type DraggableListProps = {
|
||||
blocks: Block[];
|
||||
surface?: SurfaceOptions;
|
||||
onDragEnd: OnDragEndResponder;
|
||||
};
|
||||
|
||||
const DraggableList = ({ blocks, surface, onDragEnd }: DraggableListProps) => (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<Droppable droppableId='droppable-list'>
|
||||
{(provided) => (
|
||||
<div style={{ padding: '10px' }} ref={provided.innerRef} {...provided.droppableProps}>
|
||||
{blocks.map((block, index) => (
|
||||
<DraggableListItem surface={surface || SurfaceOptions.Message} block={block} index={index} key={block.id} />
|
||||
))}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
export default memo(DraggableList);
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Draggable } from '@hello-pangea/dnd';
|
||||
|
||||
import type { Block } from './DraggableList';
|
||||
import DeleteElementBtn from '../Preview/Display/UiKitElementWrapper/DeleteElementBtn';
|
||||
import UiKitElementWrapper from '../Preview/Display/UiKitElementWrapper/UiKitElementWrapper';
|
||||
import RenderPayload from '../RenderPayload/RenderPayload';
|
||||
|
||||
export type DraggableListItemProps = {
|
||||
block: Block;
|
||||
surface: number;
|
||||
index: number;
|
||||
};
|
||||
|
||||
const DraggableListItem = ({ block, surface, index }: DraggableListItemProps) => (
|
||||
<Draggable draggableId={block.id} index={index}>
|
||||
{(provided) => (
|
||||
<div ref={provided.innerRef} {...provided.draggableProps} {...provided.dragHandleProps}>
|
||||
<UiKitElementWrapper key={index}>
|
||||
<DeleteElementBtn elementIndex={index} />
|
||||
<RenderPayload surface={surface} blocks={[block.payload]} />
|
||||
</UiKitElementWrapper>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
);
|
||||
|
||||
export default DraggableListItem;
|
||||
31
apps/uikit-playground/src/Components/DropDown/DropDown.tsx
Normal file
31
apps/uikit-playground/src/Components/DropDown/DropDown.tsx
Normal file
@@ -0,0 +1,31 @@
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { Fragment } from 'react';
|
||||
|
||||
import Items from './Items';
|
||||
import type { Item, ItemBranch } from './types';
|
||||
|
||||
export type DropDownProps = {
|
||||
readonly blocksTree: Item;
|
||||
};
|
||||
|
||||
const DropDown = ({ blocksTree }: DropDownProps) => {
|
||||
const layer = 1;
|
||||
|
||||
const recursiveComponentTree = (branch: ItemBranch, layer: number) => (
|
||||
<Items layer={layer} label={branch.label} payload={branch.payload}>
|
||||
{branch.branches?.map((branch: ItemBranch, index: number) => (
|
||||
<Fragment key={index}>{recursiveComponentTree(branch, layer + 1)}</Fragment>
|
||||
))}
|
||||
</Items>
|
||||
);
|
||||
|
||||
return (
|
||||
<Box paddingBlockStart='15px' paddingBlockEnd='30px'>
|
||||
{blocksTree.map((branch: ItemBranch, i: number) => (
|
||||
<Box key={i}>{recursiveComponentTree(branch, layer)}</Box>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default DropDown;
|
||||
62
apps/uikit-playground/src/Components/DropDown/Items.tsx
Normal file
62
apps/uikit-playground/src/Components/DropDown/Items.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box, Label, Chevron } from '@rocket.chat/fuselage';
|
||||
import { useState, useContext } from 'react';
|
||||
|
||||
import ItemsIcon from './ItemsIcon';
|
||||
import { itemStyle, labelStyle } from './itemsStyle';
|
||||
import type { ItemProps } from './types';
|
||||
import { context, updatePayloadAction } from '../../Context';
|
||||
import getUniqueId from '../../utils/getUniqueId';
|
||||
|
||||
const Items = ({ label, children, layer, payload }: ItemProps) => {
|
||||
const [isOpen, toggleItemOpen] = useState<boolean>(layer === 1);
|
||||
const [hover, setHover] = useState<boolean>(false);
|
||||
const { state, dispatch } = useContext(context);
|
||||
|
||||
const itemClickHandler = () => {
|
||||
toggleItemOpen(!isOpen);
|
||||
if (!payload) return;
|
||||
dispatch(
|
||||
updatePayloadAction({
|
||||
blocks: [...state.screens[state.activeScreen].payload.blocks, { actionId: getUniqueId(), ...payload[0] }],
|
||||
changedByEditor: false,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Box marginBlockEnd={layer === 1 ? '10px' : '0px'}>
|
||||
<Box
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
className={itemStyle(layer, hover)}
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
onClick={itemClickHandler}
|
||||
>
|
||||
<Box size='x16' display='flex' alignItems='center'>
|
||||
{children && children.length > 0 && (
|
||||
<Box
|
||||
size='x16'
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
className={css`
|
||||
transform: rotate(${!isOpen ? '-90deg' : '0deg'});
|
||||
transition: var(--animation-very-fast);
|
||||
`}
|
||||
>
|
||||
<Chevron width='12px' color={hover ? '#fff' : '#000'} />
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
<Box height='25px' display='flex' alignItems='center'>
|
||||
<ItemsIcon layer={layer} hover={hover} lastNode={children === undefined} />
|
||||
<Label className={labelStyle(layer, hover)}>{label}</Label>
|
||||
</Box>
|
||||
</Box>
|
||||
<Box>{isOpen && children}</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Items;
|
||||
18
apps/uikit-playground/src/Components/DropDown/ItemsIcon.tsx
Normal file
18
apps/uikit-playground/src/Components/DropDown/ItemsIcon.tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import { Icon } from '@rocket.chat/fuselage';
|
||||
|
||||
export type ItemsIconProps = { layer: number; lastNode: boolean; hover: boolean };
|
||||
|
||||
const ItemsIcon = ({ layer, lastNode, hover }: ItemsIconProps) => {
|
||||
const selectIcon = (layer: number, hover: boolean) => {
|
||||
if (layer === 1) {
|
||||
return <Icon name='folder' size='x12' color={hover ? '#fff' : '#1d74f5'} />;
|
||||
}
|
||||
if (lastNode) {
|
||||
return <Icon name='cube' size='x12' color={hover ? '#fff' : '#f5455c'} />;
|
||||
}
|
||||
return <Icon name='burger' size='x12' color={hover ? '#fff' : '#19ac7c'} />;
|
||||
};
|
||||
return <>{selectIcon(layer, hover)}</>;
|
||||
};
|
||||
|
||||
export default ItemsIcon;
|
||||
1
apps/uikit-playground/src/Components/DropDown/index.tsx
Normal file
1
apps/uikit-playground/src/Components/DropDown/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './DropDown';
|
||||
45
apps/uikit-playground/src/Components/DropDown/itemsStyle.ts
Normal file
45
apps/uikit-playground/src/Components/DropDown/itemsStyle.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
|
||||
export const itemStyle = (layer: number, hover: boolean) => {
|
||||
const style = css`
|
||||
cursor: pointer !important;
|
||||
padding-left: ${10 + (layer - 1) * 16}px !important;
|
||||
background-color: ${hover ? 'var(--RCPG-primary-color) !important' : 'transparent !important'};
|
||||
`;
|
||||
return style;
|
||||
};
|
||||
|
||||
export const labelStyle = (layer: number, hover: boolean) => {
|
||||
let customStyle;
|
||||
const basicStyle = css`
|
||||
cursor: pointer !important;
|
||||
padding-left: 4px !important;
|
||||
`;
|
||||
switch (layer) {
|
||||
case 1:
|
||||
customStyle = css`
|
||||
font-weight: 700 !important;
|
||||
font-size: 14px !important;
|
||||
letter-spacing: 0.3px !important;
|
||||
color: ${hover ? '#fff !important' : '#999 !important'};
|
||||
text-transform: uppercase !important;
|
||||
`;
|
||||
break;
|
||||
case 2:
|
||||
customStyle = css`
|
||||
letter-spacing: 0.1px !important;
|
||||
font-size: 12px !important;
|
||||
color: ${hover ? '#fff !important' : '#555 !important'};
|
||||
text-transform: capitalize !important;
|
||||
`;
|
||||
break;
|
||||
default:
|
||||
customStyle = css`
|
||||
font-size: 12px !important;
|
||||
color: ${hover ? '#fff !important' : '#555 !important'};
|
||||
text-transform: capitalize !important;
|
||||
`;
|
||||
break;
|
||||
}
|
||||
return [customStyle, basicStyle];
|
||||
};
|
||||
17
apps/uikit-playground/src/Components/DropDown/types.ts
Normal file
17
apps/uikit-playground/src/Components/DropDown/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { LayoutBlock } from '@rocket.chat/ui-kit';
|
||||
import type { JSX } from 'react';
|
||||
|
||||
export type ItemProps = {
|
||||
label: string;
|
||||
layer: number;
|
||||
payload?: readonly LayoutBlock[];
|
||||
children?: ReadonlyArray<JSX.Element>;
|
||||
};
|
||||
|
||||
export type ItemBranch = {
|
||||
label: string;
|
||||
branches?: Item;
|
||||
payload?: readonly LayoutBlock[];
|
||||
};
|
||||
|
||||
export type Item = ItemBranch[];
|
||||
@@ -0,0 +1,27 @@
|
||||
const ConnectionLine = ({
|
||||
fromX,
|
||||
fromY,
|
||||
toX,
|
||||
toY,
|
||||
}: {
|
||||
fromX: number;
|
||||
fromY: number;
|
||||
fromPosition: string;
|
||||
toX: number;
|
||||
toY: number;
|
||||
toPosition: string;
|
||||
connectionLineType: string;
|
||||
}) => (
|
||||
<g>
|
||||
<path
|
||||
fill='none'
|
||||
stroke='#222'
|
||||
strokeWidth={1.5}
|
||||
className='animated'
|
||||
d={`M${fromX},${fromY} C ${fromX} ${toY} ${fromX} ${toY} ${toX},${toY}`}
|
||||
/>
|
||||
<circle cx={toX} cy={toY} fill='#fff' r={2} stroke='#222' strokeWidth={1.5} />
|
||||
</g>
|
||||
);
|
||||
|
||||
export default ConnectionLine;
|
||||
@@ -0,0 +1,7 @@
|
||||
import { Controls } from 'reactflow';
|
||||
|
||||
const ControlButtons = () => {
|
||||
return <Controls></Controls>;
|
||||
};
|
||||
|
||||
export default ControlButtons;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './ControlButtons';
|
||||
@@ -0,0 +1,115 @@
|
||||
import { useCallback, useContext, useMemo, useRef, useState } from 'react';
|
||||
import ReactFlow, {
|
||||
MiniMap,
|
||||
Background,
|
||||
addEdge,
|
||||
updateEdge,
|
||||
type Node,
|
||||
type Viewport,
|
||||
type ReactFlowInstance,
|
||||
useReactFlow,
|
||||
type Connection,
|
||||
type Edge,
|
||||
} from 'reactflow';
|
||||
|
||||
import 'reactflow/dist/style.css';
|
||||
import ConnectionLine from './ConnectionLine';
|
||||
import ControlButton from './ControlButtons';
|
||||
import UIKitWrapper from './UIKitWrapper/UIKitWrapper';
|
||||
import { FlowParams } from './utils';
|
||||
import { context } from '../../Context';
|
||||
import { updateNodesAndViewPortAction } from '../../Context/action/updateNodesAndViewPortAction';
|
||||
import { useNodesAndEdges } from '../../hooks/useNodesAndEdges';
|
||||
|
||||
const FlowContainer = () => {
|
||||
const { dispatch } = useContext(context);
|
||||
|
||||
const { nodes, edges, Viewport, onNodesChange, onEdgesChange, setEdges } = useNodesAndEdges();
|
||||
const { setViewport } = useReactFlow();
|
||||
|
||||
const nodeTypes = useMemo(
|
||||
() => ({
|
||||
custom: UIKitWrapper,
|
||||
}),
|
||||
// used to rerender edge lines on reorder payload
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
[edges],
|
||||
);
|
||||
|
||||
const [rfInstance, setRfInstance] = useState<ReactFlowInstance>();
|
||||
const edgeUpdateSuccessful = useRef(true);
|
||||
|
||||
const onConnect = useCallback(
|
||||
(connection: Connection) => {
|
||||
if (connection.source === connection.target) return;
|
||||
const newEdge = {
|
||||
...connection,
|
||||
type: FlowParams.edgeType,
|
||||
markerEnd: FlowParams.markerEnd,
|
||||
style: FlowParams.style,
|
||||
};
|
||||
setEdges((eds) => addEdge(newEdge, eds));
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const onEdgeUpdateStart = useCallback(() => {
|
||||
edgeUpdateSuccessful.current = false;
|
||||
}, []);
|
||||
|
||||
const onEdgeUpdate = useCallback(
|
||||
(oldEdge: Edge<unknown>, newConnection: Connection) => {
|
||||
edgeUpdateSuccessful.current = true;
|
||||
setEdges((els) => updateEdge(oldEdge, newConnection, els));
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const onEdgeUpdateEnd = useCallback(
|
||||
(_: MouseEvent | TouchEvent, edge: Edge<unknown>) => {
|
||||
if (!edgeUpdateSuccessful.current) {
|
||||
setEdges((eds) => {
|
||||
return eds.filter((e) => e.id !== edge.id);
|
||||
});
|
||||
}
|
||||
edgeUpdateSuccessful.current = true;
|
||||
},
|
||||
[setEdges],
|
||||
);
|
||||
|
||||
const onNodeDragStop = () => {
|
||||
if (!rfInstance?.toObject()) return;
|
||||
const { nodes, viewport }: { nodes: Node[]; viewport: Viewport } = rfInstance.toObject();
|
||||
dispatch(updateNodesAndViewPortAction({ nodes, viewport }));
|
||||
};
|
||||
|
||||
const onInit = (instance: ReactFlowInstance) => {
|
||||
setRfInstance(instance);
|
||||
if (Viewport) setViewport(Viewport);
|
||||
};
|
||||
|
||||
return (
|
||||
<ReactFlow
|
||||
nodes={nodes}
|
||||
edges={edges}
|
||||
onInit={onInit}
|
||||
onNodesChange={onNodesChange}
|
||||
onEdgesChange={onEdgesChange}
|
||||
onEdgeUpdate={onEdgeUpdate}
|
||||
onNodeDragStop={onNodeDragStop}
|
||||
onEdgeUpdateStart={onEdgeUpdateStart}
|
||||
onEdgeUpdateEnd={onEdgeUpdateEnd}
|
||||
onConnect={onConnect}
|
||||
fitView
|
||||
nodeTypes={nodeTypes}
|
||||
minZoom={0.1}
|
||||
connectionLineComponent={ConnectionLine}
|
||||
>
|
||||
<MiniMap zoomable pannable />
|
||||
<ControlButton />
|
||||
<Background color='#aaa' gap={16} />
|
||||
</ReactFlow>
|
||||
);
|
||||
};
|
||||
|
||||
export default FlowContainer;
|
||||
@@ -0,0 +1,7 @@
|
||||
.rc-scrollbars-view{
|
||||
position: relative !important;
|
||||
overflow: hidden !important;
|
||||
width: 100% !important;
|
||||
z-index: 1000 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useContext } from 'react';
|
||||
import { Handle, Position } from 'reactflow';
|
||||
import './UIKitWrapper.scss';
|
||||
|
||||
import { context } from '../../../Context';
|
||||
import type { idType } from '../../../Context/initialState';
|
||||
import SurfaceRender from '../../Preview/Display/Surface/SurfaceRender';
|
||||
import RenderPayload from '../../RenderPayload/RenderPayload';
|
||||
|
||||
export type UIKitWrapperProps = { id: string; data: idType };
|
||||
|
||||
const UIKitWrapper = ({ id, data }: UIKitWrapperProps) => {
|
||||
const {
|
||||
state: { screens },
|
||||
} = useContext(context);
|
||||
if (!screens[data]) return null;
|
||||
const { blocks, surface } = screens[data].payload;
|
||||
return (
|
||||
<Box padding='10px' border='var(--default-border)' backgroundColor='white' borderRadius='10px'>
|
||||
<Handle type='target' className='react-flow-targetHandle' position={Position.Left} id={`${id}`} />
|
||||
<SurfaceRender type={surface}>
|
||||
{blocks.map((block, index) => (
|
||||
<Box key={index} paddingInlineEnd='6px' className='uiKitWrapper'>
|
||||
<Box position='relative' border='var(--default-border)' padding='10px'>
|
||||
<RenderPayload blocks={[block]} surface={surface} />
|
||||
<Handle type='source' className='react-flow-sourceHandle' position={Position.Right} id={block.actionId} />
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</SurfaceRender>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default UIKitWrapper;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './UIKitWrapper';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './FlowContainer';
|
||||
34
apps/uikit-playground/src/Components/FlowContainer/utils.ts
Normal file
34
apps/uikit-playground/src/Components/FlowContainer/utils.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { MarkerType } from 'reactflow';
|
||||
|
||||
import type { ScreenType } from '../../Context/initialState';
|
||||
|
||||
export function createNodesAndEdges(screens: ScreenType[]) {
|
||||
const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 };
|
||||
|
||||
const nodes = screens.map((screen, i) => {
|
||||
const degrees = i * (360 / 8);
|
||||
const radians = degrees * (Math.PI / 180);
|
||||
const x = 250 * Math.cos(radians) + center.x;
|
||||
const y = 250 * Math.sin(radians) + center.y;
|
||||
|
||||
return {
|
||||
id: screen.id,
|
||||
type: 'custom',
|
||||
position: { x, y },
|
||||
data: screen,
|
||||
};
|
||||
});
|
||||
|
||||
return { nodes };
|
||||
}
|
||||
|
||||
export const FlowParams = {
|
||||
edgeType: 'smoothstep',
|
||||
markerEnd: {
|
||||
type: MarkerType.Arrow,
|
||||
},
|
||||
style: {
|
||||
strokeWidth: 2,
|
||||
stroke: 'var(--RCPG-primary-color)',
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,32 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box, Label } from '@rocket.chat/fuselage';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import ProjectsList from './ProjectsList/ProjectsList';
|
||||
import { context, createNewProjectAction } from '../../Context';
|
||||
import CreateNewScreenButton from '../ScreenThumbnail/CreateNewScreenButton';
|
||||
|
||||
const HomeContainer = () => {
|
||||
const { dispatch } = useContext(context);
|
||||
return (
|
||||
<Box minWidth='100%' display='flex' justifyContent='center' height='var(--content-height)' paddingBlockStart='30px'>
|
||||
<Box
|
||||
width='max-content'
|
||||
minWidth='70%'
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
className={css`
|
||||
gap: 30px;
|
||||
`}
|
||||
>
|
||||
<Label fontScale='h1'>Projects</Label>
|
||||
<Label fontScale='h3'>Start a new project</Label>
|
||||
<CreateNewScreenButton name='plus' onClick={() => dispatch(createNewProjectAction())} />
|
||||
<Label fontScale='h4'>Existing Projects</Label>
|
||||
<ProjectsList />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default HomeContainer;
|
||||
@@ -0,0 +1,35 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import ProjectsThumbnail from './ProjectsThumbnail';
|
||||
import { context } from '../../../Context';
|
||||
|
||||
const ProjectsList = () => {
|
||||
const {
|
||||
state: { screens, projects },
|
||||
} = useContext(context);
|
||||
|
||||
return (
|
||||
<Box
|
||||
className={css`
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, 200px);
|
||||
gap: 30px;
|
||||
`}
|
||||
paddingBlockEnd='30px'
|
||||
>
|
||||
{Object.values(projects).map((project) => (
|
||||
<ProjectsThumbnail
|
||||
key={project.id}
|
||||
id={project.id}
|
||||
name={project.name}
|
||||
date={project.date}
|
||||
blocks={screens[project.screens[0]].payload.blocks}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectsList;
|
||||
@@ -0,0 +1,78 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
|
||||
import { type ChangeEvent, useContext, useState } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import { activeProjectAction, context, renameProjectAction } from '../../../Context';
|
||||
import { deleteProjectAction } from '../../../Context/action/deleteProjectAction';
|
||||
import type { ILayoutBlock } from '../../../Context/initialState';
|
||||
import routes from '../../../Routes/Routes';
|
||||
import { formatDate } from '../../../utils/formatDate';
|
||||
import RenderPayload from '../../RenderPayload/RenderPayload';
|
||||
import EditMenu from '../../ScreenThumbnail/EditMenu';
|
||||
import EditableLabel from '../../ScreenThumbnail/EditableLabel/EditableLabel';
|
||||
import ScreenThumbnailWrapper from '../../ScreenThumbnail/ScreenThumbnailWrapper';
|
||||
import Thumbnail from '../../ScreenThumbnail/Thumbnail';
|
||||
|
||||
export type ProjectsThumbnailProps = { id: string; name: string; date: string; blocks: ILayoutBlock[] };
|
||||
|
||||
const ProjectsThumbnail = ({ id, name: _name, date, blocks }: ProjectsThumbnailProps) => {
|
||||
const [name, setName] = useState<string>(_name);
|
||||
const navigate = useNavigate();
|
||||
const { dispatch } = useContext(context);
|
||||
const toast = useToastBarDispatch();
|
||||
const activeProjectHandler = () => {
|
||||
dispatch(activeProjectAction(id));
|
||||
navigate(`/${id}/${routes.project}`);
|
||||
};
|
||||
|
||||
const deleteScreenHandler = () => {
|
||||
dispatch(deleteProjectAction(id));
|
||||
};
|
||||
|
||||
const onChangeNameHandler = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setName(e.currentTarget.value);
|
||||
};
|
||||
|
||||
const nameSaveHandler = () => {
|
||||
if (!name.trim()) {
|
||||
setName(_name);
|
||||
return toast({
|
||||
type: 'error',
|
||||
message: 'Cannot rename project to empty name.',
|
||||
});
|
||||
}
|
||||
dispatch(renameProjectAction({ id, name }));
|
||||
};
|
||||
|
||||
return (
|
||||
<Box
|
||||
display='flex'
|
||||
flexDirection='column'
|
||||
className={css`
|
||||
gap: 5px;
|
||||
`}
|
||||
position='relative'
|
||||
>
|
||||
<EditMenu
|
||||
name={name}
|
||||
date={date}
|
||||
onChange={onChangeNameHandler}
|
||||
onDelete={deleteScreenHandler}
|
||||
onBlur={nameSaveHandler}
|
||||
labelProps={{ fontScale: 'h5' }}
|
||||
/>
|
||||
<ScreenThumbnailWrapper onClick={activeProjectHandler} width='200px' height='260px' padding='30px'>
|
||||
<Thumbnail of={<RenderPayload blocks={blocks} />} />
|
||||
<Box onClick={(e) => e.stopPropagation()}></Box>
|
||||
</ScreenThumbnailWrapper>
|
||||
<EditableLabel value={name} onChange={onChangeNameHandler} fontScale='h5' onBlur={nameSaveHandler} />
|
||||
<Box withTruncatedText fontScale='p2'>
|
||||
{formatDate(date)}
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default ProjectsThumbnail;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './ProjectsList';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './HomeContainer';
|
||||
@@ -0,0 +1,27 @@
|
||||
import { usePrefersReducedMotion } from '@rocket.chat/fuselage-hooks';
|
||||
import type { ReactNode } from 'react';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import Line from './Line';
|
||||
import Wrapper from './Wrapper';
|
||||
import { context } from '../../../Context';
|
||||
|
||||
export type BurgerIconProps = { children?: ReactNode };
|
||||
|
||||
const BurgerIcon = ({ children }: BurgerIconProps) => {
|
||||
const isReducedMotionPreferred = usePrefersReducedMotion();
|
||||
const {
|
||||
state: { navMenuToggle },
|
||||
} = useContext(context);
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<Line animated={!isReducedMotionPreferred} moved={navMenuToggle} />
|
||||
<Line animated={!isReducedMotionPreferred} moved={navMenuToggle} />
|
||||
<Line animated={!isReducedMotionPreferred} moved={navMenuToggle} />
|
||||
{children}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export default BurgerIcon;
|
||||
@@ -0,0 +1,40 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
|
||||
export type LineProps = { animated: boolean; moved?: boolean };
|
||||
|
||||
const Line = ({ animated, moved }: LineProps) => {
|
||||
const animatedStyle = animated
|
||||
? css`
|
||||
will-change: transform;
|
||||
transition: transform 0.1s ease-out;
|
||||
`
|
||||
: '';
|
||||
|
||||
const movedStyle = moved
|
||||
? css`
|
||||
&:nth-child(1),
|
||||
&:nth-child(3) {
|
||||
transform-origin: 50%, 50%, 0;
|
||||
}
|
||||
&:nth-child(1) {
|
||||
transform: translate(-25%, 3px) rotate(-45deg) scale(0.5, 1);
|
||||
}
|
||||
[dir='rtl'] &:nth-child(1) {
|
||||
transform: translate(25%, 3px) rotate(45deg) scale(0.5, 1);
|
||||
}
|
||||
&:nth-child(3) {
|
||||
transform: translate(-25%, -3px) rotate(45deg) scale(0.5, 1);
|
||||
}
|
||||
[dir='rtl'] &:nth-child(3) {
|
||||
transform: translate(25%, -3px) rotate(-45deg) scale(0.5, 1);
|
||||
}
|
||||
`
|
||||
: '';
|
||||
|
||||
return (
|
||||
<Box is='span' width='x20' height='x2' backgroundColor='currentColor' className={[animatedStyle, movedStyle]} aria-hidden='true' />
|
||||
);
|
||||
};
|
||||
|
||||
export default Line;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type WrapperProps = { children: ReactNode };
|
||||
|
||||
const Wrapper = ({ children }: WrapperProps) => (
|
||||
<Box
|
||||
is='span'
|
||||
display='inline-flex'
|
||||
flexDirection='column'
|
||||
alignItems='center'
|
||||
justifyContent='space-between'
|
||||
paddingBlock={4}
|
||||
paddingInline={2}
|
||||
verticalAlign='middle'
|
||||
height='x24'
|
||||
className={css`
|
||||
cursor: pointer;
|
||||
`}
|
||||
width='x24'
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default Wrapper;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './BurgerIcon';
|
||||
5
apps/uikit-playground/src/Components/NavBar/Divider.tsx
Normal file
5
apps/uikit-playground/src/Components/NavBar/Divider.tsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
|
||||
const Divider = () => <Box height='70%' width='1px' backgroundColor='#c7c7c7' />;
|
||||
|
||||
export default Divider;
|
||||
12
apps/uikit-playground/src/Components/NavBar/Logo.tsx
Normal file
12
apps/uikit-playground/src/Components/NavBar/Logo.tsx
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { RocketChatLogo } from '@rocket.chat/logo';
|
||||
|
||||
const Logo = () => (
|
||||
<Box display='flex' justifyContent='center' height='100%' width='var(--sidebar-width)'>
|
||||
<Box height='100%' width='80%'>
|
||||
<RocketChatLogo />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
|
||||
export default Logo;
|
||||
95
apps/uikit-playground/src/Components/NavBar/NavBar.tsx
Normal file
95
apps/uikit-playground/src/Components/NavBar/NavBar.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box, Tile, FlexContainer, ButtonGroup, Button, Icon } from '@rocket.chat/fuselage';
|
||||
import { useToastBarDispatch } from '@rocket.chat/fuselage-toastbar';
|
||||
import { useContext } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
import RightNavBtn from './RightNavBtn';
|
||||
import { context, updatePayloadAction } from '../../Context';
|
||||
import { openCreateNewScreenAction } from '../../Context/action/openCreateNewScreenAction';
|
||||
import routes from '../../Routes/Routes';
|
||||
|
||||
const NabBar = () => {
|
||||
const {
|
||||
state: { isMobile, screens, activeScreen },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
const toast = useToastBarDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<FlexContainer alignItems='center'>
|
||||
<Box
|
||||
position='relative'
|
||||
width='100%'
|
||||
height='var(--navbar-height)'
|
||||
is={Tile}
|
||||
padding={0}
|
||||
zIndex={3}
|
||||
elevation='2'
|
||||
className={css`
|
||||
user-select: none;
|
||||
`}
|
||||
>
|
||||
<Button primary onClick={() => navigate(routes.home)} marginInlineStart='20px' small square>
|
||||
<Icon name='home' size='x24' />
|
||||
</Button>
|
||||
{!isMobile && (
|
||||
<Button
|
||||
marginInlineStart='12px'
|
||||
small
|
||||
success
|
||||
onClick={() => {
|
||||
dispatch(openCreateNewScreenAction(true));
|
||||
}}
|
||||
>
|
||||
Screens
|
||||
</Button>
|
||||
)}
|
||||
<Box flexGrow={1} minWidth='15px' display='flex' justifyContent='flex-end'>
|
||||
<Box display='flex' height='100%'>
|
||||
{!isMobile && (
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
secondary
|
||||
success
|
||||
small
|
||||
onClick={async () => {
|
||||
await navigator.clipboard.writeText(JSON.stringify(screens[activeScreen]?.payload));
|
||||
toast({
|
||||
type: 'success',
|
||||
message: 'Payload Copied',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Copy Payload
|
||||
</Button>
|
||||
<Button
|
||||
small
|
||||
danger
|
||||
onClick={() => {
|
||||
dispatch(
|
||||
updatePayloadAction({
|
||||
blocks: [],
|
||||
changedByEditor: false,
|
||||
}),
|
||||
);
|
||||
toast({
|
||||
type: 'success',
|
||||
message: 'All Blocks Cleared',
|
||||
});
|
||||
}}
|
||||
>
|
||||
Clear Blocks
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
<RightNavBtn />
|
||||
</Box>
|
||||
</FlexContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export default NabBar;
|
||||
17
apps/uikit-playground/src/Components/NavBar/RightNavBtn.tsx
Normal file
17
apps/uikit-playground/src/Components/NavBar/RightNavBtn.tsx
Normal file
@@ -0,0 +1,17 @@
|
||||
import { Box, Button } from '@rocket.chat/fuselage';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import BurgerIcon from './BurgerIcon';
|
||||
import { context, navMenuToggleAction } from '../../Context';
|
||||
|
||||
const RightNavBtn = () => {
|
||||
const { state, dispatch } = useContext(context);
|
||||
|
||||
return (
|
||||
<Box marginInlineEnd='15px' onClick={() => state.isMobile && dispatch(navMenuToggleAction(true))}>
|
||||
{state.isMobile ? <BurgerIcon /> : <Button primary>Send to RocketChat</Button>}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightNavBtn;
|
||||
1
apps/uikit-playground/src/Components/NavBar/index.tsx
Normal file
1
apps/uikit-playground/src/Components/NavBar/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './NavBar';
|
||||
@@ -0,0 +1,24 @@
|
||||
import { type ComponentProps, Fragment, useContext, useEffect } from 'react';
|
||||
|
||||
import { context } from '../../Context';
|
||||
import persistStore from '../../utils/persistStore';
|
||||
|
||||
const PersistStore = (props: ComponentProps<'div'>) => {
|
||||
const { state } = useContext(context);
|
||||
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = () => {
|
||||
persistStore(state);
|
||||
};
|
||||
|
||||
window.onbeforeunload = handleBeforeUnload;
|
||||
|
||||
return () => {
|
||||
window.onbeforeunload = null;
|
||||
};
|
||||
}, [state]);
|
||||
|
||||
return <Fragment {...props} />;
|
||||
};
|
||||
|
||||
export default PersistStore;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './PersistStore';
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Box, Scrollable } from '@rocket.chat/fuselage';
|
||||
|
||||
import Surface from './Surface/Surface';
|
||||
|
||||
const Display = () => (
|
||||
<Scrollable vertical>
|
||||
<Box height='100%' width='100%' borderInlineStart='var(--default-border)'>
|
||||
<Surface />
|
||||
</Box>
|
||||
</Scrollable>
|
||||
);
|
||||
|
||||
export default Display;
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Banner, Icon } from '@rocket.chat/fuselage';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type MessageSurfaceProps = { children: ReactNode };
|
||||
|
||||
const MessageSurface = ({ children }: MessageSurfaceProps) => <Banner icon={<Icon name='info' size='x20' />}>{children}</Banner>;
|
||||
|
||||
export default MessageSurface;
|
||||
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
Avatar,
|
||||
Box,
|
||||
Button,
|
||||
ButtonGroup,
|
||||
Contextualbar,
|
||||
ContextualbarAction,
|
||||
ContextualbarFooter,
|
||||
ContextualbarHeader,
|
||||
ContextualbarTitle,
|
||||
} from '@rocket.chat/fuselage';
|
||||
import { Scrollbars } from 'rc-scrollbars';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type ContextualBarSurfaceProps = { children: ReactNode };
|
||||
|
||||
const ContextualBarSurface = ({ children }: ContextualBarSurfaceProps) => (
|
||||
<Contextualbar>
|
||||
<ContextualbarHeader>
|
||||
<Avatar url='data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==' />
|
||||
<ContextualbarTitle>Contextual Bar</ContextualbarTitle>
|
||||
<ContextualbarAction data-qa='ContextualbarActionClose' title='Close' name='cross' />
|
||||
</ContextualbarHeader>
|
||||
|
||||
<Box height='100%' padding='12px'>
|
||||
<Box height='100%' display='flex' flexShrink={1} flexDirection='column' flexGrow={1}>
|
||||
<Scrollbars
|
||||
autoHide
|
||||
autoHideTimeout={2000}
|
||||
autoHideDuration={500}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
flexGrow: 1,
|
||||
overflowY: 'hidden',
|
||||
}}
|
||||
renderThumbVertical={({ style, ...props }) => (
|
||||
<div
|
||||
{...props}
|
||||
style={{
|
||||
...style,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
borderRadius: '7px',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
>
|
||||
<div>{children}</div>
|
||||
</Scrollbars>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<ContextualbarFooter>
|
||||
<ButtonGroup stretch>
|
||||
<Button>Cancel</Button>
|
||||
<Button primary>Submit</Button>
|
||||
</ButtonGroup>
|
||||
</ContextualbarFooter>
|
||||
</Contextualbar>
|
||||
);
|
||||
|
||||
export default ContextualBarSurface;
|
||||
@@ -0,0 +1,49 @@
|
||||
import {
|
||||
Message,
|
||||
MessageLeftContainer,
|
||||
MessageContainer,
|
||||
MessageHeader,
|
||||
MessageName,
|
||||
MessageUsername,
|
||||
MessageRole,
|
||||
MessageTimestamp,
|
||||
MessageBody,
|
||||
MessageToolbarWrapper,
|
||||
MessageToolbar,
|
||||
MessageToolbarItem,
|
||||
Avatar,
|
||||
} from '@rocket.chat/fuselage';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type MessageSurfaceProps = { children: ReactNode };
|
||||
|
||||
const MessageSurface = ({ children }: MessageSurfaceProps) => (
|
||||
<Message>
|
||||
<MessageLeftContainer>
|
||||
<Avatar
|
||||
url='data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wCEAAcHBwcIBwgJCQgMDAsMDBEQDg4QERoSFBIUEhonGB0YGB0YJyMqIiAiKiM+MSsrMT5IPDk8SFdOTldtaG2Pj8ABBwcHBwgHCAkJCAwMCwwMERAODhARGhIUEhQSGicYHRgYHRgnIyoiICIqIz4xKysxPkg8OTxIV05OV21obY+PwP/CABEIAGQAZAMBIgACEQEDEQH/xAAcAAACAgMBAQAAAAAAAAAAAAAFBgQHAAIDAQj/2gAIAQEAAAAA+kczMwZGh8CUyurWAH80rVUuuWASGfG6m7nyp6Rinris4KpJ17U5Ysuo68Hxb1ZT6WebKzDM9dJfT1ud2KAULVUe518oCrOaBrmCY2JeVey0tKTFYsNow0vgdicyQkM0jcgFnDa9biJLTzrw4RZw2KBLGi42bsO5Rx5Nzr42MlZK58BJXqsPteM9eWRK8jRVN2nKbUosvzl9FytI41WeaeuL/8QAGQEAAwEBAQAAAAAAAAAAAAAAAAIDBAEF/9oACAECEAAAAAABWHRedSXdHFeNk35pzvs6QzxrsEbCyekQTH//xAAZAQADAQEBAAAAAAAAAAAAAAABAgMABAX/2gAIAQMQAAAA2GOI1UQkP3V8oOtqW8tqMJAM1xDFRXp4hRx//8QAKhAAAgMAAgIBAwMEAwAAAAAAAwQBAgUABhESEwcUIRAWMhUgIjMjJDH/2gAIAQEAAQgA/u1mgprCMX+vgL+Eni9ibUIISbXbRn+XRjXx2vCj3wHRj2W3ejJ6uiV9Tmbsw1t6yM/pa9a+vnZ+qWDmsEAE31EwXYVIznaC+iiu4va9aWHFuw9jUw1ovdLtXaewGskFyrWCnQirctNXozklpQgyUt1Lrwl+y/ML9PqTp9gEGqeYt1R29ZI0515lNQjFuld4f6+4IBO5dl283WzKI1mLVraMU4rudg+LveYwywvej2BojhSUTgGyuUBOn4CFOxRcTvZc5LSEiXa11MlKxz63eLC8jGfa1LKjZOrpuHLH22slW3iRTvKLIphL1ftCWhJE+ZdB0f3vXveQEjazN8RISGQiMUz4rPOmxaddib9jx1Ge7gpPZdAdhtKr7dSPbIEmHWbDLdXixxA/K7/zPEFQOo5qYyuEuviWUBtt3d65pU0G9y9O6Xi1lKwiSt85Dmur95luA50xBoWxF5cgF+yKsRqNaMMGubY0GSuQzfXZR+8oWn30WtEC6FNTNM80VKHm0Hv8YCjtfptzkLryTuANmNa1x4lHA5KdWzNrnzz3Xxs0aQbRZK3y95CSdH2v2F3yktDVjqMft55BMxdAdBCsSs9c0Wk25+z0cd12wDU7AdzPuFg3043Qa07BRd4u+N9X7ZMhL5uba2EgdWL6LzW607tECstNx7jhq0DDrJ2rpDVW/wAV9MK+itZM9ei5M3m9ks7OzB+ivyR588hr1/8Aevzl5xjyHtl9c2n7LoMPQirDel2BcmwBC+eM7/cH5Hduotaqtamm6lq8FSaC/wATk+OLetJ9Vo5P58eSRERPi4/eRV4M3k82hUAwfMOaCLY7c8/YAvf3Fh5DJt3RNGetZ4+jaBSGjNh3+UNazNTXHNIioQzZcPCxNY5Yk28xVg3xmDTgq/z52kjC2QLSWzdEbytWhjtSPEVtcc6uiJbqytM8vwTeJhoVrDn1UmeEF/xrihuPVcNakOai0etGa0HS5dBr5dXO+MceKTPCrVeQcTt1XJfz824DgJ4pNudb0IW1tCLdduaQOM3c7oWC+Cmp4oGkWj/tXnjv4EDh/wDSPhPzWI5qsG+RO1K1vcVfVUBIvWboJFVLpD5X+dacxFxT158vEwUWRZgRrW9rV4nWJhCJr/pbtxz+A+EnyGOWmbUnifhg8WI24RLHIwNntu9qefl67FC4eeW//8QAOBAAAgIBAwIDBAUMAwAAAAAAAQIDEQAEEiExQRMiURAyYXEFFCNigQZCQ1JTgpGSobGywSByov/aAAgBAQAJPwD/AJS+Gv1qAFv3x15HGaLV6370SbU/nkKrn0LFEWoo0urWw6EMppFYHkZ9EeKApCro5k2AepEhUlsjaBnoeFq49gY/dLAqcZ5tOOsDG2UfcY/2OK2zUAO1VW72PHtgCeFRNmh5/wCB9vc1/S8hl1boaLLSJkOpfUrPG5D7fCQKbpMvw5V3C8/PYgfMAn/WRPPqH4h08Qt3Ofk1DHDe2d9UHKJ8GBC2cn8WO1DaV+lg8mEkkr/1PGatkgmQOwUL75JBsN0OE7WUg8lTR+Iqs1q7IhIw8KU7nHHDg9Rz7YzRjDtIitv5tCMmEf3RybyZWCsAAAckMmgd6kiPb1ZM08L6QBZjIUZ/ftCWK4QQRYI6HJVetevKm/0EeOWUxsBE8xRAV70O5vIZ2ifTI5KyEjc3XASkqMji64YUaIzxE+rh3SnPNGucsu9WQV8t5MEJ4jHUsx7ADISZXiLb5wYx+AI5yVYdNI7ASxryzDGklHdJASGGaYwS+80J6r8V9VyeaR49PEpcRO10o5us1amVSNiNaOR6U2KADqozQ45MCZqNQC8ZTw1cqo24zuHiEhLuSbf2d4H/AMhisolKeMF6scXdLp4qTePKsgquvwOaNRFFH55kKkbiCR1B7jGMmghURhfDtVK/2IyNVDd6y2lNACruu1YkZjgSNdUGAJbkLQzwN8YRtM680JeP5scMPrS7SFK+TYFGdVD3hIrToP4ZO8W+JvOlbloX3z6Um8OKJ2dWC049DlnWyuGhgJoLCooGTg0XOfRGgZ04dlZpGZf/ABhlCwHhIgW971F1XzxJmVkvljSk9V24GzqkYr5k5pjTsQNs+zdfw4zS6jchBDo+48EkXsIORRRrvhICKV6rmsgELxqYwyEsvYjrmpWRzHYKrtAUngZKkoeJtmxgdxI7ZR1D8Pz3Avw/kOpw7n8K3YirOWNp8rKdpFCzeQeHFJI6CRjtLAHnb05NZpRHBLSIxbzFvWsHIPU5EZZZBt8PreR6YybRvjkLeT12MM0U8kWnhVvFQ2CxO0qcjeO2h4bIEkXwPMCa6k4mxjpUtetHJFV3jteNoRB+kcdz6ZqPBEP2ccTD5sb+8QAcFuqLRoZG8BBBldiCxJHKqF4yFVG4tfUkt6k8nF3x8fMMO95NOwB5BfjNOqX1I6n5nqc64xrpV8HNPHANSRu2cAle9YkTQqg2ecr68MCueErhOFRidq2aB4HOMDCrEzv2aUe6o+C47rGmoeRDXfaUByUusUYViSTucA7jhPnfef3s948DOieUH1Y9Tnd/aavPdBofIZMWMkhdAx5ojoPhkjEeNwPQbRxn0vPfxUHNVJHHp2mjDg0ZXWzhlDBrDIgZiTYyRiqqFDMPMdvr8clBZP4Kzd8cbVwg2SbBse3qyEDPXDU2hkDni7S6I+VHNm2WmHqOBYOVuOE+HpdLqG4/Oln5Y5Zl0+l+1c8kvIdxH4YKLXnVpGJzq/JzpjWdtCxeRcsedvHGe4znO7ZVTwunPbcKyWJz4zEFGNUQMPLeQf7yB5RLudnHRArd8UPJO5PJIvcc0ausU3kKvVAH0IOen9znSGKvxrP1M/VOfmpijdFIj/Oz0xCe4AGcD4cnOQdbKy0BwDVDOiRqR+IB/wB4KkmkcO3elOEr4MTunPQqhIw8F8JP2UX+OdS9f1z9mM7JhI4PIxRYlUccd8VWaO9qtZXNY0Uf7KH7Nf6c4il3Ryx9adlz/8QAJxEAAgIABQMDBQAAAAAAAAAAAAECERASITFBAwRhE1GBIjNScaH/2gAIAQIBAT8AwSbGnjb/ACHusHn0yta8l8SaZpW41hrexaTYpZnzRldrybId0jUen7exGLllSVuyXatQ3+oh2/Uck5Oj0Om1vTJwcJU/4aHqOU74O3azrwWmxcEmqOpbhfsZ4ijVeSMoweulkVsPgcnXgWsWZV7D4+TrbohsvgfBPYl9tYf/xAAiEQACAgEEAgMBAAAAAAAAAAABEQACAxASITEEYTJBgSL/2gAIAQMBAT8A1BGv5rjrjLN6kr6EvjHFqVIHXM5+xBqA1MLFH1LZQK7gHthJJJfcXcAEADnAJPqYvI2khfyUDMnk4thrWrcNi+pUlQSuKys+El7lmjGQIYAzEjpawIPqUpe44DXcsU4C4AHLQCDq35PF+Npf5GDTF3p//9k='
|
||||
size='x36'
|
||||
/>
|
||||
</MessageLeftContainer>
|
||||
<MessageContainer>
|
||||
<MessageHeader>
|
||||
<MessageName>Vivek Srivastava</MessageName>
|
||||
<MessageUsername>@vivek.srivastava</MessageUsername>
|
||||
<MessageRole>Admin</MessageRole>
|
||||
<MessageRole>User</MessageRole>
|
||||
<MessageRole>Owner</MessageRole>
|
||||
<MessageTimestamp>12:00 PM</MessageTimestamp>
|
||||
</MessageHeader>
|
||||
<MessageBody>{children}</MessageBody>
|
||||
</MessageContainer>
|
||||
<MessageToolbarWrapper>
|
||||
<MessageToolbar>
|
||||
<MessageToolbarItem icon='quote' />
|
||||
<MessageToolbarItem icon='clock' />
|
||||
<MessageToolbarItem icon='thread' />
|
||||
</MessageToolbar>
|
||||
</MessageToolbarWrapper>
|
||||
</Message>
|
||||
);
|
||||
|
||||
export default MessageSurface;
|
||||
@@ -0,0 +1,40 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import {
|
||||
Modal,
|
||||
ModalHeader,
|
||||
ModalThumb,
|
||||
ModalTitle,
|
||||
ModalClose,
|
||||
ModalContent,
|
||||
ModalFooter,
|
||||
ButtonGroup,
|
||||
Button,
|
||||
} from '@rocket.chat/fuselage';
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
export type ModalSurfaceProps = { children: ReactNode };
|
||||
|
||||
const ModalSurface = ({ children }: ModalSurfaceProps) => (
|
||||
<Modal>
|
||||
<ModalHeader>
|
||||
<ModalThumb url='data:image/gif;base64,R0lGODlhAQABAIAAAMLCwgAAACH5BAAAAAAALAAAAAABAAEAAAICRAEAOw==' />
|
||||
<ModalTitle>Modal Header</ModalTitle>
|
||||
<ModalClose />
|
||||
</ModalHeader>
|
||||
<ModalContent
|
||||
className={css`
|
||||
overflow: visible;
|
||||
`}
|
||||
>
|
||||
{children}
|
||||
</ModalContent>
|
||||
<ModalFooter>
|
||||
<ButtonGroup align='end'>
|
||||
<Button>Cancel</Button>
|
||||
<Button primary>Submit</Button>
|
||||
</ButtonGroup>
|
||||
</ModalFooter>
|
||||
</Modal>
|
||||
);
|
||||
|
||||
export default ModalSurface;
|
||||
@@ -0,0 +1,7 @@
|
||||
export const reorder = <T>(list: T[], startIndex: number, endIndex: number): T[] => {
|
||||
const result = Array.from(list);
|
||||
const [removed] = result.splice(startIndex, 1);
|
||||
result.splice(endIndex, 0, removed);
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -0,0 +1,95 @@
|
||||
import type { DropResult } from '@hello-pangea/dnd';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { UiKitContext } from '@rocket.chat/fuselage-ui-kit';
|
||||
import { useContext, useState, useEffect } from 'react';
|
||||
|
||||
import { reorder } from './Reorder';
|
||||
import SurfaceRender from './SurfaceRender';
|
||||
import { SurfaceOptions } from './constant';
|
||||
import { context, updatePayloadAction, actionPreviewAction } from '../../../../Context';
|
||||
import generateActionPreview from '../../../../Payload/actionPreview/generateActionPreview';
|
||||
import type { Block } from '../../../Draggable/DraggableList';
|
||||
import DraggableList from '../../../Draggable/DraggableList';
|
||||
|
||||
const Surface = () => {
|
||||
const {
|
||||
state: { screens, activeScreen, user },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
const [uniqueBlocks, setUniqueBlocks] = useState<{
|
||||
block: Block[];
|
||||
isChangeByDnd: boolean;
|
||||
}>({
|
||||
block: screens[activeScreen]?.payload?.blocks?.map((block, i) => ({
|
||||
id: `${i}`,
|
||||
payload: block,
|
||||
})),
|
||||
isChangeByDnd: false,
|
||||
});
|
||||
const preview = generateActionPreview({
|
||||
type: 'Action Block',
|
||||
data: {},
|
||||
surface: screens[activeScreen]?.payload.surface,
|
||||
blocks: screens[activeScreen]?.payload.blocks,
|
||||
user,
|
||||
});
|
||||
useEffect(() => {
|
||||
setUniqueBlocks({
|
||||
block: screens[activeScreen]?.payload?.blocks?.map((block, i) => ({
|
||||
id: `${i}`,
|
||||
payload: block,
|
||||
})),
|
||||
isChangeByDnd: false,
|
||||
});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [screens[activeScreen]?.payload.blocks]);
|
||||
|
||||
useEffect(() => {
|
||||
if (uniqueBlocks.isChangeByDnd) {
|
||||
dispatch(
|
||||
updatePayloadAction({
|
||||
blocks: uniqueBlocks.block.map((block) => block.payload),
|
||||
changedByEditor: false,
|
||||
}),
|
||||
);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [uniqueBlocks]);
|
||||
|
||||
const onDragEnd = ({ destination, source }: DropResult) => {
|
||||
if (!destination) return;
|
||||
|
||||
const newBlocks = reorder(uniqueBlocks.block, source.index, destination.index);
|
||||
|
||||
setUniqueBlocks({ block: newBlocks, isChangeByDnd: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<Box width='100%' height='100%' padding='20px'>
|
||||
<UiKitContext.Provider
|
||||
value={{
|
||||
action: (a) => {
|
||||
preview.action = a;
|
||||
dispatch(actionPreviewAction({ ...preview }));
|
||||
},
|
||||
updateState: (s) => {
|
||||
preview.state = s;
|
||||
dispatch(actionPreviewAction({ ...preview }));
|
||||
},
|
||||
values: {},
|
||||
appId: 'core',
|
||||
}}
|
||||
>
|
||||
<SurfaceRender type={screens[activeScreen]?.payload.surface}>
|
||||
<DraggableList
|
||||
surface={screens[activeScreen]?.payload.surface || SurfaceOptions.Message}
|
||||
blocks={uniqueBlocks.block || []}
|
||||
onDragEnd={onDragEnd}
|
||||
/>
|
||||
</SurfaceRender>
|
||||
</UiKitContext.Provider>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Surface;
|
||||
@@ -0,0 +1,19 @@
|
||||
import type { ReactNode } from 'react';
|
||||
|
||||
import BannerSurface from './BannerSurface';
|
||||
import ContextualBarSurface from './ContextualBarSurface';
|
||||
import MessageSurface from './MessageSurface';
|
||||
import ModalSurface from './ModalSurface';
|
||||
import { SurfaceOptions } from './constant';
|
||||
|
||||
export type SurfaceRenderProps = { type?: SurfaceOptions; children: ReactNode };
|
||||
|
||||
const SurfaceRender = ({ type = SurfaceOptions.Message, children }: SurfaceRenderProps) => (
|
||||
<>
|
||||
{SurfaceOptions.Message === type && <MessageSurface>{children}</MessageSurface>}
|
||||
{SurfaceOptions.Banner === type && <BannerSurface>{children}</BannerSurface>}
|
||||
{SurfaceOptions.Modal === type && <ModalSurface>{children}</ModalSurface>}
|
||||
{SurfaceOptions.ContextualBar === type && <ContextualBarSurface>{children}</ContextualBarSurface>}
|
||||
</>
|
||||
);
|
||||
export default SurfaceRender;
|
||||
@@ -0,0 +1,6 @@
|
||||
export enum SurfaceOptions {
|
||||
Message = 1,
|
||||
Banner,
|
||||
Modal,
|
||||
ContextualBar,
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './Surface';
|
||||
@@ -0,0 +1,24 @@
|
||||
import './UiKitElementWrapper.scss';
|
||||
import { Icon } from '@rocket.chat/fuselage';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { context, updatePayloadAction } from '../../../../Context';
|
||||
|
||||
export type DisplayProps = { elementIndex: number };
|
||||
|
||||
const Display = ({ elementIndex }: DisplayProps) => {
|
||||
const { state, dispatch } = useContext(context);
|
||||
|
||||
const deleteElement = () => {
|
||||
const { screens, activeScreen } = state;
|
||||
const blocks = [...screens[activeScreen].payload.blocks];
|
||||
blocks.splice(elementIndex, 1);
|
||||
dispatch(updatePayloadAction({ blocks: [...blocks], changedByEditor: false }));
|
||||
};
|
||||
return (
|
||||
<button className='uikit-element-delete-btn' onClick={deleteElement}>
|
||||
<Icon name='cross' size='x20' />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
export default Display;
|
||||
@@ -0,0 +1,65 @@
|
||||
@use '../../../../cssVariables.css';
|
||||
|
||||
.uikit-element-wrapper{
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
outline: 1px solid transparent;
|
||||
height: fit-content;
|
||||
align-items: center;
|
||||
transition: var(--animation-fast);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
position: absolute;
|
||||
inset: -8px;
|
||||
border-radius: 4px;
|
||||
outline: var(--elements-border) !important;
|
||||
box-shadow: 0px 0px 8px 1px #ddd;
|
||||
opacity: 0;
|
||||
transition: var(--animation-fast);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
> .uikit-element-delete-btn {
|
||||
visibility: visible !important;
|
||||
}
|
||||
}
|
||||
|
||||
&:active {
|
||||
> * {
|
||||
position: relative;
|
||||
}
|
||||
&::before {
|
||||
background: white;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.uikit-element-delete-btn{
|
||||
all: initial;
|
||||
appearance: none;
|
||||
padding: 0;
|
||||
position: absolute !important;
|
||||
top: 50%;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
visibility: hidden;
|
||||
cursor: pointer;
|
||||
z-index: 1;
|
||||
background-color: white;
|
||||
border-radius: 4px;
|
||||
border: var(--elements-border);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import './UiKitElementWrapper.scss';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
const ElementWrapper = (props: ComponentProps<typeof Box>) => <Box className='uikit-element-wrapper' {...props} />;
|
||||
|
||||
export default ElementWrapper;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './UiKitElementWrapper';
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './Display';
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Box, Scrollable } from '@rocket.chat/fuselage';
|
||||
|
||||
import BlockEditor from '../../CodeEditor/BlockEditor';
|
||||
import { actionBlockExtensions } from '../../CodeEditor/Extensions/Extensions';
|
||||
|
||||
const ActionBlockEditor = () => (
|
||||
<Scrollable vertical>
|
||||
<Box
|
||||
position='relative'
|
||||
height='100%'
|
||||
width='100%'
|
||||
zIndex={1}
|
||||
backgroundColor='#f6f9fc'
|
||||
display='flex'
|
||||
borderInlineStart='var(--default-border)'
|
||||
overflow='auto'
|
||||
>
|
||||
<BlockEditor extensions={actionBlockExtensions} />
|
||||
</Box>
|
||||
</Scrollable>
|
||||
);
|
||||
|
||||
export default ActionBlockEditor;
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Box, Scrollable } from '@rocket.chat/fuselage';
|
||||
|
||||
import { actionPreviewExtensions } from '../../CodeEditor/Extensions/Extensions';
|
||||
import PreviewEditor from '../../CodeEditor/PreviewEditor';
|
||||
|
||||
const ActionPreviewEditor = () => (
|
||||
<Scrollable vertical>
|
||||
<Box
|
||||
position='relative'
|
||||
height='100%'
|
||||
width='100%'
|
||||
zIndex={1}
|
||||
backgroundColor='#f6f9fc'
|
||||
display='flex'
|
||||
borderInlineStart='var(--default-border)'
|
||||
overflow='auto'
|
||||
>
|
||||
<PreviewEditor extensions={actionPreviewExtensions} />
|
||||
</Box>
|
||||
</Scrollable>
|
||||
);
|
||||
|
||||
export default ActionPreviewEditor;
|
||||
@@ -0,0 +1,88 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { Fragment, useContext } from 'react';
|
||||
|
||||
import ActionBlockEditor from './ActionBlockEditor';
|
||||
import ActionPreviewEditor from './ActionPreviewEditor';
|
||||
import { context, editorTabsToggleAction } from '../../../Context';
|
||||
import FlowDiagram from '../../../Pages/FlowDiagram';
|
||||
import PrototypeContainer from '../../PtototypeContainer/PrototypeContainer';
|
||||
import ToggleTabs from '../../ToggleTabs';
|
||||
|
||||
enum TabsItem {
|
||||
ActionBlock,
|
||||
ActionPreview,
|
||||
FlowDiagram,
|
||||
Prototype,
|
||||
}
|
||||
|
||||
const tabsItem = {
|
||||
[TabsItem.ActionBlock]: {
|
||||
name: 'Action Block',
|
||||
Container: ActionBlockEditor,
|
||||
},
|
||||
[TabsItem.ActionPreview]: {
|
||||
name: 'Action Preview',
|
||||
Container: ActionPreviewEditor,
|
||||
},
|
||||
[TabsItem.FlowDiagram]: {
|
||||
name: 'Flow Diagram',
|
||||
Container: FlowDiagram,
|
||||
},
|
||||
[TabsItem.Prototype]: { name: 'Prototype', Container: PrototypeContainer },
|
||||
} as const;
|
||||
|
||||
const EditorPanel = () => {
|
||||
const {
|
||||
state: { editorTabsToggle },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
|
||||
const toggleTabsHandler = (index: number) => {
|
||||
dispatch(editorTabsToggleAction(index));
|
||||
};
|
||||
|
||||
const tabChangeStyle = (index: number) => {
|
||||
return css`
|
||||
transition: 0.5s ease;
|
||||
left: calc(-100% * ${index});
|
||||
`;
|
||||
};
|
||||
|
||||
return (
|
||||
<Box width='100%' height='100%'>
|
||||
<Box
|
||||
position='relative'
|
||||
width='100%'
|
||||
height='100%'
|
||||
overflow='hidden'
|
||||
className={[
|
||||
css`
|
||||
user-select: none;
|
||||
`,
|
||||
]}
|
||||
>
|
||||
<ToggleTabs
|
||||
tabsItem={Object.values(tabsItem).map((item) => item.name)}
|
||||
onChange={toggleTabsHandler}
|
||||
selectedTab={editorTabsToggle}
|
||||
/>
|
||||
<Box position='relative' width='100%' height='calc(100% - 40px)' flexDirection='column'>
|
||||
<Box
|
||||
position='absolute'
|
||||
width={`calc(100% * ${Object.values(tabsItem).length})`}
|
||||
height='100%'
|
||||
display='flex'
|
||||
borderBlockStart='var(--default-border)'
|
||||
className={tabChangeStyle(editorTabsToggle)}
|
||||
>
|
||||
{Object.values(tabsItem).map(({ Container: ContainerComponent }, index) => (
|
||||
<Fragment key={index}>{index === editorTabsToggle ? <ContainerComponent /> : <Box width='100%' height='100%' />}</Fragment>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
export default EditorPanel;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './EditorPanel';
|
||||
@@ -0,0 +1,47 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box, Button } from '@rocket.chat/fuselage';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { context, previewTabsToggleAction, templatesToggleAction } from '../../../Context';
|
||||
import SurfaceSelect from '../../SurfaceSelect';
|
||||
import ToggleTabs from '../../ToggleTabs';
|
||||
|
||||
const NavPanel = () => {
|
||||
const {
|
||||
state: { isMobile, isTablet, previewTabsToggle },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
|
||||
const toggleTabsHandler = (index: number) => {
|
||||
dispatch(previewTabsToggleAction(index));
|
||||
};
|
||||
|
||||
const tabsItem: string[] = ['Preview', 'Editor'];
|
||||
return (
|
||||
<Box
|
||||
width='100%'
|
||||
height='40px'
|
||||
borderBlockEnd='var(--default-border)'
|
||||
display='flex'
|
||||
alignItems='center'
|
||||
zIndex={1}
|
||||
justifyContent={isMobile ? 'flex-end' : 'space-between'}
|
||||
backgroundColor='alternative'
|
||||
className={css`
|
||||
user-select: none;
|
||||
`}
|
||||
>
|
||||
{!isMobile && (
|
||||
<Box display='flex' alignItems='center' flexGrow={0} paddingInlineStart={4}>
|
||||
<SurfaceSelect />
|
||||
<Button marginInlineStart='10px' small warning height='max-content' onClick={() => dispatch(templatesToggleAction(true))}>
|
||||
Templates
|
||||
</Button>
|
||||
</Box>
|
||||
)}
|
||||
{isTablet && <ToggleTabs tabsItem={tabsItem} onChange={toggleTabsHandler} selectedTab={previewTabsToggle} />}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default NavPanel;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './NavPanel';
|
||||
38
apps/uikit-playground/src/Components/Preview/Preview.tsx
Normal file
38
apps/uikit-playground/src/Components/Preview/Preview.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useEffect, useContext } from 'react';
|
||||
|
||||
import NavPanel from './NavPanel';
|
||||
import Wrapper from './Wrapper';
|
||||
import { context, previewTabsToggleAction } from '../../Context';
|
||||
|
||||
const Preview = () => {
|
||||
const {
|
||||
state: { isMobile, isTablet },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(previewTabsToggleAction(0));
|
||||
}, [isTablet, dispatch]);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display='flex'
|
||||
flexGrow={1}
|
||||
backgroundColor='#fff'
|
||||
zIndex={3}
|
||||
height='100%'
|
||||
flexDirection='column'
|
||||
paddingInlineStart={isMobile ? '' : 'var(--sidebar-width)'}
|
||||
className={css`
|
||||
transition: 0.5s ease;
|
||||
`}
|
||||
>
|
||||
<NavPanel />
|
||||
<Wrapper />
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Preview;
|
||||
@@ -0,0 +1,48 @@
|
||||
import './splitPlane.css';
|
||||
import { useEffect, useContext } from 'react';
|
||||
import { Pane, SplitPane } from 'react-split-pane';
|
||||
|
||||
import { context, previewTabsToggleAction } from '../../../Context';
|
||||
import Display from '../Display';
|
||||
import EditorPanel from '../Editor';
|
||||
|
||||
type PreviewSizeType = {
|
||||
blockSize: number;
|
||||
inlineSize: number;
|
||||
};
|
||||
|
||||
export type SplitPlaneContainerProps = {
|
||||
previewSize: Partial<PreviewSizeType>;
|
||||
};
|
||||
|
||||
const SplitPlaneContainer = ({ previewSize }: SplitPlaneContainerProps) => {
|
||||
const {
|
||||
state: { isTablet },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(previewTabsToggleAction(0));
|
||||
}, [isTablet, dispatch]);
|
||||
|
||||
const minSize = 300;
|
||||
const maxSize = (previewSize.inlineSize || 1) - 350;
|
||||
|
||||
return isTablet ? (
|
||||
<>
|
||||
<Display />
|
||||
<EditorPanel />
|
||||
</>
|
||||
) : (
|
||||
<SplitPane resizable={!isTablet}>
|
||||
<Pane minSize={minSize} maxSize={maxSize}>
|
||||
<Display />
|
||||
</Pane>
|
||||
<Pane minSize={minSize} maxSize={maxSize}>
|
||||
<EditorPanel />
|
||||
</Pane>
|
||||
</SplitPane>
|
||||
);
|
||||
};
|
||||
|
||||
export default SplitPlaneContainer;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './SplitPlaneContainer';
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
.Resizer {
|
||||
background: #000;
|
||||
opacity: 0.2;
|
||||
z-index: 10;
|
||||
-moz-box-sizing: border-box;
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
-moz-background-clip: padding;
|
||||
-webkit-background-clip: padding;
|
||||
background-clip: padding-box;
|
||||
}
|
||||
|
||||
.Resizer:hover {
|
||||
-webkit-transition: all 2s ease;
|
||||
transition: all 2s ease;
|
||||
}
|
||||
|
||||
.Resizer.horizontal {
|
||||
height: 11px;
|
||||
margin: -5px 0;
|
||||
border-top: 5px solid rgba(255, 255, 255, 0);
|
||||
border-bottom: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: row-resize;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Resizer.horizontal:hover {
|
||||
border-top: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.Resizer.vertical {
|
||||
width: 11px;
|
||||
margin: 0 -5px;
|
||||
border-left: 5px solid rgba(255, 255, 255, 0);
|
||||
border-right: 5px solid rgba(255, 255, 255, 0);
|
||||
cursor: col-resize;
|
||||
}
|
||||
|
||||
.Resizer.vertical:hover {
|
||||
border-left: 5px solid rgba(0, 0, 0, 0.5);
|
||||
border-right: 5px solid rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
.Resizer.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.Resizer.disabled:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.pane1Style{
|
||||
width: 40%
|
||||
}
|
||||
41
apps/uikit-playground/src/Components/Preview/Wrapper.tsx
Normal file
41
apps/uikit-playground/src/Components/Preview/Wrapper.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useResizeObserver } from '@rocket.chat/fuselage-hooks';
|
||||
import { useContext } from 'react';
|
||||
|
||||
import { context } from '../../Context';
|
||||
import SplitPlaneContainer from './SplitPlaneContainer/SplitPlaneContainer';
|
||||
|
||||
const Wrapper = () => {
|
||||
const { ref, contentBoxSize } = useResizeObserver();
|
||||
const {
|
||||
state: { isTablet, previewTabsToggle },
|
||||
} = useContext(context);
|
||||
|
||||
const tabChangeStyle = () => {
|
||||
switch (previewTabsToggle) {
|
||||
case 0:
|
||||
return css`
|
||||
transition: 0.5s ease;
|
||||
left: 0;
|
||||
`;
|
||||
case 1:
|
||||
return css`
|
||||
transition: 0.5s ease;
|
||||
left: -100%;
|
||||
`;
|
||||
default:
|
||||
return undefined;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Box position='relative' width='100%' flexGrow={1}>
|
||||
<Box position='absolute' width={isTablet ? '200%' : '100%'} height='100%' display='flex' ref={ref} className={tabChangeStyle()}>
|
||||
<SplitPlaneContainer previewSize={contentBoxSize} />
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default Wrapper;
|
||||
1
apps/uikit-playground/src/Components/Preview/index.tsx
Normal file
1
apps/uikit-playground/src/Components/Preview/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Preview';
|
||||
@@ -0,0 +1,41 @@
|
||||
@use '../../cssVariables.css';
|
||||
|
||||
.rc-prototype-renderer {
|
||||
background: white;
|
||||
padding: 20px !important;
|
||||
|
||||
.rc-prototype_action{
|
||||
position: relative;
|
||||
|
||||
}
|
||||
|
||||
.rcx-message{
|
||||
&:hover{
|
||||
background: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.rc-prototype_action-glow {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
animation: glow 1s ease-out both;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1000;
|
||||
background-color: var(--RCPG-tertary-color);
|
||||
}
|
||||
|
||||
|
||||
@keyframes glow {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
2% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import './PrototypeRender.scss';
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Box } from '@rocket.chat/fuselage';
|
||||
import { useRef, useState } from 'react';
|
||||
import type { Edge } from 'reactflow';
|
||||
|
||||
import type { ILayoutBlock, idType } from '../../Context/initialState';
|
||||
import SurfaceRender from '../Preview/Display/Surface/SurfaceRender';
|
||||
import type { SurfaceOptions } from '../Preview/Display/Surface/constant';
|
||||
import RenderPayload from '../RenderPayload/RenderPayload';
|
||||
|
||||
const PrototypeRender = ({
|
||||
blocks,
|
||||
surface,
|
||||
flowEdges,
|
||||
activeActions,
|
||||
onSelectAction,
|
||||
}: {
|
||||
blocks: ILayoutBlock[];
|
||||
surface: SurfaceOptions;
|
||||
flowEdges: Edge[];
|
||||
activeActions: idType[];
|
||||
onSelectAction: (id: idType) => void;
|
||||
}) => {
|
||||
const timerRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||
|
||||
const [glowActive, setGlowActive] = useState(false);
|
||||
const actionClickHandler = (id: idType) => {
|
||||
const edge = flowEdges.find((edge) => edge.sourceHandle === id);
|
||||
if (edge) return onSelectAction(edge.target);
|
||||
|
||||
clearTimeout(timerRef.current);
|
||||
|
||||
setGlowActive(false);
|
||||
setTimeout(() => {
|
||||
setGlowActive(true);
|
||||
}, 0);
|
||||
timerRef.current = setTimeout(() => {
|
||||
setGlowActive(false);
|
||||
}, 1000);
|
||||
};
|
||||
return (
|
||||
<Box height='max-content' marginBlock='auto' className='rc-prototype-renderer'>
|
||||
<SurfaceRender type={surface}>
|
||||
{blocks.map((action, id) => (
|
||||
<Box
|
||||
key={id}
|
||||
className={css`
|
||||
cursor: pointer;
|
||||
`}
|
||||
onClick={() => actionClickHandler(action.actionId)}
|
||||
>
|
||||
<Box
|
||||
position='relative'
|
||||
width='100%'
|
||||
height='100%'
|
||||
className={css`
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
`}
|
||||
>
|
||||
{glowActive && activeActions.includes(action.actionId) && <Box className='rc-prototype_action-glow' />}
|
||||
<RenderPayload blocks={[action]} surface={surface} />
|
||||
</Box>
|
||||
</Box>
|
||||
))}
|
||||
</SurfaceRender>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrototypeRender;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './PrototypeRender';
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Box, Scrollable } from '@rocket.chat/fuselage';
|
||||
import { useContext, useMemo, useState } from 'react';
|
||||
|
||||
import { context } from '../../Context';
|
||||
import PrototypeRender from '../PrototypeRender/PrototypeRender';
|
||||
|
||||
const PrototypeContainer = () => {
|
||||
const {
|
||||
state: { projects, activeProject, screens },
|
||||
} = useContext(context);
|
||||
|
||||
const [currentScreenID, setCurrentScreenID] = useState<string>(projects[activeProject].screens[0]);
|
||||
|
||||
const activeActions = useMemo(() => {
|
||||
return projects[activeProject]?.flowEdges.map((edge) => edge.sourceHandle);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [activeProject, projects, projects[activeProject].flowEdges]);
|
||||
|
||||
if (!projects[activeProject].screens.length) return null;
|
||||
|
||||
const { surface, blocks } = screens[currentScreenID]?.payload || screens[projects[activeProject].screens[0]].payload;
|
||||
|
||||
return (
|
||||
<Scrollable vertical>
|
||||
<Box width='100%' height='100%'>
|
||||
<PrototypeRender
|
||||
surface={surface}
|
||||
blocks={blocks}
|
||||
activeActions={activeActions as string[]}
|
||||
flowEdges={projects[activeProject].flowEdges}
|
||||
onSelectAction={setCurrentScreenID}
|
||||
/>
|
||||
</Box>
|
||||
</Scrollable>
|
||||
);
|
||||
};
|
||||
|
||||
export default PrototypeContainer;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './PrototypeContainer';
|
||||
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
UiKitModal as uiKitModal,
|
||||
UiKitBanner as uiKitBanner,
|
||||
UiKitMessage as uiKitMessage,
|
||||
UiKitContextualBar as uiKitContextualBar,
|
||||
} from '@rocket.chat/fuselage-ui-kit';
|
||||
|
||||
import type { ILayoutBlock } from '../../Context/initialState';
|
||||
import { SurfaceOptions } from '../Preview/Display/Surface/constant';
|
||||
|
||||
const RenderPayload = ({
|
||||
blocks,
|
||||
surface = SurfaceOptions.Message,
|
||||
}: {
|
||||
index?: number;
|
||||
blocks: ILayoutBlock[];
|
||||
surface?: SurfaceOptions;
|
||||
}) => {
|
||||
switch (surface) {
|
||||
case SurfaceOptions.Message:
|
||||
return uiKitMessage(blocks);
|
||||
|
||||
case SurfaceOptions.Banner:
|
||||
return uiKitBanner(blocks);
|
||||
|
||||
case SurfaceOptions.Modal:
|
||||
return uiKitModal(blocks);
|
||||
|
||||
case SurfaceOptions.ContextualBar:
|
||||
return uiKitContextualBar(blocks);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export default RenderPayload;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './RenderPayload';
|
||||
15
apps/uikit-playground/src/Components/Routes/HomeLayout.tsx
Normal file
15
apps/uikit-playground/src/Components/Routes/HomeLayout.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { Navigate, useOutlet } from 'react-router-dom';
|
||||
|
||||
import routes from '../../Routes/Routes';
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
|
||||
export const HomeLayout = () => {
|
||||
const { user } = useAuth();
|
||||
const outlet = useOutlet();
|
||||
|
||||
if (user) {
|
||||
return <Navigate to={`${routes.home}`} replace />;
|
||||
}
|
||||
|
||||
return <>{outlet}</>;
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
import { useContext, useEffect } from 'react';
|
||||
import { useNavigate, useOutlet, useParams } from 'react-router-dom';
|
||||
|
||||
import { activeProjectAction, context } from '../../Context';
|
||||
import routes from '../../Routes/Routes';
|
||||
|
||||
export const ProjectSpecificLayout = () => {
|
||||
const {
|
||||
state: { projects },
|
||||
dispatch,
|
||||
} = useContext(context);
|
||||
const { projectId } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const outlet = useOutlet();
|
||||
|
||||
useEffect(() => {
|
||||
if (!projectId || !projects[projectId]) navigate(routes.home);
|
||||
else dispatch(activeProjectAction(projectId));
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return <>{outlet}</>;
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Navigate, useOutlet } from 'react-router-dom';
|
||||
|
||||
import routes from '../../Routes/Routes';
|
||||
import { useAuth } from '../../hooks/useAuth';
|
||||
|
||||
export const ProtectedLayout = () => {
|
||||
const { user } = useAuth();
|
||||
const outlet = useOutlet();
|
||||
|
||||
if (!user) {
|
||||
return <Navigate to={`${routes.login}`} />;
|
||||
}
|
||||
|
||||
return <>{outlet}</>;
|
||||
};
|
||||
@@ -0,0 +1,31 @@
|
||||
import { css } from '@rocket.chat/css-in-js';
|
||||
import { Icon, Box } from '@rocket.chat/fuselage';
|
||||
import type { ComponentProps } from 'react';
|
||||
|
||||
const CreateNewScreenButton = ({
|
||||
size = '60px',
|
||||
name = 'plus',
|
||||
...props
|
||||
}: {
|
||||
size?: ComponentProps<typeof Icon>['size'];
|
||||
} & ComponentProps<typeof Icon>) => {
|
||||
return (
|
||||
<Box width={size} height={size}>
|
||||
<Icon
|
||||
{...props}
|
||||
size={size}
|
||||
name={name}
|
||||
className={css`
|
||||
cursor: pointer;
|
||||
transition: var(--animation-default);
|
||||
&:hover {
|
||||
scale: 1.1;
|
||||
transition: var(--animation-default);
|
||||
}
|
||||
`}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default CreateNewScreenButton;
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Box, Icon, Button, Divider, Option } from '@rocket.chat/fuselage';
|
||||
import { useOutsideClick } from '@rocket.chat/fuselage-hooks';
|
||||
import { type ComponentProps, useRef, useState, type ChangeEvent } from 'react';
|
||||
|
||||
import { formatDate } from '../../../utils/formatDate';
|
||||
import EditableLabel from '../EditableLabel/EditableLabel';
|
||||
|
||||
const EditMenu = ({
|
||||
name,
|
||||
date,
|
||||
onChange,
|
||||
onBlur,
|
||||
onDuplicate,
|
||||
onDelete,
|
||||
labelProps,
|
||||
}: {
|
||||
name: string;
|
||||
date: string;
|
||||
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
|
||||
onDuplicate?: () => void;
|
||||
onDelete?: () => void;
|
||||
onBlur: () => void;
|
||||
labelProps?: ComponentProps<typeof EditableLabel>;
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const buttonRef = useRef<HTMLButtonElement>(null);
|
||||
const containerRef = useRef<HTMLDivElement>(null);
|
||||
useOutsideClick([containerRef], () => {
|
||||
setIsOpen(false);
|
||||
onBlur();
|
||||
});
|
||||
|
||||
const duplicatehandler = (e: { stopPropagation: () => void }) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(false);
|
||||
onDuplicate?.();
|
||||
};
|
||||
|
||||
const deleteHandler = (e: { stopPropagation: () => void }) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(false);
|
||||
onDelete?.();
|
||||
};
|
||||
return (
|
||||
<Box
|
||||
position='absolute'
|
||||
insetBlockStart='10px'
|
||||
insetInlineEnd='10px'
|
||||
zIndex={100}
|
||||
ref={containerRef}
|
||||
className='rc-edit-menu'
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setIsOpen(true);
|
||||
}}
|
||||
>
|
||||
<Button ref={buttonRef} square mini>
|
||||
<Icon name='cog' size='x16' />
|
||||
</Button>
|
||||
{isOpen && (
|
||||
<Box position='absolute'>
|
||||
<Box elevation='1' paddingBlock='10px' backgroundColor='white'>
|
||||
<Box marginInline='10px'>
|
||||
<EditableLabel value={name} onChange={onChange} onBlur={onBlur} {...labelProps} />
|
||||
<Box withTruncatedText marginBlockStart='2px' fontScale='p2'>
|
||||
{formatDate(date)}
|
||||
</Box>
|
||||
</Box>
|
||||
<Divider marginBlockStart='12px' />
|
||||
<Option disabled={!onDuplicate} onClick={duplicatehandler}>
|
||||
Duplicate
|
||||
</Option>
|
||||
<Option disabled={!onDelete} onClick={deleteHandler}>
|
||||
Delete
|
||||
</Option>
|
||||
</Box>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditMenu;
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from './EditMenu';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user