├── .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 |
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 |
25 |
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 |
21 | {label}
22 |
27 |
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 |
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 |
40 | {label}
41 |
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 |
28 | {name}
29 |
30 | );
31 | });
32 |
33 | const handleChange = (event) => {
34 | changeCallback(event);
35 | };
36 |
37 | const isDisabledClass = isDisabled && `${displayName}--disabled`;
38 |
39 | return (
40 |
41 |
42 | {label}
43 |
48 | {selectOptions}
49 |
50 |
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 |
77 | {label}
78 |
85 |
86 |
87 |
88 | );
89 | }
90 | }
91 |
92 | TextAreaComponent.displayName = 'TextAreaComponent';
93 |
94 | TextAreaComponent.propTypes = {
95 | inputValue: PropTypes.string.isRequired,
96 | label: PropTypes.string.isRequired
97 | };
98 |
99 | export default TextAreaComponent;
100 |
--------------------------------------------------------------------------------
/src/modules/HeaderModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import PrimaryControlsModule from '../PrimaryControlsModule';
5 | import SharingLinksModule from '../SharingLinksModule';
6 |
7 | import {
8 | artboard,
9 | codeInspect,
10 | share
11 | } from '../../icons';
12 |
13 | import './styles.scss';
14 |
15 | class HeaderModule extends React.PureComponent {
16 | constructor(props) {
17 | super(props);
18 |
19 | this.state = {
20 | isSharingLinksShown: false
21 | };
22 | }
23 |
24 | toggleSharingLinks = () => {
25 | this.setState((prevState) => {
26 | const {
27 | isSharingLinksShown: wasSharingLinksShown
28 | } = prevState;
29 |
30 | return ({
31 | isSharingLinksShown: !wasSharingLinksShown
32 | });
33 | });
34 | }
35 |
36 | render() {
37 | const {
38 | isArtboardWrapperShown,
39 | isToolEnabled,
40 | toggleArtboardWrapperShown,
41 | toggleToolEnable
42 | } = this.props;
43 |
44 | const {
45 | isSharingLinksShown
46 | } = this.state;
47 |
48 | return (
49 |
50 |
RedlineTool
51 |
73 |
77 |
78 | );
79 | }
80 | }
81 |
82 | HeaderModule.displayName = 'HeaderModule';
83 |
84 | HeaderModule.propTypes = {
85 | isArtboardWrapperShown: PropTypes.bool.isRequired,
86 | isToolEnabled: PropTypes.bool.isRequired,
87 | toggleArtboardWrapperShown: PropTypes.func.isRequired,
88 | toggleToolEnable: PropTypes.func.isRequired
89 | };
90 |
91 | export default HeaderModule;
92 |
--------------------------------------------------------------------------------
/src/utils/compileCSSAttributes.js:
--------------------------------------------------------------------------------
1 | const compileCSSAttributes = (pseudoClasses) => {
2 | const {
3 | styleSheets: documentStyles = {}
4 | } = document;
5 |
6 | const documentCSSList = {};
7 |
8 | let selectorName;
9 | let cssContent;
10 | let pseudoFilter;
11 | let matched;
12 | let attributeObject;
13 |
14 | // Iterate through list of stylesheets.
15 | Object.values(documentStyles).forEach((stylesheet) => {
16 | try {
17 | const {
18 | cssRules
19 | } = stylesheet;
20 |
21 | // Iterate through list of rules.
22 | Object.values(cssRules).forEach((cssRule) => {
23 | // Iterate through our defined pseudo classes.
24 | matched = false;
25 | Object.values(pseudoClasses).forEach((pseudoClass) => {
26 | try {
27 | const {
28 | axureName,
29 | keyName
30 | } = pseudoClass;
31 |
32 | if (!matched && RegExp(axureName).test(cssRule.selectorText)) {
33 | matched = true;
34 | // Extract our "pure" selector name.
35 | if (axureName.length) {
36 | pseudoFilter = new RegExp(`\\.${axureName}`);
37 | selectorName = cssRule.selectorText.replace(pseudoFilter, '').trim();
38 | } else {
39 | selectorName = cssRule.selectorText.trim();
40 | }
41 |
42 | cssContent = cssRule.cssText.replace(/^.*{/, '').replace('}', '').trim();
43 |
44 | // Check if the selector exists yet.
45 | if (!(selectorName in documentCSSList)) {
46 | documentCSSList[selectorName] = {};
47 | }
48 |
49 | // Update our master CSS attributes list.
50 | attributeObject = {};
51 |
52 | // Convert our CSS list into an object.
53 | cssContent.split(';').forEach((attribute) => {
54 | if (attribute.length) {
55 | attributeObject[attribute.split(':')[0].trim()] = attribute.split(':')[1].trim();
56 | }
57 | });
58 | documentCSSList[selectorName][keyName] = attributeObject;
59 | }
60 | } catch (err) {
61 | // Probably missing a key in the object.
62 | }
63 | });
64 | });
65 | } catch (err) {
66 | // Probably missing a key in the object.
67 | }
68 | });
69 |
70 | return documentCSSList;
71 | };
72 |
73 | export default compileCSSAttributes;
74 |
--------------------------------------------------------------------------------
/src/interfacers/eventsInterfacer.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | import {
4 | ANNOTATION_ELEMENTS,
5 | NO_INTERACT_CLASS
6 | } from '../globalConstants';
7 |
8 | export const initNoInteract = () => {
9 | $(ANNOTATION_ELEMENTS.join(', ')).addClass(NO_INTERACT_CLASS);
10 | };
11 |
12 | /**
13 | * Here we handle element hovers. We're binding event listeners
14 | * to every component. This is inefficient but we have to do
15 | * it this way so that we can block Axure's event listeners
16 | * before they bubble up.
17 | *
18 | * @param {function} callback
19 | */
20 | export const addGlobalMouseoverListener = (callback) => {
21 | $('#base, #base *').not(ANNOTATION_ELEMENTS.join(', ')).on('mouseover', (event) => {
22 | callback(event);
23 | });
24 | };
25 |
26 | /**
27 | * Here we handle element clicks. We're binding event listeners to every
28 | * component. This is inefficient but we have to do it this way so that
29 | * we can block Axure's event listeners before they bubble up.
30 | *
31 | * @param {function} callback
32 | */
33 | export const addGlobalClickListener = (callback) => {
34 | $('body, #base *').not(ANNOTATION_ELEMENTS.join(', ')).on('click', (event) => {
35 | callback(event);
36 | });
37 | };
38 |
39 | /**
40 | * This is used to capture and prevent mousedown and mouseup events when the
41 | * tool is enabled.
42 | *
43 | * @param {function} callback
44 | */
45 | export const addGlobalMouseToggleListener = (callback) => {
46 | $('#base *').not(ANNOTATION_ELEMENTS.join(', ')).on('mousedown mouseup', (event) => {
47 | callback(event);
48 | });
49 | };
50 |
51 | /**
52 | * Here we listen for any dialog open events. These occur when users click on
53 | * the default Axure notes. For some reason, 'dialogopen' listener isn't firing.
54 | *
55 | * @param {function} callback
56 | */
57 | export const addDialogOpenListener = (callback) => {
58 | $(ANNOTATION_ELEMENTS.join(', ')).on('click', (event) => {
59 | $('#base .ui-dialog').appendTo('.ArtboardModule');
60 | $('.notesDialog').appendTo('.ArtboardModule');
61 | initNoInteract();
62 |
63 | callback(event);
64 | });
65 | };
66 |
67 | /**
68 | * Here we listen for a keypress of our defined hotkeys. These keys disable the
69 | * redline tool and all direct interaction with Axure elements while the key
70 | * is depressed.
71 | *
72 | * @param {function} callback
73 | */
74 | export const addHotkeyListener = (callback) => {
75 | $('html').on('keydown', (event) => {
76 | if (event.metaKey || event.ctrlKey) {
77 | callback(true);
78 | }
79 | });
80 |
81 | $('html').on('keyup', () => {
82 | callback(false);
83 | });
84 | };
85 |
86 | /**
87 | * Here we listener for zoom key combinations used to scale the artboard
88 | * content larger or smaller.
89 | *
90 | * @param {function} callback
91 | */
92 | export const addGlobalZoomListener = (callback) => {
93 | $('html').on('keydown', (event) => {
94 | callback(event);
95 | });
96 | };
97 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const {
2 | CleanWebpackPlugin
3 | } = require('clean-webpack-plugin');
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const path = require('path');
6 | const TerserPlugin = require('terser-webpack-plugin');
7 |
8 | const config = require('./config');
9 |
10 | const WatchBundlePlugin = require('./scripts/WatchBundlePlugin');
11 |
12 | const {
13 | directories: {
14 | srcDirectory
15 | },
16 | buildTypes: {
17 | development,
18 | production
19 | },
20 | bundleName
21 | } = config();
22 |
23 | module.exports = () => {
24 | const {
25 | env: {
26 | INJECTED = false,
27 | NODE_ENV = production
28 | }
29 | } = process;
30 |
31 | const isInjectedEnvironment = INJECTED === 'true';
32 | const isProduction = NODE_ENV !== development;
33 |
34 | const plugins = [
35 | new CleanWebpackPlugin(),
36 | new WatchBundlePlugin({
37 | isInjectedEnvironment,
38 | isProduction
39 | })
40 | ];
41 |
42 | if (!isInjectedEnvironment && !isProduction) {
43 | plugins.push(new HtmlWebpackPlugin({
44 | filename: 'index.html',
45 | path: path.join(__dirname, '../dist/'),
46 | template: './src/index.html'
47 | }));
48 | }
49 |
50 | return {
51 | devServer: {
52 | contentBase: path.join(__dirname, '../dist/'),
53 | historyApiFallback: true,
54 | hot: true
55 | },
56 | devtool: !isProduction ? 'source-map' : undefined,
57 | entry: [
58 | `${srcDirectory}/index.js`
59 | ],
60 | mode: NODE_ENV,
61 | module: {
62 | rules: [
63 | {
64 | exclude: /node_modules/,
65 | test: /\.(js|jsx)$/,
66 | use: [
67 | 'babel-loader'
68 | ]
69 | },
70 | {
71 | test: /\.scss$/,
72 | use: [
73 | 'style-loader',
74 | 'css-loader',
75 | 'postcss-loader',
76 | 'sass-loader'
77 | ]
78 | },
79 | {
80 | loader: 'svg-inline-loader',
81 | test: /\.svg$/
82 | }
83 | ]
84 | },
85 | optimization: {
86 | minimize: true,
87 | minimizer: [
88 | new TerserPlugin({
89 | extractComments: false
90 | })
91 | ]
92 | },
93 | output: {
94 | filename: `${bundleName}.js`,
95 | path: path.resolve(__dirname, 'dist/')
96 | },
97 | plugins,
98 | resolve: {
99 | extensions: ['*', '.js', '.jsx']
100 | },
101 | watch: !isProduction
102 | };
103 | };
104 |
--------------------------------------------------------------------------------
/src/modules/ElementInteractionModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import HoveredElementModule from '../HoveredElementModule';
5 | import InterElementDimensionsModule from '../InterElementDimensionsModule';
6 | import SelectedElementModule from '../SelectedElementModule';
7 |
8 | class ElementInteractionModule extends React.PureComponent {
9 | render() {
10 | const {
11 | artboardOffsetLeft,
12 | artboardOffsetTop,
13 | artboardScaledHeight,
14 | artboardScaledWidth,
15 | elementMarkerThickness,
16 | hoveredElement,
17 | selectedElement
18 | } = this.props;
19 |
20 | const {
21 | target: hoveredElementTarget
22 | } = hoveredElement;
23 |
24 | const {
25 | target: selectedElementTarget
26 | } = selectedElement;
27 |
28 | return (
29 | <>
30 | {
31 | hoveredElementTarget
32 | && (
33 |
41 | )
42 | }
43 | {
44 | selectedElementTarget
45 | && (
46 |
50 | )
51 | }
52 | {
53 | hoveredElementTarget
54 | && selectedElementTarget
55 | && (
56 |
61 | )
62 | }
63 | >
64 | );
65 | }
66 | }
67 |
68 | ElementInteractionModule.propTypes = {
69 | artboardOffsetLeft: PropTypes.number.isRequired,
70 | artboardOffsetTop: PropTypes.number.isRequired,
71 | artboardScaledHeight: PropTypes.number.isRequired,
72 | artboardScaledWidth: PropTypes.number.isRequired,
73 | elementMarkerThickness: PropTypes.number.isRequired,
74 | hoveredElement: PropTypes.shape({
75 | target: PropTypes.shape({})
76 | }).isRequired,
77 | selectedElement: PropTypes.shape({
78 | target: PropTypes.shape({})
79 | }).isRequired
80 | };
81 |
82 | export default ElementInteractionModule;
83 |
--------------------------------------------------------------------------------
/src/modules/SplashScreenModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import ButtonComponent from '../../components/ButtonComponent';
5 | import Code from '../../components/CodeBadgeComponent';
6 | import Icon from '../../components/IconComponent';
7 | import ModalComponent from '../../components/ModalComponent';
8 |
9 | import {
10 | codeInspect
11 | } from '../../icons';
12 |
13 | import './styles.scss';
14 |
15 | class SplashScreenModule extends React.PureComponent {
16 | constructor(props) {
17 | super(props);
18 |
19 | this.state = {
20 | isModalShown: true
21 | };
22 | }
23 |
24 | handleCloseModal = () => {
25 | const {
26 | closeCallback
27 | } = this.props;
28 |
29 | this.setState({
30 | isModalShown: false
31 | }, () => {
32 | closeCallback();
33 | });
34 | }
35 |
36 | renderSplashScreenContent = () => (
37 |
38 |
39 |
40 |
Redline Tool V3.1.1
41 |
42 |
Yes, we're still here! It has been a while but we've spent time reviewing feature requests for the native inspect tool and have implemented any missing features here. Thanks for your continued usage and support!
43 |
44 |
What's New?
45 |
46 | Updated browser support - Behind the scenes, we've added better support for usage of the redline tool UI in legacy browsers.
47 | Optimized functionality - We've updated how the tool is built, leveraging the latest software in hopes of delivering a crisp and bug-free experience.
48 | Additional text attributes - We've added the text-transform and letter-spacing attributes
49 |
50 |
51 |
55 |
56 | );
57 |
58 | render() {
59 | const {
60 | isModalShown
61 | } = this.state;
62 |
63 | return (
64 |
68 | {this.renderSplashScreenContent()}
69 |
70 | );
71 | }
72 | }
73 |
74 | SplashScreenModule.displayName = 'SplashScreenModule';
75 |
76 | SplashScreenModule.propTypes = {
77 | closeCallback: PropTypes.func.isRequired
78 | };
79 |
80 | export default SplashScreenModule;
81 |
--------------------------------------------------------------------------------
/src/modules/SharingLinksModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import InputComponent from '../../components/InputComponent';
5 | import ModalComponent from '../../components/ModalComponent';
6 | import ButtonComponent from '../../components/ButtonComponent';
7 |
8 | import './styles.scss';
9 |
10 | const displayName = 'SharingLinksModule';
11 |
12 | const SharingLinksModule = (props) => {
13 | const {
14 | isShown,
15 | moduleCloseCallback
16 | } = props;
17 |
18 | const generateSharingLinks = () => {
19 | const disableAnnotations = 'fn=0';
20 | const pageBaseURL = window.parent.location.href;
21 | const regexBaseURL = /^.*(\/|\.html)/;
22 |
23 | let businessURL = '';
24 | let devURL = '';
25 |
26 | // Ensure we always select the PAGES tab.
27 | const selectHomePage = (pageURL) => pageURL.replace(/g=\d&/, 'g=1&');
28 |
29 | // Extract our base URL up until last forward slash found or .html extension.
30 | const extractedBaseURL = () => {
31 | let extractedURL = '';
32 |
33 | try {
34 | [
35 | extractedURL
36 | ] = pageBaseURL.match(regexBaseURL);
37 | } catch (err) {
38 | extractedURL = '';
39 | }
40 |
41 | return extractedURL;
42 | };
43 |
44 | businessURL = pageBaseURL.replace(extractedBaseURL(), `${extractedBaseURL()}?redline=business`);
45 | businessURL = `${businessURL}&${disableAnnotations}`;
46 | businessURL = selectHomePage(businessURL);
47 |
48 | devURL = pageBaseURL.replace(extractedBaseURL(), `${extractedBaseURL()}?redline=dev`);
49 | devURL = selectHomePage(devURL);
50 |
51 | return ({
52 | businessURL,
53 | devURL
54 | });
55 | };
56 |
57 | const {
58 | businessURL,
59 | devURL
60 | } = generateSharingLinks();
61 |
62 | const renderModalBody = (
63 |
64 |
Sharing Links
65 |
71 |
77 |
81 |
82 | );
83 |
84 | return (
85 |
86 |
90 | {renderModalBody}
91 |
92 |
93 | );
94 | };
95 |
96 | SharingLinksModule.propTypes = {
97 | isShown: PropTypes.bool.isRequired,
98 | moduleCloseCallback: PropTypes.func.isRequired
99 | };
100 |
101 | export default SharingLinksModule;
102 |
--------------------------------------------------------------------------------
/src/styles/axureComponentStyling.scss:
--------------------------------------------------------------------------------
1 | @import '../styles/constants';
2 |
3 | $default-border-radius: 3px;
4 |
5 | html {
6 | @extend %background;
7 | }
8 |
9 | body {
10 | position: static;
11 |
12 | width: 100vw;
13 | height: 100vh;
14 | margin: 0;
15 |
16 | #base {
17 | background-color: $artboard-background-color;
18 | }
19 |
20 | /* RP8 */
21 | .annotation {
22 | box-sizing: border-box;
23 | width: 14px !important;
24 | height: 14px !important;
25 | margin: 0;
26 | padding: 0;
27 |
28 | cursor: help !important;
29 |
30 | & > div {
31 | display: flex;
32 | flex-direction: column;
33 | align-items: center;
34 | justify-content: space-around;
35 | box-sizing: border-box;
36 | width: 100%;
37 | height: 100%;
38 | padding: 3px;
39 |
40 | background-color: $primary-color-blue;
41 | border: none;
42 | border-radius: $default-border-radius;
43 |
44 | &:hover {
45 | background-color: darken($primary-color-blue, 10%);
46 | }
47 |
48 | & > div {
49 | width: 100%;
50 | height: 1px;
51 | margin: 0;
52 | padding: 0;
53 |
54 | background-color: $primary-color-white;
55 | border: none;
56 | }
57 | }
58 |
59 | * {
60 | cursor: help !important;
61 | }
62 | }
63 |
64 | /* RP9 */
65 | .annnote {
66 | cursor: help !important;
67 |
68 | .annnotelabel {
69 | margin: 0;
70 |
71 | color: $primary-color-white;
72 |
73 | background-color: $primary-color-blue;
74 | border-radius: $default-border-radius;
75 | cursor: help !important;
76 |
77 | &:hover {
78 | background-color: darken($primary-color-blue, 10%);
79 | }
80 | }
81 | }
82 |
83 | .ui-dialog {
84 | z-index: $z-index-dialog !important;
85 |
86 | padding: 0 !important;
87 |
88 | border: solid 1px $primary-color-gray !important;
89 | border-radius: $default-border-radius !important;
90 | opacity: $ui-opacity;
91 |
92 | * {
93 | color: $primary-color-gray;
94 | }
95 | }
96 |
97 | .ui-dialog-titlebar {
98 | background-color: $primary-color-gray !important;
99 | border: none !important;
100 |
101 | button {
102 | border-radius: $default-border-radius !important;
103 | outline: none !important;
104 | }
105 |
106 | .ui-icon-closethick {
107 | background-color: $primary-color-white;
108 | border-radius: $default-border-radius;
109 | }
110 | }
111 |
112 | .ui-dialog-content {
113 | padding: 10px !important;
114 |
115 | border-radius: $default-border-radius;
116 | }
117 |
118 | .ui-button-icon-only {
119 | .ui-icon {
120 | top: 0 !important;
121 | left: 0 !important;
122 | }
123 | }
124 |
125 | .ui-corner-all {
126 | border-radius: 0;
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "jest": true,
5 | "jquery": true
6 | },
7 | "extends": "airbnb",
8 | "parser": "babel-eslint",
9 | "rules": {
10 | "arrow-parens": [
11 | "error",
12 | "always"
13 | ],
14 | "class-methods-use-this": [
15 | 0
16 | ],
17 | "comma-dangle": [
18 | "error",
19 | "never"
20 | ],
21 | "curly": [
22 | "error",
23 | "all"
24 | ],
25 | "import/no-dynamic-require": [
26 | 0
27 | ],
28 | "import/no-extraneous-dependencies": [
29 | "error",
30 | {
31 | "devDependencies": [
32 | "./*.js",
33 | "./tasks/**/*.js",
34 | "./scripts/**/*.js"
35 | ]
36 | }
37 | ],
38 | "indent": [
39 | "error",
40 | 4,
41 | {
42 | "SwitchCase": 1
43 | }
44 | ],
45 | "jsx-a11y/click-events-have-key-events": [
46 | 0
47 | ],
48 | "jsx-a11y/label-has-associated-control": [
49 | 0
50 | ],
51 | "jsx-a11y/label-has-for": [
52 | 0
53 | ],
54 | "jsx-a11y/no-static-element-interactions": [
55 | 0
56 | ],
57 | "linebreak-style": [
58 | 0
59 | ],
60 | "lines-between-class-members": [
61 | "error",
62 | "always",
63 | {
64 | "exceptAfterSingleLine": true
65 | }
66 | ],
67 | "max-len": [
68 | 0
69 | ],
70 | "no-empty": [
71 | "error",
72 | {
73 | "allowEmptyCatch": true
74 | }
75 | ],
76 | "no-plusplus": [
77 | "error",
78 | {
79 | "allowForLoopAfterthoughts": true
80 | }
81 | ],
82 | "react/button-has-type": [
83 | 0
84 | ],
85 | "react/jsx-curly-brace-presence": [
86 | "error",
87 | {
88 | "children": "ignore",
89 | "props": "always"
90 | }
91 | ],
92 | "react/jsx-filename-extension": [
93 | "error",
94 | {
95 | "extensions": [
96 | ".js",
97 | ".jsx"
98 | ]
99 | }
100 | ],
101 | "react/jsx-indent": [
102 | "error",
103 | 4
104 | ],
105 | "react/jsx-indent-props": [
106 | "error",
107 | 4
108 | ],
109 | "react/jsx-one-expression-per-line": [
110 | 0
111 | ],
112 | "react/jsx-props-no-spreading": [
113 | 0
114 | ],
115 | "react/no-did-update-set-state": [
116 | 0
117 | ],
118 | "react/no-find-dom-node": [
119 | 0
120 | ],
121 | "react/no-unescaped-entities": [
122 | 0
123 | ],
124 | "sort-keys": [
125 | "error",
126 | "asc",
127 | {
128 | "caseSensitive": true,
129 | "natural": true
130 | }
131 | ]
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/src/components/ModalComponent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import PropTypes from 'prop-types';
4 |
5 | import { ESCAPE_KEY } from '../../globalConstants';
6 |
7 | import './styles.scss';
8 |
9 | class ModalComponent extends React.PureComponent {
10 | static modalOpenClassName = 'modal-open';
11 |
12 | componentDidMount() {
13 | const {
14 | isShown
15 | } = this.props;
16 |
17 | if (isShown) {
18 | this.handleOpenModal();
19 | }
20 | }
21 |
22 | componentDidUpdate(prevProps) {
23 | const {
24 | isShown
25 | } = this.props;
26 |
27 | const {
28 | isShown: wasShown
29 | } = prevProps;
30 |
31 | if (!wasShown && isShown) {
32 | this.handleOpenModal();
33 | } else if (wasShown && !isShown) {
34 | this.handleCloseModal();
35 | }
36 | }
37 |
38 | handleOpenModal = () => {
39 | // Add modal class to body to block scrolling.
40 | document.body.classList.add(ModalComponent.modalOpenClassName);
41 | document.addEventListener('keydown', this.handleKeyPress);
42 | }
43 |
44 | handleCloseModal = () => {
45 | document.removeEventListener('keydown', this.handleKeyPress);
46 | document.body.classList.remove(ModalComponent.modalOpenClassName);
47 | }
48 |
49 | handleClick = () => {
50 | const {
51 | closeModal
52 | } = this.props;
53 |
54 | closeModal();
55 |
56 | this.handleCloseModal();
57 | }
58 |
59 | handleKeyPress = (event) => {
60 | const {
61 | keyCode
62 | } = event;
63 |
64 | if (keyCode === ESCAPE_KEY) {
65 | event.preventDefault();
66 |
67 | this.handleClick();
68 | }
69 | }
70 |
71 | render() {
72 | const {
73 | isShown,
74 | children
75 | } = this.props;
76 |
77 | const modalVisibleClass = isShown ? `${ModalComponent.displayName}--visible` : '';
78 |
79 | return (
80 |
81 |
85 |
86 |
94 |
95 | {children}
96 |
97 |
98 |
99 | );
100 | }
101 | }
102 |
103 | ModalComponent.displayName = 'ModalComponent';
104 |
105 | ModalComponent.propTypes = {
106 | children: PropTypes.node.isRequired,
107 | closeModal: PropTypes.func.isRequired,
108 | isShown: PropTypes.bool.isRequired
109 | };
110 |
111 | const PortalModalComponent = (props) => (
112 | ReactDOM.createPortal( , document.body)
113 | );
114 |
115 | export default PortalModalComponent;
116 |
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
111 |
112 | AA
113 |
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/modules/ZoomControlModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import {
5 | ENTER_KEY,
6 | ESCAPE_KEY
7 | } from '../../globalConstants';
8 |
9 | import './styles.scss';
10 |
11 | const moduleClassName = 'ZoomControlModule';
12 |
13 | const zoomDecrease = 'decrease';
14 | const zoomIncrease = 'increase';
15 |
16 | export const ZOOM_STEP = 10;
17 |
18 | class ZoomControlModule extends React.PureComponent {
19 | constructor(props) {
20 | super(props);
21 |
22 | this.state = {
23 | zoomLevel: 100
24 | };
25 | }
26 |
27 | componentDidMount() {
28 | const {
29 | documentZoom
30 | } = this.props;
31 |
32 | this.setState({
33 | zoomLevel: documentZoom
34 | });
35 | }
36 |
37 | componentDidUpdate(prevProps) {
38 | const {
39 | documentZoom
40 | } = this.props;
41 |
42 | const {
43 | documentZoom: prevDocumentZoom
44 | } = prevProps;
45 |
46 | if (documentZoom !== prevDocumentZoom) {
47 | this.setState({
48 | zoomLevel: documentZoom
49 | });
50 | }
51 | }
52 |
53 | onZoomClickChangeHandler = (zoomDirection) => {
54 | const {
55 | setArtboardZoom
56 | } = this.props;
57 |
58 | const {
59 | zoomLevel
60 | } = this.state;
61 |
62 | if (zoomDirection === zoomDecrease) {
63 | setArtboardZoom(zoomLevel - ZOOM_STEP);
64 | } else if (zoomDirection === zoomIncrease) {
65 | setArtboardZoom(zoomLevel + ZOOM_STEP);
66 | }
67 | }
68 |
69 | onZoomInputChangeHandler = (event) => {
70 | const {
71 | target: {
72 | value
73 | }
74 | } = event;
75 |
76 | const cleanedValue = value.replace(/%/g, '');
77 |
78 | this.setState({
79 | zoomLevel: cleanedValue
80 | });
81 | }
82 |
83 | onZoomInputBlurHandler = (event) => {
84 | const {
85 | setArtboardZoom
86 | } = this.props;
87 |
88 | const {
89 | target: {
90 | value
91 | }
92 | } = event;
93 |
94 | const cleanedValue = value.replace(/%/g, '');
95 |
96 | setArtboardZoom(cleanedValue);
97 | }
98 |
99 | onZoomInputFocusHandler = (event) => {
100 | const {
101 | target
102 | } = event;
103 |
104 | target.select();
105 | }
106 |
107 | onKeypressHandler = (event) => {
108 | const {
109 | keyCode,
110 | target
111 | } = event;
112 |
113 | if (keyCode === ENTER_KEY || keyCode === ESCAPE_KEY) {
114 | target.blur();
115 | }
116 | }
117 |
118 | render() {
119 | const {
120 | zoomLevel
121 | } = this.state;
122 |
123 | return (
124 |
125 |
this.onZoomClickChangeHandler(zoomDecrease)}
128 | role={'button'}
129 | tabIndex={0}
130 | >
131 |
132 |
133 |
141 |
this.onZoomClickChangeHandler(zoomIncrease)}
144 | role={'button'}
145 | tabIndex={0}
146 | >
147 |
148 |
149 |
150 | );
151 | }
152 | }
153 |
154 | ZoomControlModule.propTypes = {
155 | documentZoom: PropTypes.number.isRequired,
156 | setArtboardZoom: PropTypes.func.isRequired
157 | };
158 |
159 | export default ZoomControlModule;
160 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "author": "Sean McQuay",
3 | "bugs": {
4 | "url": "https://github.com/srm985/axure-redline-tool/issues"
5 | },
6 | "dependencies": {
7 | "@babel/runtime": "^7.4.2",
8 | "jquery": "^3.3.1",
9 | "prop-types": "^15.7.2",
10 | "react": "^17.0.1",
11 | "react-dom": "^17.0.1",
12 | "store": "^2.0.12"
13 | },
14 | "description": "An interactive redline tool plugin for Axure remotely-hosted projects.",
15 | "devDependencies": {
16 | "@babel/core": "^7.12.3",
17 | "@babel/plugin-proposal-class-properties": "^7.12.1",
18 | "@babel/plugin-transform-runtime": "^7.12.1",
19 | "@babel/preset-env": "^7.12.1",
20 | "@babel/preset-react": "^7.12.1",
21 | "autoprefixer": "^10.2.3",
22 | "babel-eslint": "^10.0.3",
23 | "babel-jest": "^26.6.1",
24 | "babel-loader": "^8.0.6",
25 | "babel-plugin-transform-es2017-object-entries": "0.0.5",
26 | "browser-sync": "^2.26.3",
27 | "clean-webpack-plugin": "^3.0.0",
28 | "concurrently": "^5.3.0",
29 | "cross-env": "^7.0.3",
30 | "css-loader": "^5.0.1",
31 | "eslint": "^7.18.0",
32 | "eslint-config-airbnb": "^18.2.1",
33 | "eslint-plugin-import": "^2.16.0",
34 | "eslint-plugin-jsx-a11y": "^6.2.1",
35 | "eslint-plugin-react": "^7.12.4",
36 | "html-webpack-plugin": "^5.0.0-beta.6",
37 | "husky": "^4.3.0",
38 | "inquirer": "^7.3.3",
39 | "jest": "^26.6.3",
40 | "lint-staged": "^10.5.0",
41 | "node-sass": "^5.0.0",
42 | "onchange": "^7.1.0",
43 | "postcss-loader": "^4.2.0",
44 | "regenerator-runtime": "^0.13.2",
45 | "sass-loader": "^10.1.1",
46 | "style-loader": "^2.0.0",
47 | "stylelint": "^13.9.0",
48 | "stylelint-config-rational-order": "^0.1.2",
49 | "stylelint-declaration-block-no-ignored-properties": "^2.3.0",
50 | "stylelint-order": "^4.1.0",
51 | "stylelint-scss": "^3.18.0",
52 | "svg-inline-loader": "^0.8.0",
53 | "terser-webpack-plugin": "^5.1.1",
54 | "webpack": "^5.2.0",
55 | "webpack-cli": "^3.3.12",
56 | "webpack-dev-server": "^3.11.0"
57 | },
58 | "homepage": "https://github.com/srm985/axure-redline-tool#readme",
59 | "husky": {
60 | "hooks": {
61 | "pre-commit": "cross-env FORCE_COLOR=true lint-staged && npm run build"
62 | }
63 | },
64 | "jest": {
65 | "transform": {
66 | "^.+\\.js$": "babel-jest"
67 | },
68 | "transformIgnorePatterns": [
69 | "/node_modules/"
70 | ],
71 | "verbose": true
72 | },
73 | "keywords": [
74 | "axure",
75 | "javascript",
76 | "jquery",
77 | "css",
78 | "wireframe",
79 | "mockup",
80 | "protoype",
81 | "prototyping",
82 | "plugin",
83 | "spec",
84 | "design-specs",
85 | "redline"
86 | ],
87 | "license": "MIT",
88 | "lint-staged": {
89 | "*.js": [
90 | "eslint --fix --ignore-pattern '/dist/*' --ignore-pattern '/web/*'"
91 | ],
92 | "*.scss": [
93 | "stylelint --syntax scss --color --allow-empty-input --fix"
94 | ]
95 | },
96 | "name": "axure-redline-tool",
97 | "repository": {
98 | "type": "git",
99 | "url": "git+https://github.com/srm985/axure-redline-tool.git"
100 | },
101 | "scripts": {
102 | "build": "concurrently -r \"npm:lint --silent\" \"npm:test\" && webpack",
103 | "build:watch": "npm run config:develop && npm run lint && concurrently -r \"webpack\" \"npm:watch --silent\"",
104 | "config:develop": "cross-env-shell NODE_ENV=development INJECTED=false",
105 | "config:develop:inject": "cross-env-shell NODE_ENV=development INJECTED=true",
106 | "develop": "cross-env-shell NODE_ENV=development webpack-dev-server --open --config ./webpack.config.js --mode=development --port 3000",
107 | "develop:inject": "npm run config:develop:inject && node ./scripts/serveInject.js",
108 | "lint": "concurrently -r \"npm:lint:js --silent\" \"npm:lint:scss --silent\"",
109 | "lint:fix": "concurrently -r \"eslint ./*.js src/**/*.js --fix\" \"stylelint src/**/*.scss --syntax scss --color --allow-empty-input --fix\"",
110 | "lint:js": "eslint ./*.js src/**/*.js",
111 | "lint:scss": "stylelint src/**/*.scss --syntax scss --color --allow-empty-input",
112 | "start": "npm run develop:inject",
113 | "test": "jest --passWithNoTests",
114 | "watch": "concurrently -r \"npm:watch:js --silent\" \"npm:watch:scss --silent\"",
115 | "watch:js": "onchange --await-write-finish 300 \"src/**/*.js\" \"*.js\" \"scripts/*.js\" -- npm run lint:js --silent",
116 | "watch:scss": "onchange --await-write-finish 300 \"src/**/*.scss\" -- npm run lint:scss --silent"
117 | },
118 | "version": "3.1.1"
119 | }
120 |
--------------------------------------------------------------------------------
/src/modules/HoveredElementModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import DimensionLineComponent from '../../components/DimensionLineComponent';
5 |
6 | import {
7 | LINE_TYPE_DASHED_HOVERED,
8 | LINE_TYPE_SOLID_HOVERED
9 | } from '../../components/DimensionLineComponent/constants';
10 |
11 | class HoveredElementModule extends React.PureComponent {
12 | render() {
13 | const {
14 | artboardOffsetLeft,
15 | artboardOffsetTop,
16 | artboardScaledHeight,
17 | artboardScaledWidth,
18 | elementMarkerThickness,
19 | hoveredElement: {
20 | height,
21 | offsetLeft,
22 | offsetTop,
23 | width
24 | }
25 | } = this.props;
26 |
27 | return (
28 | <>
29 |
37 |
45 |
53 |
61 |
62 |
70 |
78 |
86 |
94 | >
95 | );
96 | }
97 | }
98 |
99 | HoveredElementModule.propTypes = {
100 | artboardOffsetLeft: PropTypes.number.isRequired,
101 | artboardOffsetTop: PropTypes.number.isRequired,
102 | artboardScaledHeight: PropTypes.number.isRequired,
103 | artboardScaledWidth: PropTypes.number.isRequired,
104 | elementMarkerThickness: PropTypes.number.isRequired,
105 | hoveredElement: PropTypes.shape({
106 | height: PropTypes.number.isRequired,
107 | offsetLeft: PropTypes.number.isRequired,
108 | offsetTop: PropTypes.number.isRequired,
109 | width: PropTypes.number.isRequired
110 | }).isRequired
111 | };
112 |
113 | export default HoveredElementModule;
114 |
--------------------------------------------------------------------------------
/src/modules/ElementPropertiesSidebarModule/styles.scss:
--------------------------------------------------------------------------------
1 | @import '../../styles/constants';
2 |
3 | /* Module Constants */
4 | $side-pull-width: 20px;
5 | $sidebar-padding: 20px;
6 | $sidebar-width: 270px;
7 | $pseudo-tab-height: 25px;
8 | $grid-overlay-height: 100px;
9 |
10 | .ElementPropertiesSidebarModule {
11 | position: fixed;
12 | top: $header-height;
13 | right: -#{$sidebar-width - 5px};
14 | z-index: $z-index-sidebar;
15 |
16 | box-sizing: border-box;
17 | width: $sidebar-width;
18 | height: calc(100% - #{$header-height});
19 |
20 | background-color: $primary-color-background;
21 | opacity: $ui-opacity;
22 |
23 | transition: right 0.1s ease-in-out;
24 |
25 | &--visible {
26 | right: 0;
27 |
28 | .ElementPropertiesSidebarModule__grid-overlay {
29 | display: inline-block;
30 | }
31 | }
32 |
33 | &__side-pull {
34 | position: absolute;
35 | top: 65px;
36 | left: -#{$side-pull-width};
37 |
38 | display: flex;
39 | flex-direction: column;
40 | align-items: center;
41 | justify-content: space-between;
42 | box-sizing: border-box;
43 | width: $side-pull-width;
44 | height: #{2 * $side-pull-width};
45 | padding: 13px 0 13px 7px;
46 |
47 | background-color: inherit;
48 | border-radius: $side-pull-width 0 0 $side-pull-width;
49 | outline: none;
50 | cursor: pointer;
51 |
52 | & > span {
53 | width: 100%;
54 | height: 2px;
55 |
56 | background-color: $primary-color-white;
57 | border-radius: 1px;
58 | }
59 | }
60 |
61 | &__pseudo-tabs {
62 | width: 100%;
63 | height: calc(100% - #{$grid-overlay-height});
64 |
65 | &--parent-component-name {
66 | box-sizing: border-box;
67 | width: 100%;
68 | margin-bottom: 41px; /* Weird margin to nicely align pseudo tabs with pull tab. */
69 | padding: 0 $sidebar-padding;
70 |
71 | .InputComponent {
72 | margin-top: 0 !important;
73 | }
74 | }
75 |
76 | &--header {
77 | display: flex;
78 | flex-direction: row;
79 | align-items: flex-end;
80 | box-sizing: border-box;
81 | width: 100%;
82 | height: $pseudo-tab-height;
83 | padding: 0 $sidebar-padding;
84 | }
85 |
86 | &--tab {
87 | z-index: 1;
88 |
89 | display: flex;
90 | flex-basis: 0;
91 | flex-grow: 1;
92 | align-items: center;
93 | justify-content: center;
94 | box-sizing: border-box;
95 | max-width: 50%;
96 | height: 100%;
97 | margin: 0 1px;
98 | padding: 0 5px;
99 |
100 | color: $primary-color-gray;
101 | font-size: 12px;
102 | font-family: $font-stack;
103 |
104 | background-color: $primary-color-white;
105 | border-radius: 3px 3px 0 0;
106 | cursor: pointer;
107 |
108 | span {
109 | width: 100%;
110 | overflow: hidden;
111 |
112 | white-space: nowrap;
113 | text-align: center;
114 | text-overflow: ellipsis;
115 | }
116 | }
117 |
118 | &--tab-active {
119 | color: $primary-color-white;
120 |
121 | background-color: $primary-color-background;
122 | border-color: $primary-color-white;
123 | border-style: solid;
124 | border-width: 1px 1px 0;
125 | }
126 |
127 | &--tab-inactive {
128 | &:hover {
129 | height: 105%;
130 | }
131 | }
132 |
133 | &--body {
134 | box-sizing: border-box;
135 | width: 100%;
136 | height: calc(100% - #{$pseudo-tab-height});
137 | margin-top: -1px;
138 | padding: 0 $sidebar-padding 20px;
139 | overflow-y: auto;
140 |
141 | border-top: solid 1px $primary-color-white;
142 |
143 | p {
144 | margin-top: 20px;
145 |
146 | color: $primary-color-white;
147 | font-weight: bold;
148 | font-size: 12px;
149 | text-transform: uppercase;
150 | }
151 | }
152 | }
153 |
154 | .InputComponent,
155 | .TextAreaComponent {
156 | margin-top: 10px;
157 | }
158 |
159 | &__grid-overlay {
160 | display: none;
161 | box-sizing: border-box;
162 | width: 100%;
163 | height: #{$grid-overlay-height};
164 | padding: $sidebar-padding;
165 |
166 | border-top: solid 1px $primary-color-white;
167 |
168 | &--selector {
169 | width: 100%;
170 |
171 | .CheckboxComponent {
172 | margin-bottom: 10px;
173 | }
174 | }
175 | }
176 |
177 | .ElementPropertiesSidebarModule__pseudo-tabs--parent-component-name {
178 | & ~ .ElementPropertiesSidebarModule__pseudo-tabs--body {
179 | height: calc(100% - 105px);
180 | }
181 | }
182 | }
183 |
--------------------------------------------------------------------------------
/src/icons/codeInspect.svg:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/src/modules/ArtboardModule/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import ElementInteractionModule from '../ElementInteractionModule';
5 | import GridOverlayModule from '../GridOverlayModule';
6 |
7 | import {
8 | calculateScrollAfterZoom,
9 | injectArtboard,
10 | scrollCenterArtboard,
11 | sizeArtboard
12 | } from '../../interfacers/artboardInterfacer';
13 |
14 | import calculateGlobalOffset from '../../utils/calculateGlobalOffset';
15 |
16 | import './styles.scss';
17 |
18 | class ArtboardModule extends React.PureComponent {
19 | constructor(props) {
20 | super(props);
21 |
22 | this.artboardRef = React.createRef();
23 |
24 | this.state = {
25 | artboardOffsetLeft: 0,
26 | artboardOffsetTop: 0,
27 | artboardScaledHeight: 0,
28 | artboardScaledWidth: 0
29 | };
30 | }
31 |
32 | componentDidMount() {
33 | const {
34 | setArtboardDimensions,
35 | setAxureLoaded,
36 | zoomWrapperPadding
37 | } = this.props;
38 |
39 | injectArtboard(`${ArtboardModule.displayName}__artboard`).then(() => {
40 | sizeArtboard().then((dimensions) => {
41 | const {
42 | artboardHeight,
43 | artboardWidth
44 | } = dimensions;
45 |
46 | setArtboardDimensions({
47 | artboardHeight,
48 | artboardWidth
49 | });
50 |
51 | scrollCenterArtboard({
52 | artboardHeight,
53 | artboardWidth,
54 | zoomWrapperPadding
55 | });
56 |
57 | setAxureLoaded();
58 | this.updateArtboardMeasurements();
59 | });
60 | });
61 | }
62 |
63 | componentDidUpdate(prevProps) {
64 | const {
65 | documentZoom: previousZoom
66 | } = prevProps;
67 |
68 | const {
69 | artboardHeight,
70 | artboardWidth,
71 | documentZoom
72 | } = this.props;
73 |
74 | this.updateArtboardMeasurements();
75 |
76 | // Check if a zoom operation took place.
77 | if (documentZoom !== previousZoom) {
78 | calculateScrollAfterZoom({
79 | artboardHeight,
80 | artboardWidth,
81 | documentZoom,
82 | previousZoom
83 | });
84 | }
85 | }
86 |
87 | updateArtboardMeasurements = () => {
88 | const {
89 | current: artboardElement
90 | } = this.artboardRef;
91 |
92 | const {
93 | scaledHeight: artboardScaledHeight,
94 | scaledOffsetLeft: artboardOffsetLeft,
95 | scaledOffsetTop: artboardOffsetTop,
96 | scaledWidth: artboardScaledWidth
97 | } = calculateGlobalOffset(artboardElement);
98 |
99 | const {
100 | artboardOffsetLeft: currentArtboardOffsetLeft,
101 | artboardOffsetTop: currentArtboardOffsetTop,
102 | artboardScaledHeight: currentArtboardScaledHeight,
103 | artboardScaledWidth: currentArtboardScaledWidth
104 | } = this.state;
105 |
106 | // Check if we should update state.
107 | if (
108 | currentArtboardOffsetLeft !== artboardOffsetLeft
109 | || currentArtboardOffsetTop !== artboardOffsetTop
110 | || currentArtboardScaledHeight !== artboardScaledHeight
111 | || currentArtboardScaledWidth !== artboardScaledWidth
112 | ) {
113 | this.setState({
114 | artboardOffsetLeft,
115 | artboardOffsetTop,
116 | artboardScaledHeight,
117 | artboardScaledWidth
118 | });
119 | }
120 | }
121 |
122 | render() {
123 | const {
124 | artboardHeight,
125 | artboardWidth,
126 | artboardWrapperHeight,
127 | artboardWrapperWidth,
128 | documentZoom,
129 | elementMarkerThickness,
130 | gridLayout,
131 | handleClickCallback,
132 | hoveredElement,
133 | isArtboardWrapperShown,
134 | isToolEnabled,
135 | selectedElement,
136 | zoomWrapperPadding
137 | } = this.props;
138 |
139 | const {
140 | artboardOffsetLeft,
141 | artboardOffsetTop,
142 | artboardScaledHeight,
143 | artboardScaledWidth
144 | } = this.state;
145 |
146 | let artboardWrapperStyle = {};
147 | let artboardShownClass = '';
148 |
149 | if (isArtboardWrapperShown) {
150 | artboardWrapperStyle = {
151 | height: artboardWrapperHeight,
152 | width: artboardWrapperWidth
153 | };
154 | } else {
155 | artboardShownClass = `${ArtboardModule.displayName}--shown`;
156 | }
157 |
158 | const artboardStyle = {
159 | height: artboardHeight,
160 | transform: `scale(${documentZoom / 100})`,
161 | width: artboardWidth
162 | };
163 |
164 | const toolEnabledClass = isToolEnabled ? `${ArtboardModule.displayName}--enabled` : '';
165 |
166 | return (
167 |
172 |
177 |
181 |
182 |
192 |
193 | );
194 | }
195 | }
196 |
197 | ArtboardModule.displayName = 'ArtboardModule';
198 |
199 | ArtboardModule.propTypes = {
200 | artboardHeight: PropTypes.number.isRequired,
201 | artboardWidth: PropTypes.number.isRequired,
202 | artboardWrapperHeight: PropTypes.number.isRequired,
203 | artboardWrapperWidth: PropTypes.number.isRequired,
204 | documentZoom: PropTypes.number.isRequired,
205 | elementMarkerThickness: PropTypes.number.isRequired,
206 | gridLayout: PropTypes.string.isRequired,
207 | handleClickCallback: PropTypes.func.isRequired,
208 | hoveredElement: PropTypes.shape({}).isRequired,
209 | isArtboardWrapperShown: PropTypes.bool.isRequired,
210 | isToolEnabled: PropTypes.bool.isRequired,
211 | selectedElement: PropTypes.shape({}).isRequired,
212 | setArtboardDimensions: PropTypes.func.isRequired,
213 | setAxureLoaded: PropTypes.func.isRequired,
214 | zoomWrapperPadding: PropTypes.number.isRequired
215 | };
216 |
217 | export default ArtboardModule;
218 |
--------------------------------------------------------------------------------
/src/components/InputComponent/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import ColorSwatchComponent from '../ColorSwatchComponent';
5 | import TooltipComponent from '../TooltipComponent';
6 |
7 | import CSS_COLORS from '../../utils/cssColors';
8 |
9 | import { TOOLTIP_VISIBLE_TIME } from '../../globalConstants';
10 |
11 | import './styles.scss';
12 |
13 | class InputComponent extends React.PureComponent {
14 | constructor(props) {
15 | super(props);
16 |
17 | this.rgbaRegEx = /rgb(a)?\(\d+,(\s+)?\d+,(\s+)?\d+(,(\s+)?\d(\.\d+)?)?\)/;
18 | this.hexRegEx = /#([a-fA-F]|\d){6}((\s+)?\d{1,3}%)?/;
19 |
20 | this.state = {
21 | inputValue: '',
22 | isCopiedTooltipActive: false,
23 | swatchColor: null
24 | };
25 | }
26 |
27 | componentDidMount() {
28 | const {
29 | inputValue,
30 | noFormat
31 | } = this.props;
32 |
33 | if (noFormat) {
34 | this.setState({
35 | inputValue
36 | });
37 | } else {
38 | this.checkColorSwatchRequired();
39 | }
40 | }
41 |
42 | componentDidUpdate(prevProps) {
43 | const {
44 | inputValue,
45 | noFormat
46 | } = this.props;
47 |
48 | const {
49 | inputValue: prevInputValue
50 | } = prevProps;
51 |
52 | if (noFormat && inputValue !== prevInputValue) {
53 | this.setState({
54 | inputValue
55 | });
56 | } else if (inputValue !== prevInputValue) {
57 | this.checkColorSwatchRequired();
58 | }
59 | }
60 |
61 | setSwatchValue = () => {
62 | const {
63 | inputValue
64 | } = this.state;
65 |
66 | const placeholder = '!*!';
67 |
68 | let colorArr;
69 | let extractedColorString = '';
70 | let newFormat = '';
71 | let opacity;
72 | let valueTemplate = '';
73 |
74 | // Determine if we're looking at a RGB(A) or hex value.
75 | if ((this.rgbaRegEx).test(inputValue)) {
76 | [extractedColorString] = inputValue.match(this.rgbaRegEx);
77 | valueTemplate = inputValue.replace(this.rgbaRegEx, placeholder);
78 | } else if ((this.hexRegEx).test(inputValue)) {
79 | [extractedColorString] = inputValue.match(this.hexRegEx);
80 | valueTemplate = inputValue.replace(this.hexRegEx, placeholder);
81 | } else {
82 | // If we end up not passing a color at all.
83 | valueTemplate = inputValue;
84 | }
85 |
86 | if (/rgba/.test(extractedColorString)) {
87 | colorArr = extractedColorString.match(/(\d\.\d+)|\d+/g);
88 | newFormat = '#';
89 |
90 | for (let i = 0; i < 3; i++) {
91 | newFormat += (`0${Number(colorArr[i]).toString(16).toUpperCase()}`).slice(-2);
92 | }
93 |
94 | newFormat += ` ${Number(colorArr[3]) * 100}%`;
95 | } else if (/%/.test(extractedColorString)) {
96 | colorArr = extractedColorString.replace('#', '').slice(0, 6).match(/\w{2}/g);
97 | opacity = Number(extractedColorString.replace(/#\w{6}\s/, '').replace('%', '')) / 100;
98 | newFormat = `rgba(${parseInt(colorArr[0], 16)}, ${parseInt(colorArr[1], 16)}, ${parseInt(colorArr[2], 16)}, ${opacity})`;
99 | } else if (/rgb\(/.test(extractedColorString)) {
100 | colorArr = extractedColorString.replace(',', '').match(/\d+/g);
101 | newFormat = '#';
102 |
103 | colorArr.forEach((color) => {
104 | newFormat += (`0${Number(color).toString(16).toUpperCase()}`).slice(-2);
105 | });
106 | } else if (/#/.test(extractedColorString)) {
107 | colorArr = extractedColorString.replace('#', '').match(/\w{2}/g);
108 | newFormat = `rgb(${parseInt(colorArr[0], 16)}, ${parseInt(colorArr[1], 16)}, ${parseInt(colorArr[2], 16)})`;
109 | }
110 |
111 | valueTemplate = valueTemplate.replace(placeholder, newFormat);
112 |
113 | this.setState({
114 | inputValue: valueTemplate
115 | });
116 | }
117 |
118 | /**
119 | * This function handles our mouseup event and selects the
120 | * field text.
121 | *
122 | * @param {mouseup event} event
123 | */
124 | handleCopy = (event) => {
125 | const {
126 | target: inputField
127 | } = event;
128 |
129 | inputField.select();
130 | document.execCommand('Copy');
131 |
132 | this.setState({
133 | isCopiedTooltipActive: true
134 | }, () => {
135 | setTimeout(() => {
136 | this.setState({
137 | isCopiedTooltipActive: false
138 | });
139 | }, TOOLTIP_VISIBLE_TIME);
140 | });
141 | }
142 |
143 | checkColorSwatchRequired = () => {
144 | const {
145 | inputValue
146 | } = this.props;
147 |
148 | const placeholder = '!*!';
149 |
150 | let swatchOpacity;
151 | let conditionedInputColorValue;
152 | let swatchColor;
153 |
154 | if ((this.rgbaRegEx).test(inputValue) && inputValue !== 'transparent') {
155 | /**
156 | * If we have RGBA, we round our opacity to two decimals of
157 | * precision. If the opacity is 1, we'll convert to RGB.
158 | */
159 | if ((/rgba/).test(inputValue)) {
160 | // Extract our RGBA substring.
161 | const rgbaExtraction = inputValue.match(this.rgbaRegEx)[0].replace(/\s+/g, '');
162 |
163 | swatchOpacity = Math.round(Number(rgbaExtraction.replace(/rgba\(\d+,\d+,\d+,(\d?(\.\d+)?)\)/, '$1')) * 100) / 100;
164 | swatchColor = rgbaExtraction.replace(/rgba\((\d+),(\d+),(\d+),(\d?(\.\d+)?)\)/, `rgba($1, $2, $3, ${placeholder})`);
165 | swatchColor = swatchColor.replace(placeholder, swatchOpacity);
166 |
167 | /**
168 | * If our RGBA opacity is 1, then let's just convert
169 | * things to RGB.
170 | */
171 | if (swatchOpacity === 1) {
172 | swatchColor = rgbaExtraction.replace(/rgba\((\d+),(\d+),(\d+),(\d?(\.\d+)?)\)/, 'rgb($1, $2, $3)');
173 | }
174 |
175 | conditionedInputColorValue = inputValue.replace(this.rgbaRegEx, swatchColor);
176 | } else {
177 | [swatchColor] = inputValue.match(this.rgbaRegEx);
178 | }
179 | } else {
180 | const cleanedInputValue = inputValue.trim().toLowerCase();
181 | const colorsList = Object.keys(CSS_COLORS);
182 |
183 | // See if we have a CSS color name in our input value.
184 | colorsList.forEach((color) => {
185 | const colorMatchRegex = new RegExp(`\\b${color.toLowerCase()}\\b`);
186 |
187 | if (colorMatchRegex.test(cleanedInputValue)) {
188 | const {
189 | [color]: {
190 | rgb: rgbColor
191 | }
192 | } = CSS_COLORS;
193 |
194 | swatchColor = rgbColor;
195 | }
196 | });
197 | }
198 |
199 | this.setState({
200 | inputValue: conditionedInputColorValue || inputValue,
201 | swatchColor
202 | });
203 | }
204 |
205 | render() {
206 | const {
207 | label
208 | } = this.props;
209 |
210 | const {
211 | inputValue,
212 | isCopiedTooltipActive,
213 | swatchColor
214 | } = this.state;
215 |
216 | return (
217 |
218 |
221 | {label}
222 |
223 |
229 | {
230 | swatchColor
231 | && (
232 |
237 | )
238 | }
239 |
240 |
241 | );
242 | }
243 | }
244 |
245 | InputComponent.displayName = 'InputComponent';
246 |
247 | InputComponent.propTypes = {
248 | inputValue: PropTypes.string.isRequired,
249 | label: PropTypes.string.isRequired,
250 | noFormat: PropTypes.bool
251 | };
252 |
253 | InputComponent.defaultProps = {
254 | noFormat: false
255 | };
256 |
257 | export default InputComponent;
258 |
--------------------------------------------------------------------------------
/.stylelintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": [
3 | "stylelint-config-rational-order"
4 | ],
5 | "plugins": [
6 | "stylelint-scss",
7 | "stylelint-declaration-block-no-ignored-properties"
8 | ],
9 | "rules": {
10 | "at-rule-empty-line-before": [
11 | "always",
12 | {
13 | "except": [
14 | "after-same-name",
15 | "first-nested"
16 | ],
17 | "ignore": [
18 | "after-comment"
19 | ],
20 | "ignoreAtRules": [
21 | "else"
22 | ]
23 | }
24 | ],
25 | "at-rule-name-case": "lower",
26 | "at-rule-name-space-after": "always",
27 | "at-rule-no-vendor-prefix": true,
28 | "at-rule-semicolon-newline-after": "always",
29 | "at-rule-semicolon-space-before": "never",
30 | "block-closing-brace-empty-line-before": "never",
31 | "block-closing-brace-newline-after": [
32 | "always",
33 | {
34 | "ignoreAtRules": [
35 | "if"
36 | ]
37 | }
38 | ],
39 | "block-closing-brace-newline-before": "always",
40 | "block-no-empty": true,
41 | "block-opening-brace-newline-after": "always",
42 | "block-opening-brace-space-before": "always",
43 | "color-hex-case": "lower",
44 | "color-hex-length": "short",
45 | "color-named": "never",
46 | "color-no-hex": true,
47 | "color-no-invalid-hex": true,
48 | "comment-empty-line-before": "always",
49 | "comment-no-empty": true,
50 | "comment-whitespace-inside": "always",
51 | "custom-property-empty-line-before": "always",
52 | "declaration-bang-space-after": "never",
53 | "declaration-bang-space-before": "always",
54 | "declaration-block-no-duplicate-properties": true,
55 | "declaration-block-no-shorthand-property-overrides": true,
56 | "declaration-block-semicolon-newline-after": "always",
57 | "declaration-block-semicolon-newline-before": "never-multi-line",
58 | "declaration-block-semicolon-space-after": "always-single-line",
59 | "declaration-block-semicolon-space-before": "never",
60 | "declaration-block-single-line-max-declarations": 1,
61 | "declaration-block-trailing-semicolon": "always",
62 | "declaration-colon-newline-after": "always-multi-line",
63 | "declaration-colon-space-after": "always",
64 | "declaration-colon-space-before": "never",
65 | "font-family-name-quotes": "always-where-required",
66 | "font-family-no-duplicate-names": true,
67 | "font-family-no-missing-generic-family-keyword": true,
68 | "font-weight-notation": "named-where-possible",
69 | "function-calc-no-invalid": true,
70 | "function-calc-no-unspaced-operator": true,
71 | "function-comma-newline-after": "always-multi-line",
72 | "function-comma-newline-before": "never-multi-line",
73 | "function-comma-space-after": "always",
74 | "function-comma-space-before": "never",
75 | "function-linear-gradient-no-nonstandard-direction": true,
76 | "function-max-empty-lines": 0,
77 | "function-name-case": "lower",
78 | "function-parentheses-newline-inside": "always-multi-line",
79 | "function-parentheses-space-inside": "never",
80 | "function-url-no-scheme-relative": true,
81 | "function-url-quotes": "always",
82 | "function-whitespace-after": "always",
83 | "indentation": 4,
84 | "keyframe-declaration-no-important": true,
85 | "length-zero-no-unit": true,
86 | "max-empty-lines": 1,
87 | "max-nesting-depth": 6,
88 | "media-feature-colon-space-after": "always",
89 | "media-feature-colon-space-before": "never",
90 | "media-feature-name-case": "lower",
91 | "media-feature-name-no-unknown": true,
92 | "media-feature-name-no-vendor-prefix": true,
93 | "media-feature-parentheses-space-inside": "never",
94 | "media-feature-range-operator-space-after": "always",
95 | "media-feature-range-operator-space-before": "always",
96 | "media-query-list-comma-newline-after": "never-multi-line",
97 | "media-query-list-comma-newline-before": "never-multi-line",
98 | "media-query-list-comma-space-after": "always",
99 | "media-query-list-comma-space-before": "never",
100 | "no-duplicate-at-import-rules": true,
101 | "no-duplicate-selectors": true,
102 | "no-empty-first-line": true,
103 | "no-empty-source": true,
104 | "no-eol-whitespace": true,
105 | "no-extra-semicolons": true,
106 | "no-invalid-double-slash-comments": true,
107 | "no-missing-end-of-source-newline": true,
108 | "no-unknown-animations": true,
109 | "number-leading-zero": "always",
110 | "number-max-precision": 4,
111 | "number-no-trailing-zeros": true,
112 | "plugin/declaration-block-no-ignored-properties": true,
113 | "plugin/rational-order": [
114 | true,
115 | {
116 | "border-in-box-model": false,
117 | "empty-line-between-groups": true
118 | }
119 | ],
120 | "property-case": "lower",
121 | "property-no-unknown": true,
122 | "property-no-vendor-prefix": true,
123 | "rule-empty-line-before": [
124 | "always",
125 | {
126 | "except": [
127 | "first-nested"
128 | ],
129 | "ignore": [
130 | "after-comment"
131 | ]
132 | }
133 | ],
134 | "scss/at-else-closing-brace-newline-after": "always-last-in-chain",
135 | "scss/at-else-closing-brace-space-after": "always-intermediate",
136 | "scss/at-else-empty-line-before": "never",
137 | "scss/at-else-if-parentheses-space-before": "always",
138 | "scss/at-extend-no-missing-placeholder": true,
139 | "scss/at-function-parentheses-space-before": "always",
140 | "scss/at-if-closing-brace-newline-after": "always-last-in-chain",
141 | "scss/at-if-closing-brace-space-after": "always-intermediate",
142 | "scss/at-if-no-null": true,
143 | "scss/at-import-no-partial-leading-underscore": true,
144 | "scss/at-import-partial-extension": "never",
145 | "scss/at-mixin-argumentless-call-parentheses": "always",
146 | "scss/at-mixin-named-arguments": "never",
147 | "scss/at-mixin-parentheses-space-before": "never",
148 | "scss/at-rule-no-unknown": true,
149 | "scss/declaration-nested-properties": "never",
150 | "scss/declaration-nested-properties-no-divided-groups": true,
151 | "scss/dollar-variable-colon-newline-after": "always-multi-line",
152 | "scss/dollar-variable-colon-space-after": "always-single-line",
153 | "scss/dollar-variable-colon-space-before": "never",
154 | "scss/dollar-variable-empty-line-before": [
155 | "always",
156 | {
157 | "except": [
158 | "after-comment",
159 | "after-dollar-variable",
160 | "first-nested"
161 | ]
162 | }
163 | ],
164 | "scss/dollar-variable-no-missing-interpolation": true,
165 | "scss/double-slash-comment-empty-line-before": [
166 | "always",
167 | {
168 | "except": [
169 | "first-nested"
170 | ],
171 | "ignore": [
172 | "between-comments"
173 | ]
174 | }
175 | ],
176 | "scss/double-slash-comment-whitespace-inside": "always",
177 | "scss/function-quote-no-quoted-strings-inside": true,
178 | "scss/function-unquote-no-unquoted-strings-inside": true,
179 | "scss/map-keys-quotes": "always",
180 | "scss/media-feature-value-dollar-variable": "always",
181 | "scss/no-duplicate-dollar-variables": true,
182 | "scss/no-duplicate-mixins": true,
183 | "scss/operator-no-newline-after": true,
184 | "scss/operator-no-unspaced": true,
185 | "scss/selector-nest-combinators": "always",
186 | "selector-attribute-brackets-space-inside": "never",
187 | "selector-attribute-operator-space-after": "always",
188 | "selector-attribute-operator-space-before": "always",
189 | "selector-attribute-quotes": "always",
190 | "selector-class-pattern": [
191 | "^([a-zA-Z]+(\\-[a-zA-Z]+)*)((\\_\\_[a-zA-Z]+(\\-[a-zA-Z]+)*(\\-\\-[a-zA-Z]+(\\-[a-zA-Z]+)*)?)?|(\\-\\-[a-zA-Z]+(\\-[a-zA-Z]+)*)?)?$",
192 | {
193 | "message": "Class name should follow BEM notation: ReactComponent__some-element--and-modifier",
194 | "resolveNestedSelectors": true
195 | }
196 | ],
197 | "selector-combinator-space-after": "always",
198 | "selector-combinator-space-before": "always",
199 | "selector-descendant-combinator-no-non-space": true,
200 | "selector-list-comma-newline-after": "always",
201 | "selector-list-comma-newline-before": "never-multi-line",
202 | "selector-list-comma-space-after": "always-single-line",
203 | "selector-list-comma-space-before": "never",
204 | "selector-max-attribute": 2,
205 | "selector-max-class": 3,
206 | "selector-max-combinators": 4,
207 | "selector-max-compound-selectors": 4,
208 | "selector-max-empty-lines": 0,
209 | "selector-max-id": 2,
210 | "selector-max-pseudo-class": 2,
211 | "selector-max-type": 3,
212 | "selector-max-universal": 1,
213 | "selector-no-qualifying-type": true,
214 | "selector-no-vendor-prefix": true,
215 | "selector-pseudo-class-case": "lower",
216 | "selector-pseudo-class-no-unknown": true,
217 | "selector-pseudo-class-parentheses-space-inside": "never",
218 | "selector-pseudo-element-case": "lower",
219 | "selector-pseudo-element-colon-notation": "double",
220 | "selector-pseudo-element-no-unknown": true,
221 | "selector-type-case": "lower",
222 | "selector-type-no-unknown": true,
223 | "shorthand-property-no-redundant-values": true,
224 | "string-no-newline": true,
225 | "string-quotes": "single",
226 | "time-min-milliseconds": 50,
227 | "unicode-bom": "never",
228 | "unit-case": "lower",
229 | "unit-no-unknown": true,
230 | "unit-allowed-list": [
231 | "%",
232 | "deg",
233 | "fr",
234 | "px",
235 | "s",
236 | "vh",
237 | "vw"
238 | ],
239 | "value-keyword-case": "lower",
240 | "value-list-comma-newline-after": "always-multi-line",
241 | "value-list-comma-newline-before": "never-multi-line",
242 | "value-list-comma-space-after": "always-single-line",
243 | "value-list-comma-space-before": "never",
244 | "value-list-max-empty-lines": 0,
245 | "value-no-vendor-prefix": true
246 | }
247 | }
248 |
--------------------------------------------------------------------------------
/src/interfacers/artboardInterfacer.js:
--------------------------------------------------------------------------------
1 | import $ from 'jquery';
2 |
3 | /**
4 | * We invoke this function to handle document scrolling. This is useful
5 | * after artboard sizing or when zooming.
6 | *
7 | * @param {object} scrollValues
8 | */
9 | export const scrollDocument = (scrollValues) => {
10 | const {
11 | left,
12 | top
13 | } = scrollValues;
14 |
15 | $('html, body').scrollTop(top);
16 | $('html, body').scrollLeft(left);
17 | };
18 |
19 | /**
20 | * We invoke this function to correctly center our artboard after
21 | * we have sized it and our wrapper.
22 | *
23 | * @param {Object} dimensions
24 | */
25 | export const scrollCenterArtboard = (dimensions) => {
26 | const {
27 | artboardHeight,
28 | artboardWidth,
29 | documentZoom = 100,
30 | zoomWrapperPadding
31 | } = dimensions;
32 |
33 | const zoomMultiplier = documentZoom / 100;
34 |
35 | const left = zoomWrapperPadding - (($(window).innerWidth() - (artboardWidth * zoomMultiplier)) / 2);
36 | const top = zoomWrapperPadding - (($(window).innerHeight() - (artboardHeight * zoomMultiplier)) / 2);
37 |
38 | scrollDocument({
39 | left,
40 | top
41 | });
42 | };
43 |
44 | /**
45 | * We invoke this function to recenter our artboard after zooming to
46 | * keep our zooms looking smooth.
47 | *
48 | * @param {object} dimensions
49 | */
50 | export const calculateScrollAfterZoom = (dimensions) => {
51 | const {
52 | artboardHeight,
53 | artboardWidth,
54 | documentZoom,
55 | previousZoom
56 | } = dimensions;
57 |
58 | const bodyScrollTop = $('body').scrollTop() === 0 ? $('html').scrollTop() : $('body').scrollTop();
59 | const bodyScrollLeft = $('body').scrollLeft() === 0 ? $('html').scrollLeft() : $('body').scrollLeft();
60 |
61 | const top = bodyScrollTop + ((((artboardHeight) * (documentZoom / 100)) - (artboardHeight * (previousZoom / 100))) / 2);
62 | const left = bodyScrollLeft + ((((artboardWidth) * (documentZoom / 100)) - (artboardWidth * (previousZoom / 100))) / 2);
63 |
64 | scrollDocument({
65 | left,
66 | top
67 | });
68 | };
69 |
70 | /**
71 | * If our base wrapper doesn't already have a background color set,
72 | * we'll go ahead and set it as white.
73 | */
74 | const styleArtboardBase = () => {
75 | const backgroundColor = window.getComputedStyle(document.querySelector('body')).getPropertyValue('background-color');
76 | const baseArtboard = document.querySelector('#base');
77 |
78 | // If the user set a specific background color, use it.
79 | if (!(backgroundColor === 'transparent' || backgroundColor.match(/rgba\(\d+,\s\d+,\s\d+,\s0\)/))) {
80 | $('#base').css('background-color', 'rgba(255, 255, 255, 0.2)');
81 | baseArtboard.style.backgroundColor = backgroundColor;
82 | }
83 | };
84 |
85 | /**
86 | * This function attempts to strip out the native inspect tool
87 | * and to intercept any events fired by the tool, especially
88 | * when switching from preview/inspect tabs. It's some weird
89 | * logic, I know...
90 | */
91 | const removeInspectTool = () => {
92 | try {
93 | const clipFrameScroll = window.parent.document.querySelector('#clipFrameScroll');
94 | const htmlDocument = document.documentElement;
95 | const iFrame = window.parent.document.querySelector('#mainFrame');
96 |
97 | // Native Inspect Tool Elements
98 | const handoffMarkupContainer = window.parent.document.querySelector('#handoffMarkupContainer');
99 | const rsplitbar = window.parent.document.querySelector('#rsplitbar');
100 | const sidebar = window.parent.document.querySelector('#handoffHost');
101 |
102 | const hasNativeInspectTool = !!handoffMarkupContainer;
103 |
104 | let lastScrollPosition = {};
105 |
106 | const initScrollPosition = () => new Promise((resolve) => {
107 | /**
108 | * In some RP9 projects without the native inspect tool, we
109 | * sometimes measure scroll values before the whole artboard
110 | * is rendered correctly and we'll get 0. We have to wait
111 | * until it's loaded before proceeding.
112 | */
113 | const waitScrollEstablished = setInterval(() => {
114 | const {
115 | clientHeight,
116 | clientWidth,
117 | scrollHeight,
118 | scrollWidth
119 | } = htmlDocument;
120 |
121 | // Default Scroll Positions - Artboard Centered
122 | const scrollLeft = (scrollWidth - clientWidth) / 2;
123 | const scrollTop = (scrollHeight - clientHeight) / 2;
124 |
125 | // Once we have a correctly rendered artboard, proceed.
126 | if (scrollLeft && scrollTop) {
127 | clearInterval(waitScrollEstablished);
128 |
129 | lastScrollPosition = {
130 | scrollLeft,
131 | scrollTop
132 | };
133 |
134 | scrollDocument({
135 | left: scrollLeft,
136 | top: scrollTop
137 | });
138 |
139 | resolve();
140 | }
141 | }, 10);
142 | });
143 |
144 | initScrollPosition().then(() => {
145 | window.addEventListener('scroll', () => {
146 | const {
147 | scrollLeft,
148 | scrollTop
149 | } = htmlDocument;
150 |
151 | const SCROLL_DEVIATION = 50;
152 |
153 | const {
154 | scrollLeft: scrollLeftPrevious,
155 | scrollTop: scrollTopPrevious
156 | } = lastScrollPosition;
157 |
158 | /**
159 | * Here we're checking to see if it's a natural scroll
160 | * performed by the user or if AxShare is causing the
161 | * document to scroll to the top left. If it's natural,
162 | * we record it.
163 | */
164 | if ((scrollLeft === 0 && scrollLeftPrevious - scrollLeft < SCROLL_DEVIATION) || scrollLeft !== 0) {
165 | lastScrollPosition.scrollLeft = scrollLeft;
166 | }
167 | if ((scrollTop === 0 && scrollTopPrevious - scrollTop < SCROLL_DEVIATION) || scrollTop !== 0) {
168 | lastScrollPosition.scrollTop = scrollTop;
169 | }
170 | });
171 |
172 | /**
173 | * Listen for resize event and scroll back to last known position
174 | * otherwise we'll end up jumping to the top left corner because
175 | * of something in AxShare RP9 projects.
176 | */
177 | window.addEventListener('resize', () => {
178 | const {
179 | scrollLeft,
180 | scrollTop
181 | } = lastScrollPosition;
182 |
183 | scrollDocument({
184 | left: scrollLeft,
185 | top: scrollTop
186 | });
187 | });
188 | });
189 |
190 | // Only proceed if we're actually dealing with the inspect tool.
191 | if (hasNativeInspectTool) {
192 | const setiFrameAttributes = () => {
193 | // The native inspect tool keeps changing these on resize or tab toggle.
194 | clipFrameScroll.style.overflow = 'hidden';
195 | document.body.style.overflow = 'auto';
196 | iFrame.style.height = '100%';
197 | iFrame.style.minWidth = '100%';
198 | iFrame.style.width = '100%';
199 |
200 | const {
201 | scrollLeft,
202 | scrollTop
203 | } = lastScrollPosition;
204 |
205 | // Recenter the artboard after resizing or switching tabs - caused by AxShare Inspect.
206 | scrollDocument({
207 | left: scrollLeft,
208 | top: scrollTop
209 | });
210 | };
211 |
212 | const extractCurrentURL = () => window.parent.location.href;
213 |
214 | setiFrameAttributes();
215 |
216 | // We're deleting all of the elements related to the native inspect tool.
217 | handoffMarkupContainer.parentNode.removeChild(handoffMarkupContainer);
218 | rsplitbar.parentNode.removeChild(rsplitbar);
219 | sidebar.parentNode.removeChild(sidebar);
220 |
221 | // Get URL of parent iFrame.
222 | let lastCheckedURL = extractCurrentURL();
223 |
224 | setInterval(() => {
225 | const currentURL = extractCurrentURL();
226 |
227 | // When switching from inspect to preview tab, the URL changes and we need to catch that.
228 | if (currentURL !== lastCheckedURL) {
229 | setiFrameAttributes();
230 |
231 | lastCheckedURL = currentURL;
232 |
233 | /**
234 | * We have to call this a second time because something is firing
235 | * in AxShare. Need to track down what they're up to.
236 | */
237 | setTimeout(() => {
238 | setiFrameAttributes();
239 | }, 250);
240 | }
241 | }, 100);
242 | }
243 | } catch (error) {
244 | // Just in case these elements aren't on the page.
245 | }
246 | };
247 |
248 | export const injectArtboard = (className) => new Promise((resolve) => {
249 | const artboard = document.getElementsByClassName(className)[0];
250 |
251 | let loadingTime = 0;
252 |
253 | // Keep checking back until AxShare has loaded.
254 | const waitBaseRenderInterval = setInterval(() => {
255 | const base = document.getElementById('base');
256 |
257 | // Track our loading time.
258 | loadingTime += 50;
259 |
260 | if (base) {
261 | // Remove traces of native inspect tool.
262 | removeInspectTool();
263 |
264 | clearInterval(waitBaseRenderInterval);
265 | artboard.appendChild(base);
266 | resolve(loadingTime);
267 | }
268 | }, 50);
269 | });
270 |
271 | export const sizeArtboard = () => new Promise((resolve) => {
272 | let currentElement;
273 | let height = 0;
274 | let hiddenHeight = false;
275 | let hiddenWidth = false;
276 | let left = 0;
277 | let maxHeight = 0;
278 | let maxWidth = 0;
279 | let parentElementHorizontal;
280 | let parentElementVertical;
281 | let scrollHeightHidden = 0;
282 | let scrollWidthHidden = 0;
283 | let top = 0;
284 | let width = 0;
285 |
286 | $('#base *').not('script, style').each((index, node) => {
287 | // Capture jQuery reference to node.
288 | currentElement = $(node);
289 |
290 | if (parentElementHorizontal === undefined && parentElementVertical === undefined) {
291 | parentElementHorizontal = currentElement;
292 | parentElementVertical = currentElement;
293 | }
294 |
295 | width = currentElement.outerWidth();
296 | height = currentElement.outerHeight();
297 | scrollWidthHidden = currentElement[0].scrollWidth;
298 | scrollHeightHidden = currentElement[0].scrollHeight;
299 | top = currentElement.offset().top; // eslint-disable-line prefer-destructuring
300 | left = currentElement.offset().left; // eslint-disable-line prefer-destructuring
301 |
302 | // Check if we're still within the parent containing horizontal-scrolling overflow.
303 | if (!$.contains(parentElementHorizontal[0], currentElement[0])) {
304 | hiddenWidth = false;
305 | }
306 |
307 | // Check if we're still within the parent containing vertical-scrolling overflow.
308 | if (!$.contains(parentElementVertical[0], currentElement[0])) {
309 | hiddenHeight = false;
310 | }
311 |
312 | // Check if we've found an element with horizontal-scrolling content.
313 | if (!hiddenWidth) {
314 | maxWidth = maxWidth < left + width ? left + width : maxWidth;
315 | } else if (currentElement.width() > maxWidth) {
316 | currentElement.addClass('redline-layer');
317 | }
318 | if (scrollWidthHidden > width && !hiddenWidth && width > 0) {
319 | hiddenWidth = true;
320 | parentElementHorizontal = currentElement;
321 | }
322 |
323 | // Check if we've found an element with vertical-scrolling content.
324 | if (!hiddenHeight) {
325 | maxHeight = maxHeight < top + height ? top + height : maxHeight;
326 | } else if (currentElement.height() > maxHeight) {
327 | currentElement.addClass('redline-layer');
328 | }
329 | if (scrollHeightHidden > height && !hiddenHeight && height > 0) {
330 | hiddenHeight = true;
331 | parentElementVertical = currentElement;
332 | }
333 | });
334 |
335 | styleArtboardBase();
336 |
337 | resolve({
338 | artboardHeight: maxHeight,
339 | artboardWidth: maxWidth
340 | });
341 | });
342 |
--------------------------------------------------------------------------------
/src/utils/cssColors.js:
--------------------------------------------------------------------------------
1 | const cssColors = {
2 | AliceBlue: {
3 | hex: '#f0f8ff',
4 | rgb: 'rgb(240, 248, 255)'
5 | },
6 | AntiqueWhite: {
7 | hex: '#faebd7',
8 | rgb: 'rgb(250, 235, 215)'
9 | },
10 | Aqua: {
11 | hex: '#00ffff',
12 | rgb: 'rgb(0, 255, 255)'
13 | },
14 | Aquamarine: {
15 | hex: '#7fffd4',
16 | rgb: 'rgb(127, 255, 212)'
17 | },
18 | Azure: {
19 | hex: '#f0ffff',
20 | rgb: 'rgb(240, 255, 255)'
21 | },
22 | Beige: {
23 | hex: '#f5f5dc',
24 | rgb: 'rgb(245, 245, 220)'
25 | },
26 | Bisque: {
27 | hex: '#ffe4c4',
28 | rgb: 'rgb(255, 228, 196)'
29 | },
30 | Black: {
31 | hex: '#000000',
32 | rgb: 'rgb(0, 0, 0)'
33 | },
34 | BlanchedAlmond: {
35 | hex: '#ffebcd',
36 | rgb: 'rgb(255, 235, 205)'
37 | },
38 | Blue: {
39 | hex: '#0000ff',
40 | rgb: 'rgb(0, 0, 255)'
41 | },
42 | BlueViolet: {
43 | hex: '#8a2be2',
44 | rgb: 'rgb(138, 43, 226)'
45 | },
46 | Brown: {
47 | hex: '#a52a2a',
48 | rgb: 'rgb(165, 42, 42)'
49 | },
50 | BurlyWood: {
51 | hex: '#deb887',
52 | rgb: 'rgb(222, 184, 135)'
53 | },
54 | CadetBlue: {
55 | hex: '#5f9ea0',
56 | rgb: 'rgb(95, 158, 160)'
57 | },
58 | Chartreuse: {
59 | hex: '#7fff00',
60 | rgb: 'rgb(127, 255, 0)'
61 | },
62 | Chocolate: {
63 | hex: '#d2691e',
64 | rgb: 'rgb(210, 105, 30)'
65 | },
66 | Coral: {
67 | hex: '#ff7f50',
68 | rgb: 'rgb(255, 127, 80)'
69 | },
70 | CornflowerBlue: {
71 | hex: '#6495ed',
72 | rgb: 'rgb(100, 149, 237)'
73 | },
74 | Cornsilk: {
75 | hex: '#fff8dc',
76 | rgb: 'rgb(255, 248, 220)'
77 | },
78 | Crimson: {
79 | hex: '#dc143c',
80 | rgb: 'rgb(220, 20, 60)'
81 | },
82 | Cyan: {
83 | hex: '#00ffff',
84 | rgb: 'rgb(0, 255, 255)'
85 | },
86 | DarkBlue: {
87 | hex: '#00008b',
88 | rgb: 'rgb(0, 0, 139)'
89 | },
90 | DarkCyan: {
91 | hex: '#008b8b',
92 | rgb: 'rgb(0, 139, 139)'
93 | },
94 | DarkGoldenRod: {
95 | hex: '#b8860b',
96 | rgb: 'rgb(184, 134, 11)'
97 | },
98 | DarkGray: {
99 | hex: '#a9a9a9',
100 | rgb: 'rgb(169, 169, 169)'
101 | },
102 | DarkGreen: {
103 | hex: '#006400',
104 | rgb: 'rgb(0, 100, 0)'
105 | },
106 | DarkGrey: {
107 | hex: '#a9a9a9',
108 | rgb: 'rgb(169, 169, 169)'
109 | },
110 | DarkKhaki: {
111 | hex: '#bdb76b',
112 | rgb: 'rgb(189, 183, 107)'
113 | },
114 | DarkMagenta: {
115 | hex: '#8b008b',
116 | rgb: 'rgb(139, 0, 139)'
117 | },
118 | DarkOliveGreen: {
119 | hex: '#556b2f',
120 | rgb: 'rgb(85, 107, 47)'
121 | },
122 | DarkOrange: {
123 | hex: '#ff8c00',
124 | rgb: 'rgb(255, 140, 0)'
125 | },
126 | DarkOrchid: {
127 | hex: '#9932cc',
128 | rgb: 'rgb(153, 50, 204)'
129 | },
130 | DarkRed: {
131 | hex: '#8b0000',
132 | rgb: 'rgb(139, 0, 0)'
133 | },
134 | DarkSalmon: {
135 | hex: '#e9967a',
136 | rgb: 'rgb(233, 150, 122)'
137 | },
138 | DarkSeaGreen: {
139 | hex: '#8fbc8f',
140 | rgb: 'rgb(143, 188, 143)'
141 | },
142 | DarkSlateBlue: {
143 | hex: '#483d8b',
144 | rgb: 'rgb(72, 61, 139)'
145 | },
146 | DarkSlateGray: {
147 | hex: '#2f4f4f',
148 | rgb: 'rgb(47, 79, 79)'
149 | },
150 | DarkSlateGrey: {
151 | hex: '#2f4f4f',
152 | rgb: 'rgb(47, 79, 79)'
153 | },
154 | DarkTurquoise: {
155 | hex: '#00ced1',
156 | rgb: 'rgb(0, 206, 209)'
157 | },
158 | DarkViolet: {
159 | hex: '#9400d3',
160 | rgb: 'rgb(148, 0, 211)'
161 | },
162 | DeepPink: {
163 | hex: '#ff1493',
164 | rgb: 'rgb(255, 20, 147)'
165 | },
166 | DeepSkyBlue: {
167 | hex: '#00bfff',
168 | rgb: 'rgb(0, 191, 255)'
169 | },
170 | DimGray: {
171 | hex: '#696969',
172 | rgb: 'rgb(105, 105, 105)'
173 | },
174 | DimGrey: {
175 | hex: '#696969',
176 | rgb: 'rgb(105, 105, 105)'
177 | },
178 | DodgerBlue: {
179 | hex: '#1e90ff',
180 | rgb: 'rgb(30, 144, 255)'
181 | },
182 | FireBrick: {
183 | hex: '#b22222',
184 | rgb: 'rgb(178, 34, 34)'
185 | },
186 | FloralWhite: {
187 | hex: '#fffaf0',
188 | rgb: 'rgb(255, 250, 240)'
189 | },
190 | ForestGreen: {
191 | hex: '#228b22',
192 | rgb: 'rgb(34, 139, 34)'
193 | },
194 | Fuchsia: {
195 | hex: '#ff00ff',
196 | rgb: 'rgb(255, 0, 255)'
197 | },
198 | Gainsboro: {
199 | hex: '#dcdcdc',
200 | rgb: 'rgb(220, 220, 220)'
201 | },
202 | GhostWhite: {
203 | hex: '#f8f8ff',
204 | rgb: 'rgb(248, 248, 255)'
205 | },
206 | Gold: {
207 | hex: '#ffd700',
208 | rgb: 'rgb(255, 215, 0)'
209 | },
210 | GoldenRod: {
211 | hex: '#daa520',
212 | rgb: 'rgb(218, 165, 32)'
213 | },
214 | Gray: {
215 | hex: '#808080',
216 | rgb: 'rgb(128, 128, 128)'
217 | },
218 | Green: {
219 | hex: '#008000',
220 | rgb: 'rgb(0, 128, 0)'
221 | },
222 | GreenYellow: {
223 | hex: '#adff2f',
224 | rgb: 'rgb(173, 255, 47)'
225 | },
226 | Grey: {
227 | hex: '#808080',
228 | rgb: 'rgb(128, 128, 128)'
229 | },
230 | HoneyDew: {
231 | hex: '#f0fff0',
232 | rgb: 'rgb(240, 255, 240)'
233 | },
234 | HotPink: {
235 | hex: '#ff69b4',
236 | rgb: 'rgb(255, 105, 180)'
237 | },
238 | IndianRed: {
239 | hex: '#cd5c5c',
240 | rgb: 'rgb(205, 92, 92)'
241 | },
242 | Indigo: {
243 | hex: '#4b0082',
244 | rgb: 'rgb(75, 0, 130)'
245 | },
246 | Ivory: {
247 | hex: '#fffff0',
248 | rgb: 'rgb(255, 255, 240)'
249 | },
250 | Khaki: {
251 | hex: '#f0e68c',
252 | rgb: 'rgb(240, 230, 140)'
253 | },
254 | Lavender: {
255 | hex: '#e6e6fa',
256 | rgb: 'rgb(230, 230, 250)'
257 | },
258 | LavenderBlush: {
259 | hex: '#fff0f5',
260 | rgb: 'rgb(255, 240, 245)'
261 | },
262 | LawnGreen: {
263 | hex: '#7cfc00',
264 | rgb: 'rgb(124, 252, 0)'
265 | },
266 | LemonChiffon: {
267 | hex: '#fffacd',
268 | rgb: 'rgb(255, 250, 205)'
269 | },
270 | LightBlue: {
271 | hex: '#add8e6',
272 | rgb: 'rgb(173, 216, 230)'
273 | },
274 | LightCoral: {
275 | hex: '#f08080',
276 | rgb: 'rgb(240, 128, 128)'
277 | },
278 | LightCyan: {
279 | hex: '#e0ffff',
280 | rgb: 'rgb(224, 255, 255)'
281 | },
282 | LightGoldenRodYellow: {
283 | hex: '#fafad2',
284 | rgb: 'rgb(250, 250, 210)'
285 | },
286 | LightGray: {
287 | hex: '#d3d3d3',
288 | rgb: 'rgb(211, 211, 211)'
289 | },
290 | LightGreen: {
291 | hex: '#90ee90',
292 | rgb: 'rgb(144, 238, 144)'
293 | },
294 | LightGrey: {
295 | hex: '#d3d3d3',
296 | rgb: 'rgb(211, 211, 211)'
297 | },
298 | LightPink: {
299 | hex: '#ffb6c1',
300 | rgb: 'rgb(255, 182, 193)'
301 | },
302 | LightSalmon: {
303 | hex: '#ffa07a',
304 | rgb: 'rgb(255, 160, 122)'
305 | },
306 | LightSeaGreen: {
307 | hex: '#20b2aa',
308 | rgb: 'rgb(32, 178, 170)'
309 | },
310 | LightSkyBlue: {
311 | hex: '#87cefa',
312 | rgb: 'rgb(135, 206, 250)'
313 | },
314 | LightSlateGray: {
315 | hex: '#778899',
316 | rgb: 'rgb(119, 136, 153)'
317 | },
318 | LightSlateGrey: {
319 | hex: '#778899',
320 | rgb: 'rgb(119, 136, 153)'
321 | },
322 | LightSteelBlue: {
323 | hex: '#b0c4de',
324 | rgb: 'rgb(176, 196, 222)'
325 | },
326 | LightYellow: {
327 | hex: '#ffffe0',
328 | rgb: 'rgb(255, 255, 224)'
329 | },
330 | Lime: {
331 | hex: '#00ff00',
332 | rgb: 'rgb(0, 255, 0)'
333 | },
334 | LimeGreen: {
335 | hex: '#32cd32',
336 | rgb: 'rgb(50, 205, 50)'
337 | },
338 | Linen: {
339 | hex: '#faf0e6',
340 | rgb: 'rgb(250, 240, 230)'
341 | },
342 | Magenta: {
343 | hex: '#ff00ff',
344 | rgb: 'rgb(255, 0, 255)'
345 | },
346 | Maroon: {
347 | hex: '#800000',
348 | rgb: 'rgb(128, 0, 0)'
349 | },
350 | MediumAquaMarine: {
351 | hex: '#66cdaa',
352 | rgb: 'rgb(102, 205, 170)'
353 | },
354 | MediumBlue: {
355 | hex: '#0000cd',
356 | rgb: 'rgb(0, 0, 205)'
357 | },
358 | MediumOrchid: {
359 | hex: '#ba55d3',
360 | rgb: 'rgb(186, 85, 211)'
361 | },
362 | MediumPurple: {
363 | hex: '#9370db',
364 | rgb: 'rgb(147, 112, 219)'
365 | },
366 | MediumSeaGreen: {
367 | hex: '#3cb371',
368 | rgb: 'rgb(60, 179, 113)'
369 | },
370 | MediumSlateBlue: {
371 | hex: '#7b68ee',
372 | rgb: 'rgb(123, 104, 238)'
373 | },
374 | MediumSpringGreen: {
375 | hex: '#00fa9a',
376 | rgb: 'rgb(0, 250, 154)'
377 | },
378 | MediumTurquoise: {
379 | hex: '#48d1cc',
380 | rgb: 'rgb(72, 209, 204)'
381 | },
382 | MediumVioletRed: {
383 | hex: '#c71585',
384 | rgb: 'rgb(199, 21, 133)'
385 | },
386 | MidnightBlue: {
387 | hex: '#191970',
388 | rgb: 'rgb(25, 25, 112)'
389 | },
390 | MintCream: {
391 | hex: '#f5fffa',
392 | rgb: 'rgb(245, 255, 250)'
393 | },
394 | MistyRose: {
395 | hex: '#ffe4e1',
396 | rgb: 'rgb(255, 228, 225)'
397 | },
398 | Moccasin: {
399 | hex: '#ffe4b5',
400 | rgb: 'rgb(255, 228, 181)'
401 | },
402 | NavajoWhite: {
403 | hex: '#ffdead',
404 | rgb: 'rgb(255, 222, 173)'
405 | },
406 | Navy: {
407 | hex: '#000080',
408 | rgb: 'rgb(0, 0, 128)'
409 | },
410 | OldLace: {
411 | hex: '#fdf5e6',
412 | rgb: 'rgb(253, 245, 230)'
413 | },
414 | Olive: {
415 | hex: '#808000',
416 | rgb: 'rgb(128, 128, 0)'
417 | },
418 | OliveDrab: {
419 | hex: '#6b8e23',
420 | rgb: 'rgb(107, 142, 35)'
421 | },
422 | Orange: {
423 | hex: '#ffa500',
424 | rgb: 'rgb(255, 165, 0)'
425 | },
426 | OrangeRed: {
427 | hex: '#ff4500',
428 | rgb: 'rgb(255, 69, 0)'
429 | },
430 | Orchid: {
431 | hex: '#da70d6',
432 | rgb: 'rgb(218, 112, 214)'
433 | },
434 | PaleGoldenRod: {
435 | hex: '#eee8aa',
436 | rgb: 'rgb(238, 232, 170)'
437 | },
438 | PaleGreen: {
439 | hex: '#98fb98',
440 | rgb: 'rgb(152, 251, 152)'
441 | },
442 | PaleTurquoise: {
443 | hex: '#afeeee',
444 | rgb: 'rgb(175, 238, 238)'
445 | },
446 | PaleVioletRed: {
447 | hex: '#db7093',
448 | rgb: 'rgb(219, 112, 147)'
449 | },
450 | PapayaWhip: {
451 | hex: '#ffefd5',
452 | rgb: 'rgb(255, 239, 213)'
453 | },
454 | PeachPuff: {
455 | hex: '#ffdab9',
456 | rgb: 'rgb(255, 218, 185)'
457 | },
458 | Peru: {
459 | hex: '#cd853f',
460 | rgb: 'rgb(205, 133, 63)'
461 | },
462 | Pink: {
463 | hex: '#ffc0cb',
464 | rgb: 'rgb(255, 192, 203)'
465 | },
466 | Plum: {
467 | hex: '#dda0dd',
468 | rgb: 'rgb(221, 160, 221)'
469 | },
470 | PowderBlue: {
471 | hex: '#b0e0e6',
472 | rgb: 'rgb(176, 224, 230)'
473 | },
474 | Purple: {
475 | hex: '#800080',
476 | rgb: 'rgb(128, 0, 128)'
477 | },
478 | RebeccaPurple: {
479 | hex: '#663399',
480 | rgb: 'rgb(102, 51, 153)'
481 | },
482 | Red: {
483 | hex: '#ff0000',
484 | rgb: 'rgb(255, 0, 0)'
485 | },
486 | RosyBrown: {
487 | hex: '#bc8f8f',
488 | rgb: 'rgb(188, 143, 143)'
489 | },
490 | RoyalBlue: {
491 | hex: '#4169e1',
492 | rgb: 'rgb(65, 105, 225)'
493 | },
494 | SaddleBrown: {
495 | hex: '#8b4513',
496 | rgb: 'rgb(139, 69, 19)'
497 | },
498 | Salmon: {
499 | hex: '#fa8072',
500 | rgb: 'rgb(250, 128, 114)'
501 | },
502 | SandyBrown: {
503 | hex: '#f4a460',
504 | rgb: 'rgb(244, 164, 96)'
505 | },
506 | SeaGreen: {
507 | hex: '#2e8b57',
508 | rgb: 'rgb(46, 139, 87)'
509 | },
510 | SeaShell: {
511 | hex: '#fff5ee',
512 | rgb: 'rgb(255, 245, 238)'
513 | },
514 | Sienna: {
515 | hex: '#a0522d',
516 | rgb: 'rgb(160, 82, 45)'
517 | },
518 | Silver: {
519 | hex: '#c0c0c0',
520 | rgb: 'rgb(192, 192, 192)'
521 | },
522 | SkyBlue: {
523 | hex: '#87ceeb',
524 | rgb: 'rgb(135, 206, 235)'
525 | },
526 | SlateBlue: {
527 | hex: '#6a5acd',
528 | rgb: 'rgb(106, 90, 205)'
529 | },
530 | SlateGray: {
531 | hex: '#708090',
532 | rgb: 'rgb(112, 128, 144)'
533 | },
534 | SlateGrey: {
535 | hex: '#708090',
536 | rgb: 'rgb(112, 128, 144)'
537 | },
538 | Snow: {
539 | hex: '#fffafa',
540 | rgb: 'rgb(255, 250, 250)'
541 | },
542 | SpringGreen: {
543 | hex: '#00ff7f',
544 | rgb: 'rgb(0, 255, 127)'
545 | },
546 | SteelBlue: {
547 | hex: '#4682b4',
548 | rgb: 'rgb(70, 130, 180)'
549 | },
550 | Tan: {
551 | hex: '#d2b48c',
552 | rgb: 'rgb(210, 180, 140)'
553 | },
554 | Teal: {
555 | hex: '#008080',
556 | rgb: 'rgb(0, 128, 128)'
557 | },
558 | Thistle: {
559 | hex: '#d8bfd8',
560 | rgb: 'rgb(216, 191, 216)'
561 | },
562 | Tomato: {
563 | hex: '#ff6347',
564 | rgb: 'rgb(255, 99, 71)'
565 | },
566 | Turquoise: {
567 | hex: '#40e0d0',
568 | rgb: 'rgb(64, 224, 208)'
569 | },
570 | Violet: {
571 | hex: '#ee82ee',
572 | rgb: 'rgb(238, 130, 238)'
573 | },
574 | Wheat: {
575 | hex: '#f5deb3',
576 | rgb: 'rgb(245, 222, 179)'
577 | },
578 | White: {
579 | hex: '#ffffff',
580 | rgb: 'rgb(255, 255, 255)'
581 | },
582 | WhiteSmoke: {
583 | hex: '#f5f5f5',
584 | rgb: 'rgb(245, 245, 245)'
585 | },
586 | Yellow: {
587 | hex: '#ffff00',
588 | rgb: 'rgb(255, 255, 0)'
589 | },
590 | YellowGreen: {
591 | hex: '#9acd32',
592 | rgb: 'rgb(154, 205, 50)'
593 | }
594 | };
595 |
596 | export default cssColors;
597 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Axure Interactive Redline Tool
2 |
3 | This plugin intends to mimic some of the functionality of the plugin [Measure](http://utom.design/measure/) for Sketch or InVision Inspect. This application is meant for those who rely on Axure in their organizations and would like to provide developers with always-up-to-date design specifications. As Axure does not support plugins within the application itself, this code resides within and is applied to your AxShare projects.
4 |
5 | [Super Basic Demo](https://4ciu30.axshare.com/#g=1&p=master-test)
6 |
7 | ## Plugin Usage
8 |
9 | Latest Version
10 | ```sh
11 |
12 | ```
13 |
14 | Current Version
15 | ```sh
16 |
17 | ```
18 |
19 | There are several ways to use this "install" this plugin. The easiest option is to copy the script link above into your AxShare project as a plugin. Copy the script of the version you'd like to use, or simply use the script marked "latest" to ensure you're always subscribed to the latest application updates. The code in the scripts above is served from a CDN to ensure a fast response, no matter your location.
20 | If you prefer to house the code directly in your AxShare project, you'll need to copy and paste the code found within [**/axure-redline-tool/web/plugin.txt**](https://github.com/srm985/axure-redline-tool/blob/master/web/plugin.txt) into your AxShare project as a plugin. The code combination is derived from the markup framework, CDN links, the pertinent CSS, and the supporting JavaScript / jQuery code base.
21 |
22 | To apply this code to one of your AxShare hosted projects, navigate to [www.share.axure.com](https://share.axure.com) and log into your account. Once logged in, you will see an inline gear icon to the far right of each Axure project. Hovering over this icon provides a list of options, including *PLUGINS* which you should select. Once on the plugin page, select *NEW PLUGIN*. Name your plugin whatever you deem appropriate and select *End of the Head* as the insertion location. Paste your plugin.min.htm code into the content area and save the plugin. Select all desired pages within which you'd like to have the interactive redline tool. If you would like to have the plugin appended to any new pages, you may select *Add to new pages by default*. Once saved, your plugin should be activated. To modify the plugin, simply select *edit* and paste in any replacement code.
23 |
24 | [Previous Releases](https://github.com/srm985/axure-redline-tool/releases)
25 |
26 | [Axure Plugin Tutorial](https://www.axure.com/c/forum/AxShare-general-discussion/9953-create-edit-plugin-AxShare-tutorial.html)
27 |
28 | 
29 |
30 | ## Installation / Running
31 |
32 | Install [Node.js](https://nodejs.org/en/download/)
33 |
34 | Update npm to the latest version:
35 |
36 | ```sh
37 | $ npm install npm@latest -g
38 | ```
39 |
40 | To launch a demo instance of the plugin in your browser, issue the following commands:
41 |
42 | ```sh
43 | $ cd axure-redline-tool
44 | $ npm install
45 | $ gulp develop
46 | ```
47 |
48 | #### Prerequisites / Dependencies
49 |
50 | This project was built and tested on jQuery 3.2 and Axure RP.
51 |
52 | ## Building Modified Plugin
53 |
54 | If you've made changes and would like to build an updated version of the plugin, run the following commands to generate ```plugin.txt``` and ```plugin.js```. Copy the contents of ```plugin.txt``` into your AxShare project as shown above.
55 |
56 | If you would like to modify the plugin, two build scripts are available to aid in this.
57 |
58 | To quickly build your changes for production issue the following commands:
59 |
60 | ```sh
61 | $ cd axure-redline-tool
62 | $ npm install
63 | $ gulp build-prod
64 | ```
65 |
66 | Because this is a compiled plugin i.e. HTML, CSS, and JS are merged into one file, you can also keep the plugin continuously watching for source changes. This will then automatically rebuild the plugin.txt file and you may then copy the plugin code directly into AxShare. This will not open an instance of the plugin in your browser. For this, issue the following commands:
67 |
68 | ```sh
69 | $ cd axure-redline-tool
70 | $ npm install
71 | $ gulp build-watch
72 | ```
73 |
74 | ## Bugs / Drawbacks
75 |
76 | As this code is embedded within Axure projects, it does not have direct control of how assets are exported from within Axure. Additionally, Axure projects allow much more functionality and interactivity than those generated in Sketch and it's difficult to intercept and interact with these. The code makes every attempt to handle the various nested elements exported from Axure, but if you do encounter an issue, please let me know and I'll promptly resolve it.
77 |
78 | You may find the generated artboard sizing odd during initial use. This tool scans all page elements and sizes the artboard based on the most-extreme elements. If you would like a specific size artboard, I would suggest using a background rectangle within Axure to define this. Alternatively, you may use a combination of vertical and horizontal lines to define your artboard border. For example, if you would like an artboard of 1000px x 1000px, place a rectangle as the backmost element in your Axure project with a X and Y location of 0 and dimensions of 1000px x 1000px.
79 |
80 | *Axure chooses to export some common elements such as circles and lines as images instead of using CSS. As such, you may find it difficult to find accurate dimensions on some items. A workaround for circles is to place a square in Axure and set the border radius greater than or equal to 50% of the square's dimensions. This will then export as an HTML element. Axure also has difficulties in exporting elements with lots of border attributes and box shadows. It typically exports these as images and you will be unable to retrieve any CSS attributes.
81 |
82 | ## Coming Features
83 |
84 | * Provide Sliced Images
85 | * Document Color Palette
86 |
87 | ## Versioning
88 |
89 | We use [SemVer](http://semver.org/) for versioning.
90 |
91 | ## Authors
92 |
93 | * **Sean McQuay** - *Initial work* - [GitHub](https://github.com/srm985) - [Website](http://www.seanmcquay.com)
94 |
95 | See also the list of [contributors](https://github.com/srm985/mok-project/contributors) who participated in this project.
96 |
97 | ## License
98 |
99 | This project is licensed under the MIT License - see the [LICENSE](https://github.com/srm985/axure-redline-tool/blob/master/LICENSE) file for details.
100 |
101 | ## Change Log
102 |
103 | #### Version 1.1
104 |
105 | * Resolved the issue of overflow scrolling content affecting artboard sizing. In Axure terms, hidden dynamic panel content no longer causes the generated artboard to be bigger than visible elements.
106 | * Resolved how the tool handles Axure annotations. Annotations can be read while the tool is disabled. While the tool is enabled, their icons are not considered interactive elements.
107 | * Added key command functionality to support zoom controls through [Ctrl +] / [Ctrl -] and Esc key to close the redline tool panel / deselect the element.
108 | * Added zoom tracking support to ensure current zoom level is maintained while progressing through flows.
109 |
110 | #### Version 1.1.1
111 |
112 | * Revised code to handle artboard rendering issues. Axure uses images instead of CSS for many common elements such as lines and circles. These exported images often have incorrect dimensions which cause the redline tool to incorrectly size the artboard. The code has been revised accordingly to handle these scenarios.
113 | * The tool now removes element focus when the page is scrolled. This issue caused the orange selection box to remain fixed while the element below was scrolled. Code currently closes the redline tool when scrolling occurs. Later enhancement will be to bind orange selection box to the element selected so that even with scrolling the box remains.
114 |
115 | #### Version 1.1.2
116 |
117 | * Build scripts have been ported from Grunt to Gulp. Additional build options are now available, and the code injection has been improved.
118 |
119 | #### Version 1.1.3
120 |
121 | * Provided color swatch preview for color and background-color attributes.
122 | * Now support HEX and RGB(A). Color formats can be toggled by clicking color swatch.
123 |
124 | #### Version 1.1.4
125 |
126 | * Provided support for repeater widgets. Updated code to ignore embedded script and style tags.
127 | * Added box-sizing attribute to inputs to ensure correct sizing across browsers.
128 |
129 | #### Version 1.2
130 |
131 | * Revised how inter-element dimensions are calculated. Previously, all elements were iterated, and data attributes appended. Now this is done in real-time, on only the active elements. This will help performance on pages with many elements.
132 | * Resolved issue where the tool displays unintended hidden content.
133 | * Resolved small CSS styling changes to improve consistency in displaying attributes.
134 | * Resolved issue where tool throws error when disabling while an element is selected.
135 | * Tool now provides correct artboard padding when zooming.
136 | * Resolved measurement flicker which occurred when hovering over a measurement line or tag.
137 |
138 | #### Version 2.0.1
139 |
140 | * Axure Component Names - The tool will now display any element names you've used in your project.
141 | * Annotations Always Clickable - Annotations are always available to click, even when the tool is enabled.
142 | * Move Redline Switch - The redline tool enable switch has been moved to the top header for better visibility.
143 | * Show Pseudo Class Attributes - The tool now provides CSS for those pseudo classes provided by Axure.
144 | * CDN Link - The tool can now be served from a CDN link which means faster load times and no more copying of huge blocks of code.
145 | * Hotkey Interactions - Now it is no longer required to disable the tool to interact with Axure elements. By holding down [Ctrl] (Windows) or [⌘] (Mac) you can hover over or click on elements.
146 | * Business / Developer Links - The tool now offers up sharing links for both business and developers. The business link will prevent the tool from even rendering itself on the page. This ensure there is no confusion when sharing your prototypes with business.
147 | * Loading Spinner - On large pages the tool can take quite long to render the artboard. A loading spinner helps to indicate background action.
148 | * Color Swatches - Updated code to handle color swatches for any CSS attribute containing a color.
149 | * Parent Opacity - When adjusting opacity for the entire element in Axure, it is only applied to the parent. The tool now can accurately extract this information.
150 |
151 | #### Version 2.0.3
152 |
153 | * Added CSS block properties field for easy cut and paste.
154 |
155 | #### Version 2.0.4
156 |
157 | * When RGBA colors are displayed with an opacity of 1, we now convert them to RGB.
158 | * If a CSS attribute opacity is 1, we now don't bother displaying it.
159 | * Resolved bug in Microsoft Edge creating issues when displaying RGBA color values.
160 |
161 | #### Version 2.0.5
162 |
163 | * When sharing the business link, annotations are disabled by default through the AxShare interface panel.
164 |
165 | #### Version 2.0.6
166 |
167 | * Revised how sharing links are formed to accommodate private enterprise server links.
168 |
169 | #### Version 2.0.7
170 |
171 | * Corrected page loading of HTML, CSS, and Google fonts. Loading of fonts was causing incorrect jQuery measurements of elements.
172 | * Updated CSS to support RP9 artboard generation.
173 | * Updated to support RP9 annotations.
174 |
175 | #### Version 3.0.0
176 |
177 | * Ported application to React.
178 | * Slight UI modifications.
179 | * Grid overlay support.
180 |
181 | #### Version 3.0.1
182 |
183 | * Corrected plugin path.
184 |
185 | #### Version 3.0.2
186 |
187 | * Added support for native inspect tool and Axure Cloud.
188 |
189 | #### Version 3.0.3
190 |
191 | * Corrected splash screen modal body issue.
192 |
193 | #### Version 3.0.4
194 |
195 | * Resolved interaction bugs with native inspect tool.
196 | * Resolved artboard padding click error.
197 | * Revised build tools for legacy support, ensuring no breaking changes.
198 |
199 | #### Version 3.0.5
200 |
201 | * Removed click functionality from #base which is essentially the artboard and shouldn't be interacted with.
202 | * Added opacity to #base artboard to style it more like a non-interactive element.
203 |
204 | #### Version 3.0.6
205 |
206 | * Added additional functionality to support jumping artboard in RP9 projects and projects using native inspect tool.
207 |
208 | #### Version 3.0.7
209 |
210 | * Corrected artboard background color configuration. For undefined artboard backgroud colors, the artboard is rendered as transparent white. Otherwise, the user-defined color is used.
211 | * Added functionality to dev-live script to support serving a custom URL.
212 |
213 | #### Version 3.1.0
214 |
215 | * Corrected CSS Color Name Matching - The Redline Tool supports the use of CSS color names in lieu of hex/RGB values. The matching algorithm which displays the swatch for the given color was partially matching other words. This meant if the field value was bored, a red color swatch was applied.
216 | * Added Modal Inspection Support - Modals or other elements which break out of the normal page flow were being incorrectly read by the tool. From a technical aspect, this was occurring on elements with position: fixed;.
217 | * Border Color Shown With No Border - On text blocks, the tool was reading a border-color, without a border style being defined. There is no reason to display this attribute without a border-style defined.
218 | * Text Shadow Attribute - Added the CSS attribute of text-shadow to the list.
219 | * Enhanced Support For Individual Word Styling - If you attempted to style one word differently within a text block by applying, for example, a different color or font weight, this would cause the styled word to break out of the text block. From a technical perspective, this occurred because we were setting <span> elements to display: inline-block;. This was done so we could accurately measure their height, width, and position. Now for these elements, we won't show a height, width, or position because it conveys no information. We instead select the parent container which is typically a <p> tag.
220 | * Disable Artboard Concept - The redline tool attempts to mimic the concept of an artboard used in many other tools. This is represented as a floating, semi-opaque, centered board. Some users have very large documents or do not prefer this concept.
221 |
222 | #### Version 3.1.1
223 |
224 | * Migrated from Gulp to npm for all build and development tasks
225 | * Updated dependencies
226 | * Added the CSS attributes of letter-spacing and text-transform
227 | * Added postcss for legacy browser support
228 |
--------------------------------------------------------------------------------
/src/modules/InterElementDimensionsModule/index.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types';
2 | import React from 'react';
3 |
4 | import DimensionLineComponent from '../../components/DimensionLineComponent';
5 | import { LINE_TYPE_INTER_ELEMENT } from '../../components/DimensionLineComponent/constants';
6 | import {
7 | DimensionMarkerComponent,
8 | HORIZONTAL_DIMENSION,
9 | VERTICAL_DIMENSION
10 | } from '../../components/DimensionMarkerComponent';
11 |
12 | class InterElementDimensionsModule extends React.PureComponent {
13 | render() {
14 | const {
15 | elementMarkerThickness,
16 | hoveredElement: {
17 | height: hoveredElementHeight,
18 | offsetLeft: hoveredElementOffsetLeft,
19 | offsetTop: hoveredElementOffsetTop,
20 | target: hoveredElementTarget,
21 | trueHeight: hoveredElementTrueHeight,
22 | trueOffsetLeft: hoveredElementTrueOffsetLeft,
23 | trueOffsetTop: hoveredElementTrueOffsetTop,
24 | trueWidth: hoveredElementTrueWidth,
25 | width: hoveredElementWidth
26 | },
27 | selectedElement: {
28 | height: selectedElementHeight,
29 | offsetLeft: selectedElementOffsetLeft,
30 | offsetTop: selectedElementOffsetTop,
31 | target: selectedElementTarget,
32 | trueHeight: selectedElementTrueHeight,
33 | trueOffsetLeft: selectedElementTrueOffsetLeft,
34 | trueOffsetTop: selectedElementTrueOffsetTop,
35 | trueWidth: selectedElementTrueWidth,
36 | width: selectedElementWidth
37 | }
38 | } = this.props;
39 |
40 | const INLINE_ELEMENT = 'inline';
41 |
42 | // If one of the elements is inline, none of our dimensions will be correct.
43 | const isDisplayInlineElement = window.getComputedStyle(hoveredElementTarget).getPropertyValue('display') === INLINE_ELEMENT
44 | || window.getComputedStyle(selectedElementTarget).getPropertyValue('display') === INLINE_ELEMENT;
45 |
46 | // Scaled element measurements.
47 | const diffSelectedLeftHoveredRight = selectedElementOffsetLeft - (hoveredElementOffsetLeft + hoveredElementWidth);
48 | const diffSelectedLeftHoveredLeft = selectedElementOffsetLeft - hoveredElementOffsetLeft;
49 |
50 | const diffSelectedRightHoveredRight = (hoveredElementOffsetLeft + hoveredElementWidth) - (selectedElementOffsetLeft + selectedElementWidth);
51 | const diffSelectedRightHoveredLeft = hoveredElementOffsetLeft - (selectedElementOffsetLeft + selectedElementWidth);
52 |
53 | const diffSelectedTopHoveredBottom = selectedElementOffsetTop - (hoveredElementOffsetTop + hoveredElementHeight);
54 | const diffSelectedTopHoveredTop = selectedElementOffsetTop - hoveredElementOffsetTop;
55 |
56 | const diffSelectedBottomHoveredBottom = (hoveredElementOffsetTop + hoveredElementHeight) - (selectedElementOffsetTop + selectedElementHeight);
57 | const diffSelectedBottomHoveredTop = hoveredElementOffsetTop - (selectedElementOffsetTop + selectedElementHeight);
58 |
59 | // True element measurements.
60 | const trueDiffSelectedLeftHoveredRight = selectedElementTrueOffsetLeft - (hoveredElementTrueOffsetLeft + hoveredElementTrueWidth);
61 | const trueDiffSelectedLeftHoveredLeft = selectedElementTrueOffsetLeft - hoveredElementTrueOffsetLeft;
62 |
63 | const trueDiffSelectedRightHoveredRight = (hoveredElementTrueOffsetLeft + hoveredElementTrueWidth) - (selectedElementTrueOffsetLeft + selectedElementTrueWidth);
64 | const trueDiffSelectedRightHoveredLeft = hoveredElementTrueOffsetLeft - (selectedElementTrueOffsetLeft + selectedElementTrueWidth);
65 |
66 | const trueDiffSelectedTopHoveredBottom = selectedElementTrueOffsetTop - (hoveredElementTrueOffsetTop + hoveredElementTrueHeight);
67 | const trueDiffSelectedTopHoveredTop = selectedElementTrueOffsetTop - hoveredElementTrueOffsetTop;
68 |
69 | const trueDiffSelectedBottomHoveredBottom = (hoveredElementTrueOffsetTop + hoveredElementTrueHeight) - (selectedElementTrueOffsetTop + selectedElementTrueHeight);
70 | const trueDiffSelectedBottomHoveredTop = hoveredElementTrueOffsetTop - (selectedElementTrueOffsetTop + selectedElementTrueHeight);
71 |
72 | let bottomLineLength = 0;
73 | let bottomLineOffsetTop = 0;
74 | let leftLineLength = 0;
75 | let leftLineOffsetLeft = 0;
76 | let rightLineLength = 0;
77 | let rightLineOffsetLeft = 0;
78 | let topLineLength = 0;
79 | let topLineOffsetTop = 0;
80 | let trueInterElementBottomLineWidth = 0;
81 | let trueInterElementLeftLineWidth = 0;
82 | let trueInterElementRightLineWidth = 0;
83 | let trueInterElementTopLineWidth = 0;
84 |
85 | // Left inter-element dimension line calculations.
86 | if (diffSelectedLeftHoveredRight > 0) {
87 | // Scaled measurement calculations.
88 | leftLineLength = diffSelectedLeftHoveredRight;
89 | leftLineOffsetLeft = hoveredElementOffsetLeft + hoveredElementWidth;
90 |
91 | // True inter-element dimensions.
92 | trueInterElementLeftLineWidth = trueDiffSelectedLeftHoveredRight;
93 | } else if (diffSelectedLeftHoveredLeft > 0) {
94 | // Scaled measurement calculations.
95 | leftLineLength = diffSelectedLeftHoveredLeft;
96 | leftLineOffsetLeft = hoveredElementOffsetLeft;
97 |
98 | // True inter-element dimensions.
99 | trueInterElementLeftLineWidth = trueDiffSelectedLeftHoveredLeft;
100 | } else if (diffSelectedLeftHoveredRight < 0
101 | && diffSelectedLeftHoveredLeft < 0
102 | && diffSelectedRightHoveredLeft < 0
103 | && diffSelectedRightHoveredRight < 0) {
104 | // Scaled measurement calculations.
105 | leftLineLength = Math.abs(diffSelectedLeftHoveredLeft);
106 | leftLineOffsetLeft = selectedElementOffsetLeft;
107 |
108 | // True inter-element dimensions.
109 | trueInterElementLeftLineWidth = Math.abs(trueDiffSelectedLeftHoveredLeft);
110 | }
111 |
112 | // Right inter-element dimension line calculations.
113 | if (diffSelectedRightHoveredLeft > 0) {
114 | // Scaled measurement calculations.
115 | rightLineLength = diffSelectedRightHoveredLeft;
116 | rightLineOffsetLeft = selectedElementOffsetLeft + selectedElementWidth;
117 |
118 | // True inter-element dimensions.
119 | trueInterElementRightLineWidth = trueDiffSelectedRightHoveredLeft;
120 | } else if (diffSelectedRightHoveredRight > 0) {
121 | // Scaled measurement calculations.
122 | rightLineLength = diffSelectedRightHoveredRight;
123 | rightLineOffsetLeft = selectedElementOffsetLeft + selectedElementWidth;
124 |
125 | // True inter-element dimensions.
126 | trueInterElementRightLineWidth = trueDiffSelectedRightHoveredRight;
127 | } else if (diffSelectedLeftHoveredRight < 0
128 | && diffSelectedLeftHoveredLeft < 0
129 | && diffSelectedRightHoveredLeft < 0
130 | && diffSelectedRightHoveredRight < 0) {
131 | // Scaled measurement calculations.
132 | rightLineLength = Math.abs(diffSelectedRightHoveredRight);
133 | rightLineOffsetLeft = hoveredElementOffsetLeft + hoveredElementWidth;
134 |
135 | // True inter-element dimensions.
136 | trueInterElementRightLineWidth = Math.abs(trueDiffSelectedRightHoveredRight);
137 | }
138 |
139 | // Top inter-element dimension line calculations.
140 | if (diffSelectedTopHoveredBottom > 0) {
141 | // Scaled measurement calculations.
142 | topLineLength = diffSelectedTopHoveredBottom;
143 | topLineOffsetTop = hoveredElementOffsetTop + hoveredElementHeight;
144 |
145 | // True inter-element dimensions.
146 | trueInterElementTopLineWidth = trueDiffSelectedTopHoveredBottom;
147 | } else if (diffSelectedTopHoveredTop > 0) {
148 | // Scaled measurement calculations.
149 | topLineLength = diffSelectedTopHoveredTop;
150 | topLineOffsetTop = hoveredElementOffsetTop;
151 |
152 | // True inter-element dimensions.
153 | trueInterElementTopLineWidth = trueDiffSelectedTopHoveredTop;
154 | } else if (diffSelectedTopHoveredBottom < 0
155 | && diffSelectedTopHoveredTop < 0
156 | && diffSelectedBottomHoveredBottom < 0
157 | && diffSelectedBottomHoveredTop < 0) {
158 | // Scaled measurement calculations.
159 | topLineLength = Math.abs(diffSelectedTopHoveredTop);
160 | topLineOffsetTop = selectedElementOffsetTop;
161 |
162 | // True inter-element dimensions.
163 | trueInterElementTopLineWidth = Math.abs(trueDiffSelectedTopHoveredTop);
164 | }
165 |
166 | // Bottom inter-element dimension line calculations.
167 | if (diffSelectedBottomHoveredTop > 0) {
168 | // Scaled measurement calculations.
169 | bottomLineLength = diffSelectedBottomHoveredTop;
170 | bottomLineOffsetTop = selectedElementOffsetTop + selectedElementHeight;
171 |
172 | // True inter-element dimensions.
173 | trueInterElementBottomLineWidth = trueDiffSelectedBottomHoveredTop;
174 | } else if (diffSelectedBottomHoveredBottom > 0) {
175 | // Scaled measurement calculations.
176 | bottomLineLength = diffSelectedBottomHoveredBottom;
177 | bottomLineOffsetTop = selectedElementOffsetTop + selectedElementHeight;
178 |
179 | // True inter-element dimensions.
180 | trueInterElementBottomLineWidth = trueDiffSelectedBottomHoveredBottom;
181 | } else if (diffSelectedTopHoveredBottom < 0
182 | && diffSelectedTopHoveredTop < 0
183 | && diffSelectedBottomHoveredBottom < 0
184 | && diffSelectedBottomHoveredTop < 0) {
185 | // Scaled measurement calculations.
186 | bottomLineLength = Math.abs(diffSelectedBottomHoveredBottom);
187 | bottomLineOffsetTop = hoveredElementOffsetTop + hoveredElementHeight;
188 |
189 | // True inter-element dimensions.
190 | trueInterElementBottomLineWidth = Math.abs(trueDiffSelectedBottomHoveredBottom);
191 | }
192 |
193 | const isHoveredSelectedElement = selectedElementTarget === hoveredElementTarget;
194 |
195 | return (
196 | !isDisplayInlineElement
197 | && (
198 | <>
199 | {
200 | isHoveredSelectedElement
201 | ? (
202 | <>
203 |
209 |
215 | >
216 | )
217 | : (
218 | <>
219 | {
220 | leftLineLength
221 | && (
222 | <>
223 |
231 |
237 | >
238 | )
239 | }
240 | {
241 | rightLineLength
242 | && (
243 | <>
244 |
252 |
258 | >
259 | )
260 | }
261 | {
262 | topLineLength
263 | && (
264 | <>
265 |
273 |
279 | >
280 | )
281 | }
282 | {
283 | bottomLineLength
284 | && (
285 | <>
286 |
294 |
300 | >
301 | )
302 | }
303 | >
304 | )
305 | }
306 | >
307 | )
308 | );
309 | }
310 | }
311 |
312 | InterElementDimensionsModule.propTypes = {
313 | elementMarkerThickness: PropTypes.number.isRequired,
314 | hoveredElement: PropTypes.shape({
315 | height: PropTypes.number.isRequired,
316 | offsetLeft: PropTypes.number.isRequired,
317 | offsetTop: PropTypes.number.isRequired,
318 | target: PropTypes.shape({}),
319 | trueHeight: PropTypes.number,
320 | trueOffsetLeft: PropTypes.number,
321 | trueOffsetTop: PropTypes.number,
322 | trueWidth: PropTypes.number,
323 | width: PropTypes.number.isRequired
324 | }).isRequired,
325 | selectedElement: PropTypes.shape({
326 | height: PropTypes.number.isRequired,
327 | offsetLeft: PropTypes.number.isRequired,
328 | offsetTop: PropTypes.number.isRequired,
329 | target: PropTypes.shape({}),
330 | trueHeight: PropTypes.number,
331 | trueOffsetLeft: PropTypes.number,
332 | trueOffsetTop: PropTypes.number,
333 | trueWidth: PropTypes.number,
334 | width: PropTypes.number.isRequired
335 | }).isRequired
336 | };
337 |
338 | export default InterElementDimensionsModule;
339 |
--------------------------------------------------------------------------------