├── .gitignore ├── test.rp ├── postcss.config.js ├── src ├── components │ ├── ButtonComponent │ │ ├── buttonOptions.js │ │ ├── styles.scss │ │ └── index.js │ ├── IconComponent │ │ ├── styles.scss │ │ └── index.js │ ├── DimensionLineComponent │ │ ├── constants.js │ │ ├── styles.scss │ │ └── index.js │ ├── CodeBadgeComponent │ │ ├── styles.scss │ │ └── index.js │ ├── CheckboxComponent │ │ ├── styles.scss │ │ └── index.js │ ├── DimensionMarkerComponent │ │ ├── styles.scss │ │ └── index.js │ ├── TooltipComponent │ │ ├── index.js │ │ └── styles.scss │ ├── LoadingIndicatorComponent │ │ ├── index.js │ │ └── styles.scss │ ├── TextAreaComponent │ │ ├── styles.scss │ │ └── index.js │ ├── SelectComponent │ │ ├── styles.scss │ │ └── index.js │ ├── InputComponent │ │ ├── styles.scss │ │ └── index.js │ ├── ColorSwatchComponent │ │ ├── index.js │ │ └── styles.scss │ └── ModalComponent │ │ ├── styles.scss │ │ └── index.js ├── icons │ ├── index.js │ ├── share.svg │ ├── artboard.svg │ └── codeInspect.svg ├── styles.scss ├── modules │ ├── HeaderModule │ │ ├── styles.scss │ │ └── index.js │ ├── GridOverlayModule │ │ ├── styles.scss │ │ ├── config.js │ │ └── index.js │ ├── SharingLinksModule │ │ ├── styles.scss │ │ └── index.js │ ├── ArtboardModule │ │ ├── styles.scss │ │ └── index.js │ ├── PrimaryControlsModule │ │ ├── styles.scss │ │ └── index.js │ ├── EnableToolModule │ │ ├── index.js │ │ └── styles.scss │ ├── SplashScreenModule │ │ ├── styles.scss │ │ └── index.js │ ├── ZoomControlModule │ │ ├── styles.scss │ │ └── index.js │ ├── SelectedElementModule │ │ └── index.js │ ├── ElementInteractionModule │ │ └── index.js │ ├── HoveredElementModule │ │ └── index.js │ ├── ElementPropertiesSidebarModule │ │ └── styles.scss │ └── InterElementDimensionsModule │ │ └── index.js ├── utils │ ├── calculateGlobalOffset.js │ ├── calculateTrueArtboardOffset.js │ ├── storage.js │ ├── compileCSSAttributes.js │ └── cssColors.js ├── globalConstants.js ├── styles │ ├── constants.scss │ └── axureComponentStyling.scss ├── index.js ├── interfacers │ ├── eventsInterfacer.js │ └── artboardInterfacer.js └── index.html ├── .babelrc ├── LICENSE ├── config.js ├── scripts ├── WatchBundlePlugin.js └── serveInject.js ├── webpack.config.js ├── .eslintrc ├── package.json ├── .stylelintrc.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /dist/*.map 3 | /dist/*.html 4 | -------------------------------------------------------------------------------- /test.rp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/srm985/axure-redline-tool/HEAD/test.rp -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const autoprefixer = require('autoprefixer'); 2 | 3 | module.exports = { 4 | plugins: [ 5 | autoprefixer 6 | ] 7 | }; 8 | -------------------------------------------------------------------------------- /src/components/ButtonComponent/buttonOptions.js: -------------------------------------------------------------------------------- 1 | const buttonLevels = { 2 | primary: 'primary', 3 | secondary: 'secondary' 4 | }; 5 | 6 | export default buttonLevels; 7 | -------------------------------------------------------------------------------- /src/components/IconComponent/styles.scss: -------------------------------------------------------------------------------- 1 | .IconComponent { 2 | svg { 3 | width: inherit; 4 | height: inherit; 5 | 6 | color: inherit; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/icons/index.js: -------------------------------------------------------------------------------- 1 | import artboard from './artboard.svg'; 2 | import codeInspect from './codeInspect.svg'; 3 | import share from './share.svg'; 4 | 5 | export { 6 | artboard, 7 | codeInspect, 8 | share 9 | }; 10 | -------------------------------------------------------------------------------- /src/components/DimensionLineComponent/constants.js: -------------------------------------------------------------------------------- 1 | export const LINE_TYPE_DASHED_HOVERED = 'hovered-dashed'; 2 | export const LINE_TYPE_INTER_ELEMENT = 'inter-element-dimension'; 3 | export const LINE_TYPE_SOLID_HOVERED = 'hovered-solid'; 4 | export const LINE_TYPE_SOLID_SELECTED = 'selected-solid'; 5 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@babel/plugin-proposal-class-properties", 4 | "@babel/plugin-transform-runtime", 5 | "transform-es2017-object-entries" 6 | ], 7 | "presets": [ 8 | "@babel/preset-env", 9 | "@babel/preset-react" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/components/CodeBadgeComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .CodeBadgeComponent { 4 | 5 | padding: 1px 4px; 6 | 7 | font-size: small; 8 | font-family: $font-mono; 9 | 10 | background-color: $tertiary-color-gray; 11 | 12 | border-radius: $border-radius-small; 13 | } 14 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | /* Web Font */ 2 | @import url('https://fonts.googleapis.com/css?family=Lato:400,700'); 3 | @import url('https://fonts.googleapis.com/css?family=Oxygen+Mono'); 4 | 5 | /* Global Constants */ 6 | @import './styles/constants'; 7 | 8 | /* AxShare Override Styling */ 9 | @import './styles/axureComponentStyling'; 10 | 11 | body { 12 | font-family: $font-stack; 13 | } 14 | -------------------------------------------------------------------------------- /src/icons/share.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/components/CheckboxComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .CheckboxComponent { 4 | &__label { 5 | 6 | display: flex; 7 | align-items: center; 8 | justify-content: flex-start; 9 | 10 | color: $primary-color-white; 11 | font-size: 12px; 12 | font-family: $font-stack; 13 | 14 | cursor: pointer; 15 | } 16 | 17 | &__input { 18 | margin: 0 0 0 10px; 19 | 20 | border-radius: 5px; 21 | 22 | cursor: pointer; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/components/DimensionMarkerComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .DimensionMarkerComponent { 4 | 5 | position: absolute; 6 | z-index: $z-index-dimension-marker; 7 | 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | 12 | color: $primary-color-white; 13 | font-weight: bold; 14 | font-size: 10px; 15 | 16 | letter-spacing: 1px; 17 | 18 | background-color: $primary-color-orange; 19 | border-radius: 5px; 20 | 21 | opacity: $ui-opacity; 22 | } 23 | -------------------------------------------------------------------------------- /src/components/CodeBadgeComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | const CodeBadgeComponent = (props) => { 7 | const { 8 | children 9 | } = props; 10 | 11 | return ( 12 | {children} 13 | ); 14 | }; 15 | 16 | CodeBadgeComponent.displayName = 'CodeBadgeComponent'; 17 | 18 | CodeBadgeComponent.propTypes = { 19 | children: PropTypes.node.isRequired 20 | }; 21 | 22 | export default CodeBadgeComponent; 23 | -------------------------------------------------------------------------------- /src/components/IconComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | const displayName = 'IconComponent'; 7 | 8 | const IconComponent = (props) => { 9 | const { icon } = props; 10 | 11 | return ( 12 |
16 | ); 17 | }; 18 | 19 | IconComponent.propTypes = { 20 | icon: PropTypes.string.isRequired 21 | }; 22 | 23 | export default IconComponent; 24 | -------------------------------------------------------------------------------- /src/components/TooltipComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | const TooltipComponent = (props) => { 7 | const { 8 | isVisible 9 | } = props; 10 | 11 | const COMPONENT_NAME = 'TooltipComponent'; 12 | 13 | const tooltipVisibleClass = isVisible ? `${COMPONENT_NAME}--active` : ''; 14 | 15 | return ( 16 | copied 17 | ); 18 | }; 19 | 20 | TooltipComponent.propTypes = { 21 | isVisible: PropTypes.bool.isRequired 22 | }; 23 | 24 | export default TooltipComponent; 25 | -------------------------------------------------------------------------------- /src/components/LoadingIndicatorComponent/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | import './styles.scss'; 4 | 5 | const LoadingIndicatorComponent = () => ( 6 |
7 |
8 |
9 |
10 |
11 |
12 | ); 13 | 14 | LoadingIndicatorComponent.className = 'LoadingIndicatorComponent'; 15 | 16 | export default LoadingIndicatorComponent; 17 | -------------------------------------------------------------------------------- /src/modules/HeaderModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .HeaderModule { 4 | 5 | position: fixed; 6 | top: 0; 7 | left: 0; 8 | z-index: $z-index-header; 9 | 10 | display: flex; 11 | align-items: center; 12 | justify-content: space-between; 13 | 14 | box-sizing: border-box; 15 | width: 100%; 16 | height: $header-height; 17 | padding: 0 30px; 18 | 19 | font-family: $font-stack; 20 | 21 | background-color: $primary-color-background; 22 | 23 | opacity: $ui-opacity; 24 | 25 | &__logo { 26 | 27 | color: $primary-color-white; 28 | font-size: 18px; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/utils/calculateGlobalOffset.js: -------------------------------------------------------------------------------- 1 | const calculateGlobalOffset = (element) => { 2 | const { 3 | height: scaledHeight, 4 | left: scaledLeft, 5 | top: scaledTop, 6 | width: scaledWidth 7 | } = element.getBoundingClientRect(); 8 | 9 | const { 10 | pageXOffset, 11 | pageYOffset 12 | } = window; 13 | 14 | const scaledOffsetLeft = scaledLeft + pageXOffset; 15 | const scaledOffsetTop = scaledTop + pageYOffset; 16 | 17 | return ({ 18 | scaledHeight, 19 | scaledOffsetLeft, 20 | scaledOffsetTop, 21 | scaledWidth 22 | }); 23 | }; 24 | 25 | export default calculateGlobalOffset; 26 | -------------------------------------------------------------------------------- /src/modules/GridOverlayModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .GridOverlayModule { 4 | position: absolute; 5 | top: 0; 6 | left: 0; 7 | z-index: 1; 8 | 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | 13 | width: 100%; 14 | height: 100%; 15 | 16 | pointer-events: none; 17 | 18 | &__container { 19 | display: flex; 20 | align-items: center; 21 | justify-content: space-between; 22 | 23 | width: 100%; 24 | height: 100%; 25 | 26 | opacity: 0.2; 27 | 28 | &--column { 29 | height: 100%; 30 | 31 | background-color: $primary-color-red; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/utils/calculateTrueArtboardOffset.js: -------------------------------------------------------------------------------- 1 | const calculateTrueArtboardOffset = (element) => { 2 | const { 3 | offsetHeight, 4 | offsetWidth 5 | } = element; 6 | 7 | let currentElement = element; 8 | let trueOffsetLeft = 0; 9 | let trueOffsetTop = 0; 10 | 11 | while (currentElement && currentElement.id !== 'base') { 12 | const { 13 | offsetLeft, 14 | offsetParent, 15 | offsetTop 16 | } = currentElement; 17 | 18 | trueOffsetLeft += offsetLeft; 19 | trueOffsetTop += offsetTop; 20 | 21 | currentElement = offsetParent; 22 | } 23 | 24 | return ({ 25 | trueHeight: offsetHeight, 26 | trueOffsetLeft, 27 | trueOffsetTop, 28 | trueWidth: offsetWidth 29 | }); 30 | }; 31 | 32 | export default calculateTrueArtboardOffset; 33 | -------------------------------------------------------------------------------- /src/modules/SharingLinksModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .SharingLinksModule { 4 | display: none; 5 | 6 | &__modal-block { 7 | h1 { 8 | margin: 0; 9 | 10 | color: $secondary-color-gray; 11 | } 12 | 13 | .InputComponent { 14 | height: 50px; 15 | margin-top: 20px; 16 | 17 | &__label { 18 | 19 | color: $secondary-color-gray; 20 | font-size: 16px; 21 | } 22 | 23 | &__input { 24 | border: $border-default; 25 | border-radius: $border-radius-large; 26 | } 27 | 28 | .TooltipComponent { 29 | top: 0; 30 | } 31 | } 32 | 33 | .ButtonComponent { 34 | float: right; 35 | 36 | margin-top: 30px; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/components/TextAreaComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .TextAreaComponent { 4 | position: relative; 5 | 6 | &__label { 7 | 8 | position: relative; 9 | 10 | display: block; 11 | 12 | color: $primary-color-white; 13 | font-size: 12px; 14 | font-family: $font-stack; 15 | } 16 | 17 | &__textarea { 18 | 19 | position: relative; 20 | 21 | box-sizing: border-box; 22 | width: 100%; 23 | height: 50px; 24 | margin-top: 0; 25 | padding: 4px 5px; 26 | 27 | color: $secondary-color-gray; 28 | font-size: 12px; 29 | font-family: $font-stack; 30 | 31 | border: none; 32 | border-radius: 3px; 33 | outline: none; 34 | cursor: text; 35 | 36 | resize: none; 37 | } 38 | 39 | .TooltipComponent { 40 | top: -11px; 41 | left: 0; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/components/SelectComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .SelectComponent { 4 | position: relative; 5 | 6 | &--disabled { 7 | opacity: 0.5; 8 | } 9 | 10 | &__label { 11 | 12 | position: relative; 13 | 14 | display: block; 15 | 16 | color: $primary-color-white; 17 | font-size: 12px; 18 | font-family: $font-stack; 19 | } 20 | 21 | &__select { 22 | 23 | position: relative; 24 | 25 | box-sizing: border-box; 26 | width: 100%; 27 | height: 25px; 28 | margin-top: 0; 29 | padding: 4px 5px; 30 | 31 | color: $secondary-color-gray; 32 | font-size: 12px; 33 | font-family: $font-stack; 34 | 35 | border: none; 36 | border-radius: 3px; 37 | outline: none; 38 | 39 | cursor: pointer; 40 | 41 | &--option { 42 | cursor: pointer; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/modules/GridOverlayModule/config.js: -------------------------------------------------------------------------------- 1 | // Add grid constants here. 2 | const BOOTSTRAP4 = 'BOOTSTRAP_4'; 3 | 4 | // Grid constants are added to this array for our dropdown menu. 5 | export const GRID_OPTIONS = [ 6 | BOOTSTRAP4 7 | ]; 8 | 9 | // This object allows us to apply a vanity name to our object keys. 10 | export const GRID_OPTION_VANITY = { 11 | [BOOTSTRAP4]: 'Bootstrap 4' 12 | }; 13 | 14 | export const gridLayouts = { 15 | [BOOTSTRAP4]: { 16 | breakpoints: [ 17 | { 18 | maxWidth: 540, 19 | viewportWidth: 576 20 | }, { 21 | maxWidth: 720, 22 | viewportWidth: 768 23 | }, { 24 | maxWidth: 960, 25 | viewportWidth: 992 26 | }, { 27 | maxWidth: 1140, 28 | viewportWidth: 1200 29 | } 30 | ], 31 | columns: 12, 32 | gutterWidth: 30 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /src/modules/ArtboardModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .ArtboardModule { 4 | @extend %background; 5 | 6 | position: absolute; 7 | 8 | display: flex; 9 | align-items: center; 10 | justify-content: center; 11 | 12 | &__artboard { 13 | position: absolute; 14 | } 15 | 16 | #base { 17 | position: relative; 18 | 19 | width: 100%; 20 | height: 100%; 21 | 22 | transform-origin: center; 23 | } 24 | 25 | &--enabled { 26 | .ArtboardModule__artboard { 27 | * { 28 | &:not(.ui-dialog) { 29 | cursor: pointer; 30 | } 31 | } 32 | } 33 | } 34 | 35 | &--shown { 36 | position: relative; 37 | 38 | display: inline-block; 39 | padding-top: $header-height; 40 | 41 | .ArtboardModule__artboard { 42 | transform-origin: top left; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/modules/PrimaryControlsModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .PrimaryControlsModule { 4 | display: flex; 5 | align-items: center; 6 | 7 | height: 100%; 8 | 9 | &__control { 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | 14 | width: $header-height; 15 | height: 100%; 16 | 17 | color: $primary-color-white; 18 | 19 | border-radius: 5px; 20 | 21 | cursor: pointer; 22 | 23 | transition: background-color 0.2s ease-in-out, color 0.1s ease-in-out; 24 | 25 | &:hover { 26 | 27 | background-color: $primary-color-gray; 28 | 29 | transition: background-color 0.05s ease-in-out, color 0.1s ease-in-out; 30 | } 31 | 32 | &--enabled { 33 | color: $primary-color-teal; 34 | } 35 | } 36 | 37 | .IconComponent { 38 | width: 25px; 39 | height: 25px; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/EnableToolModule/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | const displayName = 'EnableToolModule'; 7 | 8 | const EnableToolModule = (props) => { 9 | const { 10 | isToolEnabled, 11 | toggleToolEnable 12 | } = props; 13 | 14 | return ( 15 |
16 | Tool Enable: 17 |
18 | toggleToolEnable()} 23 | /> 24 |
26 |
27 | ); 28 | }; 29 | 30 | EnableToolModule.propTypes = { 31 | isToolEnabled: PropTypes.bool.isRequired, 32 | toggleToolEnable: PropTypes.func.isRequired 33 | }; 34 | 35 | export default EnableToolModule; 36 | -------------------------------------------------------------------------------- /src/components/InputComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .InputComponent { 4 | position: relative; 5 | 6 | display: flex; 7 | flex-direction: column; 8 | align-items: flex-start; 9 | justify-content: space-between; 10 | 11 | width: 100%; 12 | height: 40px; 13 | 14 | &__label { 15 | 16 | color: $primary-color-white; 17 | font-size: 12px; 18 | font-family: $font-stack; 19 | } 20 | 21 | &__input { 22 | 23 | position: relative; 24 | 25 | box-sizing: border-box; 26 | width: 100%; 27 | height: 25px; 28 | margin-top: 0; 29 | padding: 4px 5px; 30 | 31 | color: $secondary-color-gray; 32 | font-size: 12px; 33 | font-family: $font-stack; 34 | 35 | border: none; 36 | border-radius: 3px; 37 | outline: none; 38 | 39 | cursor: text; 40 | } 41 | 42 | .TooltipComponent { 43 | top: -11px; 44 | left: 0; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/DimensionLineComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .DimensionLineComponent { 4 | position: absolute; 5 | 6 | &--hovered-solid { 7 | z-index: $z-index-hover-solid-line; 8 | 9 | border-color: $primary-color-blue; 10 | border-style: solid; 11 | 12 | border-width: 0; 13 | } 14 | 15 | &--hovered-dashed { 16 | z-index: $z-index-hover-dashed-line; 17 | 18 | border-color: $primary-color-blue; 19 | border-style: dashed; 20 | 21 | border-width: 0; 22 | } 23 | 24 | &--selected-solid { 25 | z-index: $z-index-selected-solid-line; 26 | 27 | border-color: $primary-color-orange; 28 | border-style: solid; 29 | 30 | border-width: 0; 31 | } 32 | 33 | &--inter-element-dimension { 34 | z-index: $z-index-inter-dimension-line; 35 | 36 | border-color: $primary-color-orange; 37 | border-style: solid; 38 | 39 | border-width: 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/components/TooltipComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .TooltipComponent { 4 | 5 | position: absolute; 6 | z-index: 1; 7 | 8 | box-sizing: border-box; 9 | padding: 3px 5px; 10 | 11 | color: $primary-color-white; 12 | font-size: 12px; 13 | 14 | background-color: $primary-color-orange; 15 | border-radius: 3px; 16 | 17 | visibility: hidden; 18 | 19 | cursor: default; 20 | 21 | opacity: 0; 22 | 23 | transition: opacity ease-in-out 0.5s, visibility ease-in-out 0.5s; 24 | 25 | &::after { 26 | position: absolute; 27 | top: 100%; 28 | left: 50%; 29 | 30 | margin-left: -5px; 31 | 32 | border-color: $primary-color-orange transparent transparent; 33 | border-style: solid; 34 | 35 | border-width: 5px; 36 | 37 | content: ''; 38 | } 39 | 40 | &--active { 41 | visibility: visible; 42 | 43 | opacity: 1; 44 | 45 | transition: opacity 0s, visibility 0s; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/components/ButtonComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .ButtonComponent { 4 | 5 | height: 40px; 6 | padding: 0 30px; 7 | 8 | font-weight: bold; 9 | font-size: 16px; 10 | text-transform: uppercase; 11 | 12 | border: none; 13 | border-radius: $border-radius-large; 14 | outline: none; 15 | 16 | cursor: pointer; 17 | 18 | transition: background-color 0.2s ease-in-out, color 0.1s ease-in-out; 19 | 20 | &--primary { 21 | color: $primary-color-white; 22 | 23 | background-color: $primary-color-teal; 24 | 25 | &:hover { 26 | background-color: darken($color: $primary-color-teal, $amount: 10%); 27 | } 28 | } 29 | 30 | &--secondary { 31 | color: $primary-color-teal; 32 | 33 | background-color: $primary-color-white; 34 | border: solid 1px $primary-color-teal; 35 | 36 | &:hover { 37 | color: $primary-color-white; 38 | 39 | background-color: $primary-color-teal; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/components/CheckboxComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | const displayName = 'CheckboxComponent'; 7 | 8 | const CheckboxComponent = (props) => { 9 | const { 10 | changeCallback, 11 | label 12 | } = props; 13 | 14 | const handleChange = (event) => { 15 | changeCallback(event); 16 | }; 17 | 18 | return ( 19 |
20 | 28 |
29 | ); 30 | }; 31 | 32 | CheckboxComponent.propTypes = { 33 | changeCallback: PropTypes.func.isRequired, 34 | label: PropTypes.string 35 | }; 36 | 37 | CheckboxComponent.defaultProps = { 38 | label: '' 39 | }; 40 | 41 | export default CheckboxComponent; 42 | -------------------------------------------------------------------------------- /src/globalConstants.js: -------------------------------------------------------------------------------- 1 | export const SPLASH_SCREEN_VERSION = 3; 2 | 3 | export const ANNOTATION_ELEMENTS = [ 4 | // RP8 Syntax 5 | '.annnoteimage', 6 | '.annnoteline', 7 | '.annotation', 8 | '.ui-dialog', 9 | '.ui-dialog *', 10 | 11 | // RP9 Syntax 12 | '.annnote', 13 | '.annnote *' 14 | ]; 15 | 16 | export const NO_INTERACT_CLASS = 'no-interact'; 17 | 18 | export const NO_INTERACT_ELEMENTS = [ 19 | 'DimensionLineComponent', 20 | 'DimensionMarkerComponent', 21 | NO_INTERACT_CLASS 22 | ]; 23 | 24 | export const TOOLTIP_VISIBLE_TIME = 750; // 750ms 25 | 26 | // Mapped Keys 27 | export const ENTER_KEY = 13; 28 | export const ESCAPE_KEY = 27; 29 | export const MINUS_KEY = 189; 30 | export const PLUS_KEY = 187; 31 | 32 | // Storage Keys 33 | export const STORE_NAME = 'redlineTool'; 34 | 35 | export const STORE_ARTBOARD_WRAPPER_SHOWN = 'artboardWrapperShown'; 36 | export const STORE_DOCUMENT_ZOOM = 'redline-tool-document-zoom'; 37 | export const STORE_SPLASH_SCREEN = 'redline-tool-splash-screen'; 38 | export const STORE_TOOL_ENABLED = 'redline-tool-enabled'; 39 | -------------------------------------------------------------------------------- /src/icons/artboard.svg: -------------------------------------------------------------------------------- 1 | 6 | 10 | 13 | 16 | 19 | 23 | 27 | 30 | 34 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Sean 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/ColorSwatchComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | class ColorSwatchComponent extends React.PureComponent { 7 | render() { 8 | const { 9 | setSwatchValue, 10 | swatchColor 11 | } = this.props; 12 | 13 | const componentStyle = { 14 | backgroundColor: swatchColor 15 | }; 16 | 17 | return ( 18 |
25 |
29 |
30 |
31 | ); 32 | } 33 | } 34 | 35 | ColorSwatchComponent.displayName = 'ColorSwatchComponent'; 36 | 37 | ColorSwatchComponent.propTypes = { 38 | setSwatchValue: PropTypes.func.isRequired, 39 | swatchColor: PropTypes.string.isRequired 40 | }; 41 | 42 | export default ColorSwatchComponent; 43 | -------------------------------------------------------------------------------- /src/components/LoadingIndicatorComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .LoadingIndicatorComponent { 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | z-index: $z-index-loading-indicator; 8 | 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | width: 100%; 13 | height: 100%; 14 | 15 | background-color: $secondary-color-gray; 16 | 17 | /* Credit: http://tobiasahlin.com/spinkit/ */ 18 | &__spinner { 19 | position: relative; 20 | 21 | width: 40px; 22 | height: 40px; 23 | 24 | &--animation { 25 | position: absolute; 26 | top: 0; 27 | left: 0; 28 | 29 | width: 100%; 30 | height: 100%; 31 | 32 | background-color: $primary-color-white; 33 | border-radius: 50%; 34 | opacity: 0.6; 35 | 36 | animation: sk-bounce 2s infinite ease-in-out; 37 | 38 | &:last-of-type { 39 | animation-delay: -1s; 40 | } 41 | } 42 | 43 | @keyframes sk-bounce { 44 | 0%, 45 | 100% { 46 | transform: scale(0); 47 | } 48 | 49 | 50% { 50 | transform: scale(1); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /config.js: -------------------------------------------------------------------------------- 1 | module.exports = () => ({ 2 | browserSyncConfig: { 3 | ghostMode: true, 4 | https: true, 5 | logConnections: true, 6 | logLevel: 'debug', 7 | logPrefix: 'Redline Tool', 8 | open: true, 9 | port: 3100, 10 | proxyRP8: 'https://tqap6c.axshare.com', 11 | proxyRP9: 'https://98t4tt.axshare.com', 12 | reloadDebounce: 250, 13 | reloadDelay: 50, 14 | reloadOnRestart: true, 15 | serveStatic: ['dist'], 16 | snippetOptions: { 17 | rule: { 18 | fn: (snippet, match) => `${snippet}${match}`, 19 | match: /<\/head>/i 20 | } 21 | }, 22 | ui: { 23 | port: 3101 24 | } 25 | }, 26 | buildTypes: { 27 | development: 'development', 28 | production: 'production' 29 | }, 30 | bundleName: 'plugin', 31 | directories: { 32 | distDirectory: './dist', 33 | legacyWebDirectory: './web', 34 | rootDirectory: './', 35 | srcDirectory: './src', 36 | tasksDirectory: './tasks' 37 | }, 38 | environmentalVariables: { 39 | buildEnvironment: 'BUILD_ENVIRONMENT', 40 | injectedEnvironment: 'INJECTED_ENVIRONMENT' 41 | }, 42 | legacyBundleName: 'axure-redline-plugin', 43 | webpackConfig: '../webpack.config.js' 44 | }); 45 | -------------------------------------------------------------------------------- /src/modules/SplashScreenModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | .SplashScreenModule { 4 | color: $secondary-color-gray; 5 | 6 | &__header { 7 | display: flex; 8 | align-items: center; 9 | 10 | width: 100%; 11 | margin-bottom: 10px; 12 | 13 | .IconComponent { 14 | width: 28px; 15 | height: 28px; 16 | margin-right: 10px; 17 | 18 | color: $primary-color-teal; 19 | } 20 | } 21 | 22 | &__scrolling-body { 23 | 24 | max-height: 50vh; 25 | margin-top: 20px; 26 | overflow-y: auto; 27 | } 28 | 29 | &--disclosure { 30 | font-size: 14px; 31 | font-style: italic; 32 | } 33 | 34 | &--highlight { 35 | 36 | color: $primary-color-teal; 37 | font-weight: bold; 38 | } 39 | 40 | h1 { 41 | margin: 0; 42 | } 43 | 44 | h2 { 45 | margin: 0; 46 | } 47 | 48 | ul { 49 | margin: 10px 0 0; 50 | padding: 0 40px; 51 | } 52 | 53 | li { 54 | margin-bottom: 15px; 55 | 56 | &:last-of-type { 57 | margin-bottom: 0; 58 | } 59 | 60 | .IconComponent { 61 | width: 28px; 62 | height: 28px; 63 | margin-top: 10px; 64 | } 65 | } 66 | 67 | .ButtonComponent { 68 | float: right; 69 | 70 | margin-top: 30px; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/components/ButtonComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import buttonLevels from './buttonOptions'; 5 | 6 | import './styles.scss'; 7 | 8 | const displayName = 'ButtonComponent'; 9 | 10 | const { 11 | primary: primaryButton 12 | } = buttonLevels; 13 | 14 | const ButtonComponent = (props) => { 15 | const { 16 | label, 17 | level, 18 | onClickCallback, 19 | type = 'button' 20 | } = props; 21 | 22 | const { 23 | [level]: buttonLevelProp 24 | } = buttonLevels; 25 | 26 | const buttonLevelClass = `${displayName}--${buttonLevelProp || primaryButton}`; 27 | 28 | const handleClick = () => { 29 | if (typeof onClickCallback === 'function') { 30 | onClickCallback(); 31 | } 32 | }; 33 | 34 | return ( 35 | 42 | ); 43 | }; 44 | 45 | ButtonComponent.propTypes = { 46 | label: PropTypes.string, 47 | level: PropTypes.string, 48 | onClickCallback: PropTypes.func, 49 | type: PropTypes.string 50 | }; 51 | 52 | ButtonComponent.defaultProps = { 53 | label: '', 54 | level: primaryButton, 55 | onClickCallback: () => { }, 56 | type: 'button' 57 | }; 58 | 59 | export default ButtonComponent; 60 | -------------------------------------------------------------------------------- /src/components/DimensionLineComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | class DimensionLineComponent extends React.PureComponent { 7 | render() { 8 | const { 9 | elementMarkerThickness = 0, 10 | height = 0, 11 | left = 0, 12 | lineType = '', 13 | top = 0, 14 | width = 0 15 | } = this.props; 16 | 17 | const componentStyle = { 18 | borderLeftWidth: !width ? elementMarkerThickness : 0, 19 | borderTopWidth: !height ? elementMarkerThickness : 0, 20 | height, 21 | left, 22 | top, 23 | width 24 | }; 25 | 26 | const lineTypeClass = lineType ? `${DimensionLineComponent.displayName}--${lineType}` : ''; 27 | 28 | return ( 29 |
33 | ); 34 | } 35 | } 36 | 37 | DimensionLineComponent.displayName = 'DimensionLineComponent'; 38 | 39 | DimensionLineComponent.propTypes = { 40 | elementMarkerThickness: PropTypes.number.isRequired, 41 | height: PropTypes.number.isRequired, 42 | left: PropTypes.number.isRequired, 43 | lineType: PropTypes.string.isRequired, 44 | top: PropTypes.number.isRequired, 45 | width: PropTypes.number.isRequired 46 | }; 47 | 48 | export default DimensionLineComponent; 49 | -------------------------------------------------------------------------------- /src/components/ColorSwatchComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | /* Checkerboard Styling */ 4 | $grid-size: 5px; 5 | $checkerboard-color: rgba($primary-color-gray, 0.5); 6 | 7 | .ColorSwatchComponent { 8 | position: absolute; 9 | right: 0; 10 | bottom: 0; 11 | z-index: 1; 12 | 13 | width: 25px; 14 | height: 25px; 15 | 16 | border-left: solid 1px $primary-color-background; 17 | border-radius: 0 3px 3px 0; 18 | outline: none; 19 | cursor: pointer; 20 | 21 | &__swatch { 22 | position: absolute; 23 | top: 0; 24 | left: 0; 25 | z-index: 2; 26 | 27 | width: 100%; 28 | height: 100%; 29 | 30 | border-radius: inherit; 31 | } 32 | 33 | &__checkerboard { 34 | position: absolute; 35 | top: 0; 36 | left: 0; 37 | z-index: 1; 38 | 39 | width: 100%; 40 | height: 100%; 41 | 42 | /* stylelint-disable-next-line declaration-colon-space-after */ 43 | background-image: 44 | linear-gradient(45deg, $checkerboard-color 25%, transparent 25%), 45 | linear-gradient(-45deg, $checkerboard-color 25%, transparent 25%), 46 | linear-gradient(45deg, transparent 75%, $checkerboard-color 75%), 47 | linear-gradient(-45deg, transparent 75%, $checkerboard-color 75%); 48 | background-position: 0 0, 0 $grid-size, $grid-size -#{$grid-size}, -#{$grid-size} 0; 49 | background-size: #{$grid-size * 2} #{$grid-size * 2}; 50 | border-radius: inherit; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /scripts/WatchBundlePlugin.js: -------------------------------------------------------------------------------- 1 | const browserSync = require('browser-sync'); 2 | const fs = require('fs'); 3 | 4 | const { 5 | bundleName, 6 | directories: { 7 | distDirectory, 8 | legacyWebDirectory 9 | }, 10 | legacyBundleName 11 | } = require('../config')(); 12 | 13 | class WatchBundlePlugin { 14 | constructor(options = {}) { 15 | const { 16 | isInjectedEnvironment, 17 | isProduction 18 | } = options; 19 | 20 | this.isInjectedEnvironment = isInjectedEnvironment; 21 | this.isProduction = isProduction; 22 | } 23 | 24 | apply = (compiler) => { 25 | compiler.hooks.afterEmit.tap('WatchBundlePlugin', () => { 26 | if (this.isInjectedEnvironment) { 27 | browserSync.reload(); 28 | } else if (this.isProduction) { 29 | fs.readFile(`${distDirectory}/${bundleName}.js`, (error, fileContents) => { 30 | const wrappedFile = ``; 31 | 32 | // Write out our file used for copy and paste into AxShare. 33 | console.log('Repackaging into copy & paste module...'); 34 | fs.writeFile(`${distDirectory}/${bundleName}.txt`, wrappedFile, () => {}); 35 | 36 | // Write out our CDN-served file to legacy directory to prevent breaking changes from V2. 37 | console.log('Repackaging into legacy support module...'); 38 | fs.writeFile(`${legacyWebDirectory}/${legacyBundleName}.js`, fileContents, () => {}); 39 | }); 40 | } 41 | }); 42 | } 43 | } 44 | 45 | module.exports = WatchBundlePlugin; 46 | -------------------------------------------------------------------------------- /src/modules/PrimaryControlsModule/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import Icon from '../../components/IconComponent'; 5 | 6 | import './styles.scss'; 7 | 8 | class PrimaryControlsModule extends React.PureComponent { 9 | render = () => { 10 | const { 11 | controlList = [] 12 | } = this.props; 13 | 14 | const controls = controlList.map((control) => { 15 | const { 16 | callback, 17 | icon, 18 | isEnabled, 19 | title 20 | } = control; 21 | 22 | const enabledClass = isEnabled ? `${PrimaryControlsModule.displayName}__control--enabled` : ''; 23 | 24 | const key = Math.random(); 25 | 26 | return ( 27 |
33 | 34 |
35 | ); 36 | }); 37 | 38 | return ( 39 |
40 | {controls} 41 |
42 | ); 43 | } 44 | } 45 | 46 | PrimaryControlsModule.displayName = 'PrimaryControlsModule'; 47 | 48 | PrimaryControlsModule.propTypes = { 49 | controlList: PropTypes.arrayOf(PropTypes.shape({ 50 | callback: PropTypes.func, 51 | icon: PropTypes.string.isRequired, 52 | isEnabled: PropTypes.bool, 53 | title: PropTypes.string.isRequired 54 | })) 55 | }; 56 | 57 | PrimaryControlsModule.defaultProps = { 58 | controlList: [{ 59 | callback: () => { }, 60 | isEnabled: false 61 | }] 62 | }; 63 | 64 | export default PrimaryControlsModule; 65 | -------------------------------------------------------------------------------- /src/utils/storage.js: -------------------------------------------------------------------------------- 1 | import store from 'store'; 2 | 3 | import { 4 | STORE_NAME, 5 | STORE_TOOL_ENABLED 6 | } from '../globalConstants'; 7 | 8 | const readStore = () => store.get(STORE_NAME) || {}; 9 | 10 | export const storageWrite = (keyName, value) => { 11 | const currentStore = readStore(); 12 | 13 | store.set(STORE_NAME, { 14 | ...currentStore, 15 | [keyName]: value 16 | }); 17 | }; 18 | 19 | /** 20 | * We switched to using the package 'store', but some users 21 | * may already have legacy cookies set. So to ensure they don't 22 | * see duplicate messages, we'll migrate them here. 23 | * 24 | * @param {String} keyName 25 | */ 26 | const migrateLegacyCookies = (keyName) => { 27 | const currentStore = readStore(); 28 | 29 | const { 30 | cookie: cookieListString = '' 31 | } = document; 32 | 33 | const cookieList = cookieListString.split(';'); 34 | 35 | const cookieObject = {}; 36 | 37 | // If we don't already have the key in our store. 38 | if (!(keyName in currentStore)) { 39 | cookieList.forEach((cookie) => { 40 | if (cookie) { 41 | const [ 42 | cookieName, 43 | cookieValue 44 | ] = cookie.split('='); 45 | 46 | cookieObject[cookieName.trim()] = cookieValue.trim(); 47 | } 48 | }); 49 | 50 | let value = cookieObject[keyName]; 51 | 52 | if (value) { 53 | // Special case for tool enabled boolean. 54 | if (keyName === STORE_TOOL_ENABLED) { 55 | value = value === 'true'; 56 | } 57 | 58 | storageWrite(keyName, value); 59 | } 60 | } 61 | }; 62 | 63 | export const storageRead = (keyName) => { 64 | migrateLegacyCookies(keyName); 65 | 66 | const currentStore = readStore(); 67 | 68 | return currentStore[keyName]; 69 | }; 70 | -------------------------------------------------------------------------------- /src/components/SelectComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | const displayName = 'SelectComponent'; 7 | 8 | const SelectComponent = (props) => { 9 | const { 10 | changeCallback, 11 | isDisabled, 12 | label, 13 | options 14 | } = props; 15 | 16 | const selectOptions = options.map((option) => { 17 | const { 18 | name, 19 | value 20 | } = option; 21 | 22 | return ( 23 | 30 | ); 31 | }); 32 | 33 | const handleChange = (event) => { 34 | changeCallback(event); 35 | }; 36 | 37 | const isDisabledClass = isDisabled && `${displayName}--disabled`; 38 | 39 | return ( 40 |
41 | 51 |
52 | ); 53 | }; 54 | 55 | SelectComponent.propTypes = { 56 | changeCallback: PropTypes.func.isRequired, 57 | isDisabled: PropTypes.bool, 58 | label: PropTypes.string, 59 | options: PropTypes.arrayOf( 60 | PropTypes.shape({ 61 | name: PropTypes.string, 62 | value: PropTypes.string 63 | }) 64 | ).isRequired 65 | }; 66 | 67 | SelectComponent.defaultProps = { 68 | isDisabled: false, 69 | label: '' 70 | }; 71 | 72 | export default SelectComponent; 73 | -------------------------------------------------------------------------------- /src/components/DimensionMarkerComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import './styles.scss'; 5 | 6 | export const VERTICAL_DIMENSION = 'VERTICAL'; 7 | export const HORIZONTAL_DIMENSION = 'HORIZONTAL'; 8 | 9 | export class DimensionMarkerComponent extends React.PureComponent { 10 | static height = 20; 11 | static padding = 5; 12 | static width = 30; 13 | 14 | render() { 15 | const { 16 | dimensionType, 17 | measurement, 18 | offsetLeft, 19 | offsetTop 20 | } = this.props; 21 | 22 | let top = 0; 23 | let left = 0; 24 | 25 | if (dimensionType === HORIZONTAL_DIMENSION) { 26 | left = offsetLeft - (DimensionMarkerComponent.width / 2); 27 | top = offsetTop - DimensionMarkerComponent.height - DimensionMarkerComponent.padding; 28 | } else if (dimensionType === VERTICAL_DIMENSION) { 29 | left = offsetLeft + DimensionMarkerComponent.padding; 30 | top = offsetTop - (DimensionMarkerComponent.height / 2); 31 | } 32 | 33 | const componentStyle = { 34 | height: DimensionMarkerComponent.height, 35 | left, 36 | top, 37 | width: DimensionMarkerComponent.width 38 | }; 39 | 40 | const cleanedMeasurement = Math.round(measurement); 41 | 42 | return ( 43 |
47 | {cleanedMeasurement} 48 |
49 | ); 50 | } 51 | } 52 | 53 | DimensionMarkerComponent.displayName = 'DimensionMarkerComponent'; 54 | 55 | DimensionMarkerComponent.propTypes = { 56 | dimensionType: PropTypes.string.isRequired, 57 | measurement: PropTypes.number.isRequired, 58 | offsetLeft: PropTypes.number.isRequired, 59 | offsetTop: PropTypes.number.isRequired 60 | }; 61 | -------------------------------------------------------------------------------- /src/modules/EnableToolModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | $switch-transition: all 0.15s ease-in-out; 4 | 5 | .EnableToolModule { 6 | display: flex; 7 | align-items: center; 8 | justify-content: space-between; 9 | width: 160px; 10 | height: 100%; 11 | 12 | span { 13 | color: $primary-color-white; 14 | font-size: 16px; 15 | font-family: $font-stack; 16 | } 17 | 18 | input { 19 | position: absolute; 20 | 21 | margin-left: -9999px; 22 | 23 | visibility: hidden; 24 | 25 | &:checked { 26 | & + label { 27 | background-color: $primary-color-teal; 28 | 29 | &::after { 30 | margin-left: 32px; 31 | 32 | background-color: $primary-color-teal; 33 | } 34 | } 35 | } 36 | } 37 | 38 | label { 39 | position: relative; 40 | 41 | display: block; 42 | width: 60px; 43 | height: 28px; 44 | padding: 2px; 45 | 46 | background-color: $color-gray; 47 | border-radius: 28px; 48 | outline: none; 49 | cursor: pointer; 50 | 51 | transition: $switch-transition; 52 | 53 | user-select: none; 54 | 55 | &::before { 56 | position: absolute; 57 | top: 2px; 58 | right: 2px; 59 | bottom: 2px; 60 | left: 2px; 61 | 62 | display: block; 63 | 64 | background-color: $primary-color-white; 65 | border-radius: 28px; 66 | 67 | transition: $switch-transition; 68 | 69 | content: ''; 70 | } 71 | 72 | &::after { 73 | position: absolute; 74 | top: 4px; 75 | bottom: 4px; 76 | left: 4px; 77 | 78 | display: block; 79 | width: 24px; 80 | 81 | background-color: $color-gray; 82 | border-radius: 50%; 83 | 84 | transition: $switch-transition; 85 | 86 | content: ''; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /scripts/serveInject.js: -------------------------------------------------------------------------------- 1 | const browserSync = require('browser-sync'); 2 | const inquirer = require('inquirer'); 3 | const webpack = require('webpack'); 4 | 5 | const config = require('../config'); 6 | 7 | const { 8 | browserSyncConfig, 9 | webpackConfig 10 | } = config(); 11 | 12 | const rpVersionChoices = { 13 | promptInput: 'Serve Custom URL', 14 | rp8: 'Serve RP8', 15 | rp9: 'Serve RP9' 16 | }; 17 | 18 | const handlePrompt = async () => { 19 | const { 20 | promptInput, 21 | rp8, 22 | rp9 23 | } = rpVersionChoices; 24 | 25 | return inquirer.prompt({ 26 | choices: [ 27 | rp8, 28 | rp9, 29 | promptInput 30 | ], 31 | message: 'Which version of Axure do you want to serve?', 32 | name: 'rpVersion', 33 | type: 'checkbox' 34 | }); 35 | }; 36 | 37 | const handlePromptCustomURL = async () => { 38 | const { 39 | proxyInput 40 | } = await inquirer.prompt({ 41 | message: 'Please enter custom AxShare URL.', 42 | name: 'proxyInput', 43 | type: 'input' 44 | }); 45 | 46 | return proxyInput; 47 | }; 48 | 49 | const serveInject = async () => { 50 | const { 51 | rp8, 52 | rp9 53 | } = rpVersionChoices; 54 | 55 | const { 56 | proxyRP8, 57 | proxyRP9 58 | } = browserSyncConfig; 59 | 60 | const { 61 | rpVersion: [ 62 | promptSelection 63 | ] = [] 64 | } = await handlePrompt(); 65 | 66 | let proxyURL = ''; 67 | 68 | if (promptSelection === rp8) { 69 | proxyURL = proxyRP8; 70 | } else if (promptSelection === rp9) { 71 | proxyURL = proxyRP9; 72 | } else { 73 | proxyURL = await handlePromptCustomURL(); 74 | } 75 | 76 | browserSync.init({ 77 | ...browserSyncConfig, 78 | proxy: proxyURL 79 | }, () => { 80 | process.env.NODE_ENV = 'development'; 81 | process.env.INJECTED = true; 82 | 83 | const webpackConfigObject = require(webpackConfig)(); // eslint-disable-line global-require 84 | 85 | webpack(webpackConfigObject, (error) => { 86 | console.log(error); 87 | }); 88 | }); 89 | }; 90 | 91 | serveInject(); 92 | -------------------------------------------------------------------------------- /src/styles/constants.scss: -------------------------------------------------------------------------------- 1 | /* stylelint-disable */ 2 | 3 | /* Colors */ 4 | $primary-color-background: rgba(17, 17, 17, 1); 5 | $primary-color-blue: #4860ff; 6 | $primary-color-gray: #555; 7 | $primary-color-orange: #e89a28; 8 | $primary-color-red: #ff5757; 9 | $primary-color-teal: #4edec2; 10 | $primary-color-white: #fff; 11 | $secondary-color-gray: #333; 12 | $tertiary-color-gray: #eee; 13 | $color-gray: #ddd; 14 | 15 | /* Loading Spinner Background */ 16 | $background-color: rgba(0, 0, 0, 0.2); 17 | 18 | /* Artboard Background */ 19 | $artboard-background-color: rgba($primary-color-white, 0.2); 20 | 21 | /* Fonts */ 22 | $font-stack: 'Lato', 23 | sans-serif; 24 | $font-mono: 'Oxygen Mono', monospace; 25 | 26 | /* GUI Opacity */ 27 | $ui-opacity: 0.9; 28 | 29 | /* Header Height */ 30 | $header-height: 50px; 31 | 32 | /* Z-Index Ranking */ 33 | $z-index-modal: 9999999; 34 | $z-index-loading-indicator: 999999; 35 | $z-index-sidebar: 99999; 36 | $z-index-header: 99998; 37 | $z-index-zoom-control: 99997; 38 | $z-index-dialog: 99996; 39 | $z-index-dimension-marker: 99995; 40 | $z-index-selected-solid-line: 99994; 41 | $z-index-hover-dashed-line: 99993; 42 | $z-index-hover-solid-line: 99992; 43 | $z-index-inter-dimension-line: 99991; 44 | 45 | /* Borders */ 46 | $border-default: solid 1px $secondary-color-gray; 47 | 48 | /* Border Radii */ 49 | $border-radius-small: 3px; 50 | $border-radius-large: 5px; 51 | 52 | /* Custom Classes */ 53 | %background { 54 | background-color: $secondary-color-gray; 55 | background-image: radial-gradient($background-color 1px, transparent 0); 56 | background-size: 10px 10px; 57 | } 58 | 59 | /* Exported constants can be imported into JavaScript. */ 60 | :export { 61 | /* sass-lint:disable no-misspelled-properties */ 62 | primaryColorBackground: $primary-color-background; 63 | primaryColorBlue: $primary-color-blue; 64 | primaryColorGray: $primary-color-gray; 65 | primaryColorOrange: $primary-color-orange; 66 | primaryColorRed: $primary-color-red; 67 | primaryColorTeal: $primary-color-teal; 68 | primaryColorWhite: $primary-color-white; 69 | secondaryColorGray: $secondary-color-gray; 70 | tertiaryColorGray: $tertiary-color-gray; 71 | /* sass-lint:enable no-misspelled-properties */ 72 | } 73 | -------------------------------------------------------------------------------- /src/modules/ZoomControlModule/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | $icon-thickness: 2px; 4 | $icon-diameter: 23px; 5 | $icon-border-radius: 5px; 6 | 7 | .ZoomControlModule { 8 | position: fixed; 9 | bottom: 20px; 10 | left: 20px; 11 | z-index: $z-index-zoom-control; 12 | 13 | display: flex; 14 | align-items: flex-start; 15 | justify-content: space-between; 16 | 17 | border-radius: $icon-border-radius; 18 | box-shadow: 0 3px 5px $primary-color-gray; 19 | opacity: $ui-opacity; 20 | 21 | &__zoom-input { 22 | width: 50px; 23 | height: $icon-diameter; 24 | 25 | font-family: $font-stack; 26 | text-align: center; 27 | 28 | border: none; 29 | outline: none; 30 | } 31 | 32 | &__zoom-control { 33 | display: flex; 34 | align-items: center; 35 | justify-content: center; 36 | box-sizing: border-box; 37 | width: $icon-diameter; 38 | height: $icon-diameter; 39 | padding: 6px; 40 | 41 | background-color: $primary-color-teal; 42 | cursor: pointer; 43 | 44 | transition: transform 0.05s ease-in-out; 45 | 46 | &:hover { 47 | transform: scale(1.05); 48 | } 49 | 50 | span { 51 | position: relative; 52 | 53 | display: inline-block; 54 | width: 100%; 55 | height: $icon-thickness; 56 | 57 | background-color: $primary-color-white; 58 | } 59 | 60 | &--negative { 61 | border-right: solid 1px $primary-color-gray; 62 | border-radius: $icon-border-radius 0 0 $icon-border-radius; 63 | } 64 | 65 | &--positive { 66 | border-left: solid 1px $primary-color-gray; 67 | border-radius: 0 $icon-border-radius $icon-border-radius 0; 68 | 69 | span { 70 | &::before { 71 | position: absolute; 72 | top: 0; 73 | right: 0; 74 | 75 | display: inline-block; 76 | width: 100%; 77 | height: $icon-thickness; 78 | 79 | background-color: inherit; 80 | transform: rotate(90deg); 81 | 82 | content: ''; 83 | } 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import InspectView from './views/InspectView'; 5 | 6 | import './styles.scss'; 7 | 8 | // We generate our own container. 9 | const appendNode = () => { 10 | const containerName = 'redline-tool'; 11 | 12 | // See if we already have the container on the page. 13 | let containerNode = document.getElementById(containerName); 14 | 15 | return new Promise((resolve) => { 16 | if (!containerNode) { 17 | // Create our own container and add an ID for tracking. 18 | containerNode = document.createElement('div'); 19 | containerNode.id = containerName; 20 | 21 | new Promise((resolveInner) => { 22 | const attemptAppendNodeInterval = setInterval(() => { 23 | try { 24 | document.body.appendChild(containerNode); 25 | 26 | clearInterval(attemptAppendNodeInterval); 27 | 28 | resolveInner(); 29 | } catch (error) { 30 | // If the page isn't ready yet, this fails. 31 | } 32 | }, 10); 33 | }).then(() => { 34 | const checkNodeAppendedInterval = setInterval(() => { 35 | const foundContainerNode = document.getElementById(containerName); 36 | 37 | // We keep checking until we find the container on the page. 38 | if (foundContainerNode) { 39 | clearInterval(checkNodeAppendedInterval); 40 | 41 | resolve(foundContainerNode); 42 | } 43 | }, 10); 44 | }); 45 | } else { 46 | resolve(containerNode); 47 | } 48 | }); 49 | }; 50 | 51 | appendNode().then((container) => { 52 | /** 53 | * This is a weird check we have to do in dev environments. BrowserSync injection 54 | * match first matches the of the whole page and then the of the 55 | * iframe so we would end up rendering twice. In AxShare, the whole page 56 | * has the class "hashover". 57 | */ 58 | const canRender = !container.parentNode.classList.contains('hashover'); 59 | 60 | if (canRender) { 61 | ReactDOM.render( 62 | , 63 | container 64 | ); 65 | 66 | const { 67 | hot 68 | } = module; 69 | 70 | if (hot) { 71 | hot.accept(); 72 | } 73 | } 74 | }); 75 | -------------------------------------------------------------------------------- /src/modules/GridOverlayModule/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import { gridLayouts } from './config'; 5 | 6 | import './styles.scss'; 7 | 8 | const displayName = 'GridOverlayModule'; 9 | 10 | const GridOverlayModule = (props) => { 11 | const { 12 | artboardWidth, 13 | gridLayout 14 | } = props; 15 | 16 | const { 17 | [gridLayout]: { 18 | breakpoints = [], 19 | columns, 20 | gutterWidth 21 | } = {} 22 | } = gridLayouts; 23 | 24 | const containerWidth = () => { 25 | let width = artboardWidth; 26 | 27 | breakpoints.forEach((breakpoint) => { 28 | const { 29 | // If no max width, it's 100% i.e. artboard width. 30 | maxWidth = artboardWidth, 31 | viewportWidth 32 | } = breakpoint; 33 | 34 | if (artboardWidth >= viewportWidth) { 35 | width = maxWidth; 36 | } 37 | }); 38 | 39 | return width; 40 | }; 41 | 42 | const renderColumns = () => { 43 | const markupBlock = []; 44 | 45 | const columnWidth = (containerWidth() - (gutterWidth * (columns - 1))) / 12; 46 | const margin = gutterWidth / 2; 47 | 48 | for (let i = 0; i < columns; i++) { 49 | markupBlock.push( 50 |
59 | ); 60 | } 61 | 62 | return markupBlock; 63 | }; 64 | 65 | const containerStyles = { 66 | maxWidth: containerWidth() 67 | }; 68 | 69 | return ( 70 | <> 71 | { 72 | gridLayout 73 | && ( 74 |
75 |
79 | {renderColumns()} 80 |
81 |
82 | ) 83 | } 84 | 85 | ); 86 | }; 87 | 88 | GridOverlayModule.propTypes = { 89 | artboardWidth: PropTypes.number.isRequired, 90 | gridLayout: PropTypes.string.isRequired 91 | }; 92 | 93 | export default GridOverlayModule; 94 | -------------------------------------------------------------------------------- /src/modules/SelectedElementModule/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import DimensionLineComponent from '../../components/DimensionLineComponent'; 5 | 6 | import { LINE_TYPE_SOLID_SELECTED } from '../../components/DimensionLineComponent/constants'; 7 | 8 | class SelectedElementModule extends React.PureComponent { 9 | render() { 10 | const { 11 | elementMarkerThickness, 12 | selectedElement: { 13 | height, 14 | offsetLeft, 15 | offsetTop, 16 | width 17 | } 18 | } = this.props; 19 | 20 | return ( 21 | <> 22 | 30 | 38 | 46 | 54 | 55 | ); 56 | } 57 | } 58 | 59 | SelectedElementModule.propTypes = { 60 | elementMarkerThickness: PropTypes.number.isRequired, 61 | selectedElement: PropTypes.shape({ 62 | height: PropTypes.number.isRequired, 63 | offsetLeft: PropTypes.number.isRequired, 64 | offsetTop: PropTypes.number.isRequired, 65 | width: PropTypes.number.isRequired 66 | }).isRequired 67 | }; 68 | 69 | export default SelectedElementModule; 70 | -------------------------------------------------------------------------------- /src/components/ModalComponent/styles.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/constants'; 2 | 3 | $modal-overlay-background: rgba($primary-color-gray, 0.7); 4 | 5 | .modal-open { 6 | overflow: hidden; 7 | } 8 | 9 | .ModalComponent { 10 | position: fixed; 11 | top: 0; 12 | left: 0; 13 | z-index: $z-index-modal; 14 | 15 | display: flex; 16 | align-items: center; 17 | justify-content: center; 18 | box-sizing: border-box; 19 | width: 100%; 20 | height: 100%; 21 | padding: 20px; 22 | 23 | visibility: hidden; 24 | 25 | &__overlay { 26 | position: fixed; 27 | top: 0; 28 | left: 0; 29 | 30 | width: 100%; 31 | height: 100%; 32 | 33 | background-color: $modal-overlay-background; 34 | } 35 | 36 | &__modal { 37 | position: relative; 38 | 39 | box-sizing: border-box; 40 | width: 100%; 41 | max-width: 600px; 42 | min-height: 200px; 43 | max-height: 100%; 44 | margin-top: 50px; 45 | padding: 30px; 46 | 47 | background-color: $primary-color-white; 48 | border-radius: 5px; 49 | box-shadow: 0 3px 5px $secondary-color-gray; 50 | opacity: 0; 51 | 52 | transition: margin-top 0.2s ease-in-out, opacity 0.2s ease-in-out; 53 | 54 | &--body { 55 | width: 100%; 56 | height: 100%; 57 | } 58 | } 59 | 60 | &__modal-close { 61 | position: absolute; 62 | top: 10px; 63 | right: 10px; 64 | 65 | display: flex; 66 | align-items: center; 67 | justify-content: center; 68 | width: 20px; 69 | height: 20px; 70 | 71 | cursor: pointer; 72 | 73 | transition: background-color 0.05s ease-in-out; 74 | 75 | &:hover { 76 | div { 77 | background-color: $primary-color-gray; 78 | 79 | transition: background-color 0.2s ease-in-out; 80 | } 81 | } 82 | 83 | div { 84 | width: 1px; 85 | height: 100%; 86 | 87 | background-color: $secondary-color-gray; 88 | transform: rotate(45deg); 89 | 90 | &::after { 91 | position: absolute; 92 | top: 0; 93 | left: 0; 94 | 95 | width: 1px; 96 | height: 100%; 97 | 98 | background-color: inherit; 99 | transform: rotate(-90deg); 100 | 101 | content: ''; 102 | } 103 | } 104 | } 105 | 106 | &--visible { 107 | visibility: visible; 108 | 109 | .ModalComponent__modal { 110 | margin-top: 0; 111 | 112 | opacity: 1; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/components/TextAreaComponent/index.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | 4 | import TooltipComponent from '../TooltipComponent'; 5 | 6 | import { TOOLTIP_VISIBLE_TIME } from '../../globalConstants'; 7 | 8 | import './styles.scss'; 9 | 10 | class TextAreaComponent extends React.PureComponent { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.state = { 15 | isCopiedTooltipActive: false, 16 | isScrolling: false 17 | }; 18 | } 19 | 20 | /** 21 | * This function handles our mouseup event and selects the 22 | * field text. 23 | * 24 | * @param {mouseup event} event 25 | */ 26 | handleCopy = (event) => { 27 | const { 28 | isScrolling 29 | } = this.state; 30 | 31 | const { 32 | target: inputField 33 | } = event; 34 | 35 | if (!isScrolling) { 36 | inputField.select(); 37 | document.execCommand('Copy'); 38 | 39 | this.setState({ 40 | isCopiedTooltipActive: true 41 | }, () => { 42 | setTimeout(() => { 43 | this.setState({ 44 | isCopiedTooltipActive: false 45 | }); 46 | }, TOOLTIP_VISIBLE_TIME); 47 | }); 48 | } 49 | 50 | // Debounce our scroll event. 51 | setTimeout(() => { 52 | this.setState({ 53 | isScrolling: false 54 | }); 55 | }, 100); 56 | } 57 | 58 | handleScroll = () => { 59 | this.setState({ 60 | isScrolling: true 61 | }); 62 | } 63 | 64 | render() { 65 | const { 66 | inputValue, 67 | label 68 | } = this.props; 69 | 70 | const { 71 | isCopiedTooltipActive 72 | } = this.state; 73 | 74 | return ( 75 |
76 |