From 9424b0101da3e70c75a4b4e625062887df1c7efa Mon Sep 17 00:00:00 2001 From: Dong Cai Date: Fri, 20 Oct 2023 23:08:28 +1100 Subject: [PATCH 1/6] feat: add popover --- src/components/PopOver.jsx | 68 +++++++++++++++++++++++++++++ src/style.css | 89 +++++++++++++++++++++++++++++++++++--- 2 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 src/components/PopOver.jsx diff --git a/src/components/PopOver.jsx b/src/components/PopOver.jsx new file mode 100644 index 00000000..92e80c51 --- /dev/null +++ b/src/components/PopOver.jsx @@ -0,0 +1,68 @@ +import { Component, createRef } from 'preact'; +import PropTypes from 'prop-types'; + + +export class Popover extends Component { + constructor(props) { + super(props); + this.state = { + isVisible: false, + }; + this.popoverRef = createRef(); + } + + componentDidMount() { + // Add a click event listener to the document + console.log('xxx') + document.addEventListener('click', this.handleDocumentClick, true); + } + + componentWillUnmount() { + // Remove the click event listener when the component unmounts + console.log('yyy') + document.removeEventListener('click', this.handleDocumentClick); + } + + togglePopover = () => { + this.setState((prevState) => ({ + isVisible: !prevState.isVisible, + })); + }; + + handleDocumentClick = (event) => { + console.log('handleDocumentClick', event.target) + // Check if the click target is outside the popover + if (this.popoverRef.current && !this.popoverRef.current.contains(event.target)) { + this.setState({ isVisible: false }); + } + }; + + + render() { + const { trigger, content } = this.props; + const { isVisible } = this.state; + + return ( +
+
+ {trigger} +
+ {isVisible && ( + <> +
+
+
+ {content} +
+ + )} +
+ ); + } +} + + +Popover.propTypes = { + trigger: PropTypes.node.isRequired, + content: PropTypes.node.isRequired, +} diff --git a/src/style.css b/src/style.css index d6c50cce..276b5588 100644 --- a/src/style.css +++ b/src/style.css @@ -1,4 +1,4 @@ -@import url('https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap'); +@import url("https://fonts.googleapis.com/css2?family=Ubuntu:wght@300;400;500;700&display=swap"); :root { --color-text: #d4cde9; @@ -23,7 +23,7 @@ body { font-size: 87.5%; /* speocifically for mobile when keyboard is open */ position: relative; - font-family: 'Ubuntu', sans-serif; + font-family: "Ubuntu", sans-serif; /* font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif, 'Segoe UI Emoji', 'Segoe UI Symbol'; */ } @@ -46,7 +46,7 @@ textarea { box-sizing: border-box; } -[role='button'] { +[role="button"] { cursor: pointer; } @@ -207,7 +207,7 @@ label { cursor: pointer; } -[class*='hint--']:after { +[class*="hint--"]:after { text-transform: none; font-weight: normal; letter-spacing: 0.5px; @@ -323,7 +323,7 @@ a > svg { } .star:after { - content: '★'; + content: "★"; color: currentColor; } @@ -1600,7 +1600,7 @@ body:not(.is-app) .show-when-app { } .onboard-selection.selected:after { - content: ''; + content: ""; position: absolute; right: -20px; bottom: 40px; @@ -1968,3 +1968,80 @@ ol.editor-nav li { .prod-version.basic { } + +.popover { + position: relative; + display: inline-block; +} + +.popover-trigger { + cursor: pointer; +} + +.popover-content { + position: absolute; + background: #fff; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); + border: 1px solid #ccc; + border-radius: 4px; + padding: 12px; + z-index: 3; + top: calc(100% + 8px); + left: 50%; + transform: translateX(-50%); +} + +.popover-backdrop { + content: ""; + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + background: transparent; + opacity: 0.5; + z-index: 2; +} + +.popover-arrow { + position: absolute; + width: 0; + height: 0; + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-bottom: 8px solid #fff; + top: -8px; + left: 50%; + transform: translateX(-50%); +} + +.share-panel { + position: relative; + color: #000; + width: 300px; +} + +.share-panel ul { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 16px; +} + +.share-panel li { + display: flex; + flex-direction: column; + gap: 8px; +} + +.share-panel li h4, +.share-panel li p { + margin: 0; + padding: 0; +} + +.share-panel li p { + line-height: 1.25; +} From bbbf4a03a233ec623f78a4be1e0a547e5350b873 Mon Sep 17 00:00:00 2001 From: Dong Cai Date: Fri, 20 Oct 2023 23:08:44 +1100 Subject: [PATCH 2/6] feat: add share panel --- src/components/ContentWrap.jsx | 403 +++++++++++++++++---------------- src/components/PopOver.jsx | 5 - src/components/SharePanel.jsx | 28 +++ 3 files changed, 230 insertions(+), 206 deletions(-) create mode 100644 src/components/SharePanel.jsx diff --git a/src/components/ContentWrap.jsx b/src/components/ContentWrap.jsx index 34f61c0a..11191369 100644 --- a/src/components/ContentWrap.jsx +++ b/src/components/ContentWrap.jsx @@ -1,26 +1,24 @@ -import React, {Component} from 'preact'; -import {saveAs} from 'file-saver' +import React, { Component } from 'preact'; +import { saveAs } from 'file-saver' import UserCodeMirror from './UserCodeMirror.jsx'; import Toolbox from './Toolbox.jsx'; import Tabs from './Tabs.jsx'; -import {computeCss, computeHtml, computeJs} from '../computes'; -import {CssModes, HtmlModes, JsModes, modes} from '../codeModes'; -import {getCompleteHtml, loadJS, log} from '../utils'; -import {writeFileAsync} from "../WriteFile"; -import Vue from '!!file-loader!vue/dist/vue.global'; -import Vuex from '!!file-loader!vuex/dist/vuex.global'; -import zenuml from '@zenuml/core'; - -import {Button} from './common'; -import {SplitPane} from './SplitPane.jsx'; -import {trackEvent} from '../analytics'; +import { computeCss, computeHtml, computeJs } from '../computes'; +import { CssModes, HtmlModes, JsModes, modes } from '../codeModes'; +import { getCompleteHtml, loadJS, log } from '../utils'; + +import { Button } from './common'; +import { SplitPane } from './SplitPane.jsx'; +import { trackEvent } from '../analytics'; import CodeMirror from '../CodeMirror'; import 'codemirror/mode/javascript/javascript.js' -import {Console} from './Console'; -import {deferred} from '../deferred'; +import { Console } from './Console'; +import { deferred } from '../deferred'; import CssSettingsModal from './CssSettingsModal'; import codeService from '../services/code_service' -import {alertsService} from '../notifications'; +import { alertsService } from '../notifications'; +import { Popover } from './PopOver.jsx'; +import { SharePanel } from './SharePanel.jsx'; const minCodeWrapSize = 33; @@ -99,7 +97,7 @@ export default class ContentWrap extends Component { this.onCodeChange(editor, change); } async onJsCodeChange(editor, change) { - await this.setState({lineOfCode: editor.doc.size}); + await this.setState({ lineOfCode: editor.doc.size }); this.cmCodes.js = editor.getValue(); this.props.onCodeChange( 'js', @@ -108,7 +106,7 @@ export default class ContentWrap extends Component { ); const targetWindow = this.detachedWindow || document.getElementById('demo-frame').contentWindow; - targetWindow.postMessage({code: this.cmCodes.js}, '*'); + targetWindow.postMessage({ code: this.cmCodes.js }, '*'); } onCursorMove(editor) { @@ -167,11 +165,11 @@ export default class ContentWrap extends Component { } const that = this; that.frame.onload = function () { - that.frame.contentWindow.postMessage({code: that.cmCodes.js}, '*') + that.frame.contentWindow.postMessage({ code: that.cmCodes.js }, '*') } if (this.detachedWindow) { - this.detachedWindow.postMessage({contents}, '*'); + this.detachedWindow.postMessage({ contents }, '*'); } else { this.frame.src = this.frame.src; setTimeout(() => { @@ -188,8 +186,8 @@ export default class ContentWrap extends Component { showErrors(lang, errors) { var editor = this.cm[lang]; - errors.forEach(function(e) { - editor.operation(function() { + errors.forEach(function (e) { + editor.operation(function () { var n = document.createElement('div'); n.setAttribute('data-title', e.message); n.classList.add('gutter-error-marker'); @@ -318,9 +316,8 @@ export default class ContentWrap extends Component { )}px`; // Replace correct css file in LINK tags's href - window.editorThemeLinkTag.href = `lib/codemirror/theme/${ - prefs.editorTheme - }.css`; + window.editorThemeLinkTag.href = `lib/codemirror/theme/${prefs.editorTheme + }.css`; window.fontStyleTag.textContent = window.fontStyleTemplate.textContent.replace( /fontname/g, (prefs.editorFont === 'other' @@ -357,7 +354,7 @@ export default class ContentWrap extends Component { const { currentLayoutMode } = this.props; const prop = currentLayoutMode === 2 || currentLayoutMode === 5 ? 'width' : 'height'; - [htmlCodeEl, cssCodeEl, jsCodeEl].forEach(function(el) { + [htmlCodeEl, cssCodeEl, jsCodeEl].forEach(function (el) { const bounds = el.getBoundingClientRect(); const size = bounds[prop]; if (size < 100) { @@ -424,7 +421,7 @@ export default class ContentWrap extends Component { } async copyImageClickHandler(e) { - if(!navigator.clipboard || !navigator.clipboard.write){ + if (!navigator.clipboard || !navigator.clipboard.write) { this.showCopyErrorNotice(); trackEvent('ui', 'copyImageFailed1'); return; @@ -535,7 +532,7 @@ export default class ContentWrap extends Component { } else if (mode === CssModes.LESS) { loadJS(`${baseTranspilerPath}/less.min.js`).then(setLoadedFlag); } else if (mode === CssModes.SCSS || mode === CssModes.SASS) { - loadJS(`${baseTranspilerPath}/sass.js`).then(function() { + loadJS(`${baseTranspilerPath}/sass.js`).then(function () { window.sass = new Sass(`${baseTranspilerPath}/sass.worker.js`); setLoadedFlag(); }); @@ -626,7 +623,7 @@ export default class ContentWrap extends Component { this.detachedWindow.onload = function () { that.setPreviewContent(true); const frm = that.detachedWindow.document.querySelector('iframe') - frm.onload = function() { + frm.onload = function () { that.detachedWindow.postMessage({ code: that.cmCodes.js }, '*'); } } @@ -665,9 +662,9 @@ export default class ContentWrap extends Component { try { this.consoleCm.replaceRange( arg + - ' ' + - ((arg + '').match(/\[object \w+]/) ? JSON.stringify(arg) : '') + - '\n', + ' ' + + ((arg + '').match(/\[object \w+]/) ? JSON.stringify(arg) : '') + + '\n', { line: Infinity } @@ -691,7 +688,7 @@ export default class ContentWrap extends Component { } async toggleConsole() { - await this.setState({isConsoleOpen: !this.state.isConsoleOpen}); + await this.setState({ isConsoleOpen: !this.state.isConsoleOpen }); trackEvent('ui', 'consoleToggle'); } consoleHeaderDblClickHandler(e) { @@ -729,7 +726,7 @@ export default class ContentWrap extends Component { } } async cssSettingsBtnClickHandler() { - await this.setState({isCssSettingsModalOpen: true}); + await this.setState({ isCssSettingsModalOpen: true }); trackEvent('ui', 'cssSettingsBtnClick'); } cssSettingsChangeHandler(settings) { @@ -765,7 +762,7 @@ export default class ContentWrap extends Component { return (
- (this.tabsRef = tabs)} - onChange={this.onTabChanges.bind(this)} - style="height: 100%;display:flex;flex-direction: column;" - > -
-
- {/*
(this.tabsRef = tabs)} + onChange={this.onTabChanges.bind(this)} + style="height: 100%;display:flex;flex-direction: column;" + > +
+
+ {/*
*/} - - (this.dslEditor = dslEditor)} - options={{ - mode: 'htmlmixed', - profile: 'xhtml', - gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], - noAutocomplete: true, - matchTags: {bothTags: true}, - prettier: true, - prettierParser: 'html', - emmet: true - }} - prefs={this.props.prefs} - autoComplete={this.props.prefs.autoComplete} - onChange={this.onJsCodeChange.bind(this)} - onCursorMove={this.onCursorMove.bind(this)} - onCreation={el => (this.cm.js = el)} - onFocus={this.editorFocusHandler.bind(this)} - /> - {/* Inlet(scope.cm.js); */} + + (this.dslEditor = dslEditor)} + options={{ + mode: 'htmlmixed', + profile: 'xhtml', + gutters: ['CodeMirror-linenumbers', 'CodeMirror-foldgutter'], + noAutocomplete: true, + matchTags: { bothTags: true }, + prettier: true, + prettierParser: 'html', + emmet: true + }} + prefs={this.props.prefs} + autoComplete={this.props.prefs.autoComplete} + onChange={this.onJsCodeChange.bind(this)} + onCursorMove={this.onCursorMove.bind(this)} + onCreation={el => (this.cm.js = el)} + onFocus={this.editorFocusHandler.bind(this)} + /> + {/* Inlet(scope.cm.js); */} +
-
-
-
- -
- - -
- - - - - - {/* + - (this.cssEditor = cssEditor)} - options={{ - mode: 'css', - gutters: [ - 'error-gutter', - 'CodeMirror-linenumbers', - 'CodeMirror-foldgutter' - ], - emmet: true, - prettier: true, - prettierParser: 'css' - }} - prefs={this.props.prefs} - onChange={this.onCssCodeChange.bind(this)} - onCreation={el => (this.cm.css = el)} - onFocus={this.editorFocusHandler.bind(this)} - />
-
-
- -
- {/*
+ +
+ {/*
@@ -936,78 +933,82 @@ export default class ContentWrap extends Component { />
*/} -
- - - - - - - - - - - - - - - - - - - - +
+
FeatureSample
ParticipantParticipantA
ParticipantB
MessageA.messageA()
Asyc messageAlice->Bob: How are you?
Nested messageA.messageA() {'{'}
  B.messageB()
{'}'}
+ + + + + + + + + + + + + + + - + + + + + - - + + - - + + -
FeatureSample
ParticipantParticipantA
ParticipantB
MessageA.messageA()
Asyc messageAlice->Bob: How are you?
Self-messageNested messageA.messageA() {'{'}
  B.messageB()
{'}'}
Self-message internalMessage()
Altif (condition1) {'{'}
  A.methodA()
{'}'} else if (condition2) {'{'}
  B.methodB()
{'}'} else {'{'}
  C.methodC()
{'}'}
Altif (condition1) {'{'}
  A.methodA()
{'}'} else if (condition2) {'{'}
  B.methodB()
{'}'} else {'{'}
  C.methodC()
{'}'}
Loopwhile (condition) {'{'}
  A.methodA()
{'}'}
Loopwhile (condition) {'{'}
  A.methodA()
{'}'}
+ +
-
- +
{/* (this.codeSplitInstance = splitInstance)}*/} + {/*class="code-side"*/} + {/*id="js-code-side"*/} + {/*sizes={this.state.codeSplitSizes}*/} + {/*minSize={minCodeWrapSize}*/} + {/*direction={*/} + {/*this.props.currentLayoutMode === 2 ||*/} + {/*this.props.currentLayoutMode === 5*/} + {/*? 'horizontal'*/} + {/*: 'vertical'*/} + {/*}*/} + {/*onDragStart={this.codeSplitDragStart.bind(this)}*/} + {/*onDragEnd={this.codeSplitDragEnd.bind(this)}*/} + {/*onSplit={splitInstance => (this.codeSplitInstance = splitInstance)}*/} {/*>*/} - {/**/} - {/**/} + {/**/} + {/**/} {/**/}
{window.zenumlDesktop ? (null) : (
- - - + + share + Share + + } + content={ + + } + /> } content={ - + } />
- ) +
+

Preview

+
+ + +
+
+ * Anyone with the link can view the diagram. The view is optimised for Confluence. + } +
+ ); + } } + +SharePanel.propTypes = { + id: PropTypes.string, + dsl: PropTypes.string, +}; diff --git a/src/style.css b/src/style.css index 276b5588..f3e44753 100644 --- a/src/style.css +++ b/src/style.css @@ -1966,9 +1966,6 @@ ol.editor-nav li { padding: 10px 5px; } -.prod-version.basic { -} - .popover { position: relative; display: inline-block; @@ -1984,7 +1981,7 @@ ol.editor-nav li { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); border: 1px solid #ccc; border-radius: 4px; - padding: 12px; + padding: 20px; z-index: 3; top: calc(100% + 8px); left: 50%; @@ -2018,30 +2015,47 @@ ol.editor-nav li { .share-panel { position: relative; color: #000; - width: 300px; + width: 400px; + display: flex; + flex-direction: column; + gap: 4px; } -.share-panel ul { - list-style: none; - padding: 0; +.share-panel p { margin: 0; - display: flex; - flex-direction: column; - gap: 16px; + padding: 0; +} + +.share-panel .loader { + margin: 0 auto; } -.share-panel li { +.share-panel .preview { display: flex; flex-direction: column; + align-items: end; gap: 8px; } -.share-panel li h4, -.share-panel li p { +.share-panel .copy-button { margin: 0; - padding: 0; + padding: 8px 20px; + border: 1px solid #414bb2; + background-color: transparent; + border-radius: 4px; + font-weight: 500; + color: #414bb2; +} + +.share-panel .copy-button:focus { + outline-color: #414bb2; } -.share-panel li p { - line-height: 1.25; +.share-panel .copy-button .material-symbols-outlined { + transform: rotate(315deg); +} +.share-panel .footnote { + margin-top: 8px; + color: #acacac; + font-size: 12px; } From b518368d9d03e56e1be921555cae1b64bb0eb582 Mon Sep 17 00:00:00 2001 From: Dong Cai Date: Wed, 25 Oct 2023 23:05:51 +1100 Subject: [PATCH 4/6] highlight share button --- src/components/ContentWrap.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/ContentWrap.jsx b/src/components/ContentWrap.jsx index d36c458c..2d98a6c9 100644 --- a/src/components/ContentWrap.jsx +++ b/src/components/ContentWrap.jsx @@ -998,7 +998,9 @@ export default class ContentWrap extends Component { share From aefe183c90935b0135bca234ce80dec294325b9d Mon Sep 17 00:00:00 2001 From: dontry Date: Sat, 28 Oct 2023 16:23:56 +1100 Subject: [PATCH 5/6] doc: fix format --- README.md | 2 +- src/components/PopOver.jsx | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index eabeb1e2..9b7645c3 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -#Web Sequence +# Web Sequence [![Gitter](https://badges.gitter.im/zenuml/Lobby.svg)](https://gitter.im/zenuml/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) [![HitCount](http://hits.dwyl.com/zenuml/web-sequence.svg?style=flat-square)](http://hits.dwyl.com/zenuml/web-sequence) diff --git a/src/components/PopOver.jsx b/src/components/PopOver.jsx index 66115632..7b07e411 100644 --- a/src/components/PopOver.jsx +++ b/src/components/PopOver.jsx @@ -16,7 +16,6 @@ export class Popover extends Component { } componentWillUnmount() { - console.log('yyy') document.removeEventListener('click', this.handleDocumentClick); } From 3fd5e724851ace03a6d96eb4a535430c1ca8192f Mon Sep 17 00:00:00 2001 From: dontry Date: Sat, 28 Oct 2023 17:21:26 +1100 Subject: [PATCH 6/6] feat: PreviewCard component --- src/components/PreviewCard.jsx | 26 +++++++++++++++ src/components/SharePanel.jsx | 53 +++++++++++++++++-------------- src/style.css | 58 +++++++++++++++++++++++++++++++++- 3 files changed, 112 insertions(+), 25 deletions(-) create mode 100644 src/components/PreviewCard.jsx diff --git a/src/components/PreviewCard.jsx b/src/components/PreviewCard.jsx new file mode 100644 index 00000000..4a2e9480 --- /dev/null +++ b/src/components/PreviewCard.jsx @@ -0,0 +1,26 @@ +import PropTypes from "prop-types"; + +export function PreviewCard({ title, description, image }) { + return ( +
+
+
+ + {title} +
+
+ {description} +
+
+
+ +
+
+ ); +} + +PreviewCard.propTypes = { + title: PropTypes.string.isRequired, + description: PropTypes.string.isRequired, + image: PropTypes.string.isRequired, +}; diff --git a/src/components/SharePanel.jsx b/src/components/SharePanel.jsx index b0f214ba..75249218 100644 --- a/src/components/SharePanel.jsx +++ b/src/components/SharePanel.jsx @@ -1,6 +1,7 @@ import { Component } from 'preact'; import PropTypes from 'prop-types'; +import { PreviewCard } from './PreviewCard'; export class SharePanel extends Component { @@ -19,7 +20,7 @@ export class SharePanel extends Component { isLoading: false, link: 'https://sequencediagram.zenuml.com/preview/1234', }) - }, 300); + }, 3000); } handleCopyLink = () => { @@ -34,33 +35,35 @@ export class SharePanel extends Component { return (

Share the Diagram on Confluence*

- {isLoading ? -
+ <> +
+

Paste the link on Confluence and select "Display as a Card"

+
- : <> -
-

Paste the link on Confluence and select "Display as a Card"

- -
-
-
-

Preview

-
- - -
+ } + Copy link +
- * Anyone with the link can view the diagram. The view is optimised for Confluence. - } +
+ * Anyone with the link can view the diagram. The view is optimised for Confluence. +
); } @@ -69,4 +72,6 @@ export class SharePanel extends Component { SharePanel.propTypes = { id: PropTypes.string, dsl: PropTypes.string, + email: PropTypes.string, + image: PropTypes.string, }; diff --git a/src/style.css b/src/style.css index f3e44753..7f567b84 100644 --- a/src/style.css +++ b/src/style.css @@ -2038,8 +2038,10 @@ ol.editor-nav li { } .share-panel .copy-button { + display: flex; + gap: 4px; margin: 0; - padding: 8px 20px; + padding: 8px 20px 8px 16px; border: 1px solid #414bb2; background-color: transparent; border-radius: 4px; @@ -2054,8 +2056,62 @@ ol.editor-nav li { .share-panel .copy-button .material-symbols-outlined { transform: rotate(315deg); } + .share-panel .footnote { margin-top: 8px; color: #acacac; font-size: 12px; } + +.preview-card { + position: relative; + display: flex; + width: 100%; + padding: 12px; + border: 1px solid #414bb2; + border-radius: 4px; + justify-content: space-between; + box-sizing: border-box; + border: 1px solid #dfe1e6; + margin-bottom: 8px; +} + +.preview-card__content { + display: flex; + flex-direction: column; + gap: 20px; +} + +.preview-card__title { + display: flex; + gap: 8px; + align-items: center; + font-size: 14px; + color: #0052cc; + font-weight: 500; +} + +.preview-card__title img { + width: 16px; + height: 16px; +} + +.preview-card__description { + font-size: 12px; +} + +.preview-card__image { + position: absolute; + width: 30%; + top: 0; + bottom: 0; + right: 0; + overflow: hidden; +} + +.preview-card__image img { + object-fit: cover; + object-position: left top; + width: 200%; + height: 200%; +}