WIP: Shadow DOM

This commit is contained in:
Manuel Bouza
2019-02-19 15:32:53 +01:00
parent dd4fa996e8
commit a9007b825e
21 changed files with 251 additions and 220 deletions

View File

@@ -5,6 +5,7 @@
"main": "bundle.js",
"scripts": {
"start": "node_modules/.bin/webpack --watch",
"build": "node_modules/.bin/webpack",
"test": "node_modules/.bin/jest",
"test:watch": "node_modules/.bin/jest --watch",
"release": "copyfiles main.css main.min.js background.min.js manifest.json popup.html options.html node_modules/jquery/dist/jquery.min.js node_modules/select2/select2.js src/images/* release"
@@ -20,6 +21,7 @@
"react": "^16.8.0",
"react-dom": "^16.8.0",
"react-select": "^2.3.0",
"react-shadow": "^16.3.2",
"route-parser": "^0.0.5"
},
"devDependencies": {
@@ -42,6 +44,7 @@
"file-loader": "^3.0.1",
"html-webpack-plugin": "^3.2.0",
"jest": "^24.1.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"prettier": "^1.16.4",
"sass-loader": "^7.1.0",

View File

@@ -1,93 +1,91 @@
#moco-bx-container {
input {
border-radius: 0;
input {
border-radius: 0;
}
.form-group {
width: 100%;
margin: 1rem 0;
label {
display: block;
font-weight: bold;
margin-bottom: 0.25rem;
}
.form-group {
input, textarea {
padding: 0.25rem 0.5rem;
background-color: white;
border-color: #cccccc;
width: 100%;
margin: 1rem 0;
}
label {
display: block;
font-weight: bold;
margin-bottom: 0.25rem;
}
text-muted {
color: #eee;
}
&.has-error {
input, textarea {
padding: 0.25rem 0.5rem;
background-color: white;
border-color: #cccccc;
width: 100%;
border-color: #FB3A2F;
}
}
text-muted {
color: #eee;
}
.form-error {
color: #FB3A2F;
}
&.has-error {
input, textarea {
border-color: #FB3A2F;
}
}
.input-group {
position: relative;
display: table;
border-collapse: separate;
.form-error {
color: #FB3A2F;
}
.input-group {
input {
position: relative;
display: table;
border-collapse: separate;
input {
position: relative;
display: table-cell;
}
.input-group-addon {
padding: 0.25rem 0.5rem;
font-weight: normal;
color: #555555;
text-align: center;
background-color: #eeeeee;
border: 1px solid #cccccc;
border-left: none;
display: table-cell;
width: 1%;
}
}
}
input[name="hours"] {
width: 33%;
}
textarea[name="description"] {
resize: none;
width: 100%;
}
button {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-weight: normal;
text-align: center;
white-space: nowrap;
color: white;
background-image: none;
background-color: #7dc332;
border-color: #7dc332;
cursor: pointer;
&:hover:not(:disabled) {
background-color: #639a28;
border-color: #639a28;
display: table-cell;
}
&:disabled {
opacity: 0.65;
cursor: default;
.input-group-addon {
padding: 0.25rem 0.5rem;
font-weight: normal;
color: #555555;
text-align: center;
background-color: #eeeeee;
border: 1px solid #cccccc;
border-left: none;
display: table-cell;
width: 1%;
}
}
}
input[name="hours"] {
width: 33%;
}
textarea[name="description"] {
resize: none;
width: 100%;
}
button {
display: inline-block;
padding: 6px 12px;
margin-bottom: 0;
font-weight: normal;
text-align: center;
white-space: nowrap;
color: white;
background-image: none;
background-color: #7dc332;
border-color: #7dc332;
cursor: pointer;
&:hover:not(:disabled) {
background-color: #639a28;
border-color: #639a28;
}
&:disabled {
opacity: 0.65;
cursor: default;
}
}

45
src/css/_reset.scss Normal file
View File

@@ -0,0 +1,45 @@
#moco-bx-bubble, #moco-bx-container {
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
}

View File

@@ -1,11 +1,11 @@
#moco-bx-bubble, #moco-bx-container {
@keyframes spinner {
@keyframes moco-bx-spinner {
to {
transform: rotate(360deg);
}
}
.spinner {
.moco-bx-spinner {
display: inline-block;
width: 2rem;
height: 2rem;
@@ -13,6 +13,6 @@
border: 2px solid #999;
border-right-color: transparent;
border-radius: 50%;
animation: spinner .75s linear infinite;
animation: moco-bx-spinner .75s linear infinite;
}
}

31
src/css/content.scss Normal file
View File

@@ -0,0 +1,31 @@
@import "mixins";
@import "form";
@import "spinner";
.moco-bx-bubble {
img {
width: 30px;
height: 30px;
}
}
.moco-bx-modal {
position: fixed; /* Stay in place */
z-index: 2000; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
.moco-bx-modal-content {
background-color: white;
width: 600px;
padding: 40px;
margin: 0 auto;
}
}

View File

@@ -1,48 +0,0 @@
@import "mixins";
@import "form";
@import "spinner";
#moco-bx-bubble {
position: fixed;
bottom: 40px;
left: 50%;
margin-left: -30px;
z-index: 1000;
height: 60px;
width: 60px;
background-color: white;
border-radius: 50%;
box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05),
2px 2px 15px 4px rgba(0, 0, 0, 0.05);
padding: 5px;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
cursor: pointer;
}
#moco-bx-container {
.moco-bx-modal {
position: fixed; /* Stay in place */
z-index: 2000; /* Sit on top */
padding-top: 100px; /* Location of the box */
left: 0;
top: 0;
width: 100%; /* Full width */
height: 100%; /* Full height */
overflow: auto; /* Enable scroll if needed */
background-color: rgb(0, 0, 0); /* Fallback color */
background-color: rgba(0, 0, 0, 0.4); /* Black w/ opacity */
}
.moco-bx-modal-content {
background-color: white;
width: 600px;
padding: 40px;
margin: 0 auto;
}
}

0
src/css/options.scss Normal file
View File

22
src/css/styles.css Normal file
View File

@@ -0,0 +1,22 @@
#moco-bx-root {
position: fixed;
bottom: 40px;
left: 50%;
margin-left: -30px;
z-index: 1000;
height: 60px;
width: 60px;
background-color: white;
border-radius: 50%;
box-shadow: -1px -1px 15px 4px rgba(0, 0, 0, 0.05),
2px 2px 15px 4px rgba(0, 0, 0, 0.05);
padding: 5px;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
cursor: pointer;
}

View File

@@ -1,7 +1,7 @@
import React, { Component } from "react"
import PropTypes from "prop-types"
import ApiClient from "api/Client"
import Modal, { Content } from "components/Modal"
import Modal from "components/Modal"
import InvalidConfigurationError from "components/InvalidConfigurationError"
import Form from "components/Form"
import Spinner from "components/Spinner"
@@ -160,6 +160,7 @@ class Bubble extends Component {
// EVENT HANDLERS -----------------------------------------------------------
handleKeyDown = event => {
event.stopPropagation()
if (event.keyCode === 27) {
this.close()
}
@@ -223,19 +224,18 @@ class Bubble extends Component {
}
return (
<>
<div className="moco-bx-bubble">
<img
onClick={this.open}
src={chrome.extension.getURL(logoUrl)}
width="50%"
/>
{this.bookedHours > 0 && <span className="booked-hours"><small>{this.bookedHours}h</small></span>}
{this.isOpen && (
<Modal>
<Content>{this.renderContent()}</Content>
{this.renderContent()}
</Modal>
)}
</>
</div>
)
}
}

View File

@@ -1,5 +1,4 @@
import React, { Component } from "react"
import { createPortal } from "react-dom"
import PropTypes from "prop-types"
class Modal extends Component {
@@ -7,35 +6,17 @@ class Modal extends Component {
children: PropTypes.node.isRequired
}
constructor(props) {
super(props)
this.el = document.createElement("div")
this.el.setAttribute("class", "moco-bx-modal")
}
componentDidMount() {
const modalRoot = document.getElementById("moco-bx-container")
modalRoot.appendChild(this.el)
}
componentWillUnmount() {
const modalRoot = document.getElementById("moco-bx-container")
modalRoot.removeChild(this.el)
}
// RENDER -------------------------------------------------------------------
render() {
return createPortal(this.props.children, this.el)
return (
<div className="moco-bx-modal">
<div className="moco-bx-modal-content">
{this.props.children}
</div>
</div>
)
}
}
export function Content({ children }) {
return <div className="moco-bx-modal-content">{children}</div>
}
Content.propTypes = {
children: PropTypes.node
}
export default Modal

View File

@@ -12,7 +12,6 @@ import {
flatMap,
pathEq
} from "lodash/fp"
import { trace } from "utils"
const customTheme = theme => ({
...theme,

View File

@@ -1,9 +1,7 @@
import React from 'react'
const Spinner = () => (
<div className="spinner" role="status">
<span className="sr-only">Loading...</span>
</div>
<div className="moco-bx-spinner" role="status" />
)
export default Spinner

View File

@@ -1,11 +1,11 @@
import { createElement } from "react"
import React from "react"
import ReactDOM from "react-dom"
import ShadowDOM from "react-shadow"
import Bubble from "./components/Bubble"
import services from "remoteServices"
import { parseServices, createMatcher, createEnhancer } from "utils/urlMatcher"
import { createMatcher, createEnhancer } from "utils/urlMatcher"
import remoteServices from "./remoteServices"
import { pipe } from 'lodash/fp'
import "../css/main.scss"
import "../css/content.scss"
const matcher = createMatcher(remoteServices)
const serviceEnhancer = createEnhancer(window.document)
@@ -32,35 +32,27 @@ const mountBubble = (settings) => {
return
}
if (!document.getElementById("moco-bx-container")) {
const domContainer = document.createElement("div")
domContainer.setAttribute("id", "moco-bx-container")
document.body.appendChild(domContainer)
}
if (!document.getElementById("moco-bx-bubble")) {
const domBubble = document.createElement("div")
domBubble.setAttribute("id", "moco-bx-bubble")
document.body.appendChild(domBubble)
if (!document.getElementById("moco-bx-root")) {
const domRoot = document.createElement("div")
domRoot.setAttribute("id", "moco-bx-root")
document.body.appendChild(domRoot)
}
ReactDOM.render(
createElement(Bubble, { service, settings }),
document.getElementById("moco-bx-bubble")
<ShadowDOM include={[chrome.extension.getURL('content.css')]}>
<div>
<Bubble service={service} settings={settings} />
</div>
</ShadowDOM>,
document.getElementById("moco-bx-root")
)
}
const unmountBubble = () => {
const domBubble = document.getElementById("moco-bx-bubble")
const domContainer = document.getElementById("moco-bx-container")
const domRoot = document.getElementById("moco-bx-root")
if (domBubble) {
ReactDOM.unmountComponentAtNode(domBubble)
domBubble.remove()
}
if (domContainer) {
ReactDOM.unmountComponentAtNode(domContainer)
domContainer.remove()
if (domRoot) {
ReactDOM.unmountComponentAtNode(domRoot)
domRoot.remove()
}
}

View File

@@ -1,7 +1,7 @@
import { createElement } from "react"
import React from "react"
import ReactDOM from "react-dom"
import Setup from "./components/Setup"
import "../css/main.scss"
import "../css/options.scss"
const domContainer = document.querySelector("#moco-bx-container")
ReactDOM.render(createElement(Setup), domContainer)
const domContainer = document.querySelector("#moco-bx-root")
ReactDOM.render(<Setup />, domContainer)

View File

@@ -1,6 +0,0 @@
import { createElement } from 'react'
import ReactDOM from 'react-dom'
import Form from './components/Form'
const domContainer = document.querySelector('#moco-bx-container')
ReactDOM.render(createElement(Form, {inline: false}), domContainer)

View File

@@ -25,6 +25,12 @@ export default {
description: (document, service, { org, repo, id }) =>
`${org}/${repo}/${id} - ${document
.querySelector(".gh-header-title")
.textContent.trim()}`
.textContent.trim()}`,
},
"trello": {
name: "trello",
urlPattern: "https://trello.com/c/:id/:title",
description: (document, service, { title }) => title.split('-').slice(1).join(' ')
}
}

View File

@@ -28,7 +28,8 @@
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"]
"js": ["content.js"],
"css": ["styles.css"]
}
],
"browser_action": {
@@ -36,7 +37,7 @@
"default_title": "MOCO Time Tracking",
"default_popup": "popup.html"
},
"web_accessible_resources": ["src/images/*"],
"web_accessible_resources": ["src/images/*", "content.css"],
"commands": {
"_execute_browser_action": {
"suggested_key": {

View File

@@ -4,6 +4,6 @@
<meta charset="utf-8" />
</head>
<body>
<div id="moco-bx-container"></div>
<div id="moco-bx-root"></div>
</body>
</html>

View File

@@ -1,9 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="moco-bx-container"></div>
</body>
</html>

View File

@@ -1,4 +1,5 @@
const path = require("path")
const MiniCssExtractPlugin = require("mini-css-extract-plugin")
const CleanWebpackPlugin = require("clean-webpack-plugin")
const HtmlWebpackPlugin = require("html-webpack-plugin")
const CopyWebpackPlugin = require("copy-webpack-plugin")
@@ -7,8 +8,7 @@ module.exports = {
entry: {
background: "./src/js/background.js",
content: "./src/js/content.js",
options: "./src/js/options.js",
popup: "./src/js/popup.js"
options: "./src/js/options.js"
},
output: {
path: path.join(__dirname, "build"),
@@ -19,7 +19,9 @@ module.exports = {
{
test: /\.scss$/,
use: [
"style-loader",
{
loader: MiniCssExtractPlugin.loader
},
"css-loader",
{
loader: "sass-loader",
@@ -48,6 +50,10 @@ module.exports = {
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].css",
chunkFilename: '[id].css'
}),
new CleanWebpackPlugin(["build"]),
new CopyWebpackPlugin([
{
@@ -62,13 +68,11 @@ module.exports = {
})
)
}
},
{
from: "src/css/styles.css",
}
]),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "popup.html"),
filename: "popup.html",
chunks: ["popup"]
}),
new HtmlWebpackPlugin({
template: path.join(__dirname, "src", "options.html"),
filename: "options.html",

View File

@@ -4563,6 +4563,15 @@ mimic-fn@^1.0.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==
mini-css-extract-plugin@^0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz#ac0059b02b9692515a637115b0cc9fed3a35c7b0"
integrity sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==
dependencies:
loader-utils "^1.1.0"
schema-utils "^1.0.0"
webpack-sources "^1.1.0"
minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7"
@@ -5642,6 +5651,11 @@ react-select@^2.3.0:
react-input-autosize "^2.2.1"
react-transition-group "^2.2.1"
react-shadow@^16.3.2:
version "16.3.2"
resolved "https://registry.yarnpkg.com/react-shadow/-/react-shadow-16.3.2.tgz#a431a7101b0e222dbc669c4070ab395f42dbb675"
integrity sha512-NxFoBvEg2WD3V25jgiWgIltpDfs2XBCbDyi63jEM6Gch8g8r2cqJxSWy2GfG666EfOpRR5Zsrhu0Qn5yze1PrA==
react-transition-group@^2.2.1:
version "2.5.3"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.5.3.tgz#26de363cab19e5c88ae5dbae105c706cf953bb92"