Commit 8ca0ba70 authored by Laurent Wouters's avatar Laurent Wouters
Browse files

Refactoring the extension

parent f0d23cff97a2
......@@ -209,6 +209,8 @@ chrome.runtime.onMessage.addListener(
detectDataOnLinks(request.payload)
);
onObservedTabUpdated(sender.tab.id);
} else if (request.requestType == "GetTabId") {
sendResponse(sender.tab.id);
} else if (request.requestType == "GetObservations") {
let tabId = request.payload;
if (tabId == null || tabId == undefined) {
......
......@@ -152,6 +152,25 @@ export function getResourceContent(
);
}
/**
* Gets the tab id of the caller
*/
export function getMyTabId(): Promise<number> {
return new Promise<number>(
(resolve: (data: number) => void, reject: (reason: any) => void) => {
chrome.runtime.sendMessage(
{
requestType: "GetTabId",
payload: null
},
(data: number) => {
resolve(data);
}
);
}
);
}
/**
* Gets the observations about a tab
* @param tabId The corresponding tab number
......
......@@ -39,7 +39,8 @@ import {
getResourceContent,
getViewRegistry,
getObservationsFor,
fetchObservableAt
fetchObservableAt,
getMyTabId
} from "../common/messages";
require("./view-defaults-impl");
......@@ -160,6 +161,14 @@ function main() {
.catch((reason: any) => {
currentApp.onError(reason.toString());
});
getMyTabId().then((tabId: number) => {
chrome.pageAction.setPopup({
tabId: tabId,
popup: chrome.extension.getURL("popup/index.html")
});
chrome.pageAction.show(tabId);
});
}
/**
......
......@@ -28,6 +28,6 @@
"background": {
"scripts": ["background/main.js"]
},
"web_accessible_resources": ["ldviews/*", "ldbrowser/*"],
"web_accessible_resources": ["ldviews/*", "ldbrowser/*", "popup/*"],
"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="bootstrap.min.css">
<link rel="stylesheet" href="main.css">
<title>
Linked Data Browser
</title>
</head>
<body>
<div id="root"></div>
<script type="text/javascript" src="bootstrap.min.js"></script>
<script type="text/javascript" src="main.js"></script></body>
</html>
\ No newline at end of file
#root {
width: 400pt;
margin: 10pt;
font-size: 12pt;
}
select,
option,
input {
font-size: 10pt;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 30px;
height: 17px;
}
/* Hide default HTML checkbox */
.switch input {
display: none;
}
/* The slider */
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: 0.4s;
transition: 0.4s;
}
.slider:before {
position: absolute;
content: "";
height: 13px;
width: 13px;
left: 2px;
bottom: 2px;
background-color: white;
-webkit-transition: 0.4s;
transition: 0.4s;
}
input:checked + .slider {
background-color: #2196f3;
}
input:focus + .slider {
box-shadow: 0 0 1px #2196f3;
}
input:checked + .slider:before {
-webkit-transform: translateX(13px);
-ms-transform: translateX(13px);
transform: translateX(13px);
}
/* Rounded sliders */
.slider.round {
border-radius: 17px;
}
.slider.round:before {
border-radius: 50%;
}
/*******************************************************************************
* Copyright 2003-2018 LOGILAB S.A. (Paris, FRANCE), all rights reserved.
* contact http://www.logilab.fr/ -- mailto:contact@logilab.fr
*
* This file is part of CubicWeb.
*
* CubicWeb is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 2.1 of the License, or (at your option)
* any later version.
*
* CubicWeb is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with CubicWeb. If not, see <http://www.gnu.org/licenses/>.
******************************************************************************/
import React = require("react");
import ReactDOM = require("react-dom");
import { definition } from "@logilab/libview";
import {
getViewRegistry,
reloadViewRegistry,
addViewSource,
removeViewSource
} from "../common/messages";
interface State {
pane: string;
registry: definition.ViewRegistry;
registryNewSourceName: string;
registryNewSourceURI: string;
showViews: boolean;
}
class Popup extends React.Component<{}, State> {
state: State;
constructor(props: any) {
super(props);
this.state = {
pane: "loading",
registry: null,
registryNewSourceName: "",
registryNewSourceURI: "",
showViews: true
};
this.onButtonReloadRegistry = this.onButtonReloadRegistry.bind(this);
this.onPaneSources = this.onPaneSources.bind(this);
this.onPaneViews = this.onPaneViews.bind(this);
this.onRegistryUpdateNewSourceName = this.onRegistryUpdateNewSourceName.bind(
this
);
this.onRegistryUpdateNewSourceUri = this.onRegistryUpdateNewSourceUri.bind(
this
);
this.onRegistryAddSource = this.onRegistryAddSource.bind(this);
this.onRegistryRemoveSource = this.onRegistryRemoveSource.bind(this);
this.renderLoading = this.renderLoading.bind(this);
this.renderSources = this.renderSources.bind(this);
this.renderViews = this.renderViews.bind(this);
let self = this;
getViewRegistry().then((registry: definition.ViewRegistry) => {
self.state.pane = "sources";
self.state.registry = registry;
self.setState(self.state);
});
}
onButtonReloadRegistry() {
let self = this;
reloadViewRegistry().then((registry: definition.ViewRegistry) => {
self.state.registry = registry;
self.setState(self.state);
});
}
onPaneSources() {
this.state.pane = "sources";
this.setState(this.state);
}
onPaneViews() {
this.state.pane = "views";
this.setState(this.state);
}
onRegistryUpdateNewSourceName(event: React.FormEvent<HTMLInputElement>) {
this.state.registryNewSourceName = event.currentTarget.value;
this.setState(this.state);
}
onRegistryUpdateNewSourceUri(event: React.FormEvent<HTMLInputElement>) {
this.state.registryNewSourceURI = event.currentTarget.value;
this.setState(this.state);
}
onRegistryAddSource() {
let newSource: definition.ViewRegistrySourceRemote = {
kind: definition.ViewRegistrySourceKind.remote,
name: this.state.registryNewSourceName,
uri: this.state.registryNewSourceURI
};
let self = this;
addViewSource(newSource).then((registry: definition.ViewRegistry) => {
self.state.registry = registry;
self.state.registryNewSourceName = "";
self.state.registryNewSourceURI = "";
self.setState(self.state);
});
}
onRegistryRemoveSource(index: number) {
let self = this;
removeViewSource(index).then((registry: definition.ViewRegistry) => {
self.state.registry = registry;
self.state.registryNewSourceName = "";
self.state.registryNewSourceURI = "";
self.setState(self.state);
});
}
render() {
if (this.state.pane == "loading") {
return this.renderLoading();
} else if (this.state.pane == "sources") {
return this.renderSources();
} else if (this.state.pane == "views") {
return this.renderViews();
}
}
renderLoading() {
return (
<div
style={{
width: "90%",
marginLeft: "5%",
marginTop: "5vh",
marginBottom: "5vh"
}}
>
<div className="alert alert-info" role="alert">
Loading
</div>
</div>
);
}
renderSources() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-2">
<a onClick={this.onPaneViews} title="Available views">
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
{"\u23FF"}
</span>
</a>
</div>
<div className="col-2">
<a onClick={this.onButtonReloadRegistry} title="Reload registry">
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
{"\u21bb"}
</span>
</a>
</div>
<div className="col-8">
<span style={{ fontSize: "17pt" }}>Registered sources</span>
</div>
</div>
<hr />
<div className="row">
<div className="col-4">New source name:</div>
<div className="col-8">
<input
className="form-control"
onChange={this.onRegistryUpdateNewSourceName}
value={this.state.registryNewSourceName}
/>
</div>
</div>
<div className="row">
<div className="col-4">New source URI:</div>
<div className="col-8">
<input
className="form-control"
onChange={this.onRegistryUpdateNewSourceUri}
value={this.state.registryNewSourceURI}
/>
</div>
</div>
<div className="row">
<div className="col-4" />
<div className="col-8">
<a className="btn btn-primary" onClick={this.onRegistryAddSource}>
OK
</a>
</div>
</div>
<hr />
{this.state.registry.sources.map(
(source: definition.ViewRegistrySource, index: number) => {
return (
<div className="row" key={"source-" + index}>
<div className="col-2">
{source.kind != definition.ViewRegistrySourceKind.inline ? (
<a title="Remove this source">
<span
onClick={() => this.onRegistryRemoveSource(index)}
style={{ cursor: "pointer" }}
className="text-danger"
>
{"\u2716"}
</span>
</a>
) : (
<span />
)}
</div>
<div className="col-10">
<span>{source.name}</span>
{source.kind == definition.ViewRegistrySourceKind.remote ? (
<span>
{" : "}
<a
href={
(source as definition.ViewRegistrySourceRemote).uri
}
>
{(source as definition.ViewRegistrySourceRemote).uri}
</a>
</span>
) : (
<span />
)}
</div>
</div>
);
}
)}
</div>
);
}
renderViews() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-2">
<a onClick={this.onPaneSources} title="Back">
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
{"\u2190"}
</span>
</a>
</div>
<div className="col-2">
<a onClick={this.onButtonReloadRegistry} title="Reload registry">
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
{"\u21bb"}
</span>
</a>
</div>
<div className="col-8">
<span style={{ fontSize: "17pt" }}>Available views</span>
</div>
</div>
<hr />
{Object.keys(this.state.registry.descriptors)
.map((key: string) => this.state.registry.descriptors[key])
.sort(
(
a: definition.ViewDescriptor,
b: definition.ViewDescriptor
): number => a.identifier.localeCompare(b.identifier)
)
.map((descriptor: definition.ViewDescriptor, index: number) => {
return (
<div className="row">
<div className="col-6">
<span title={descriptor.description}>
{descriptor.identifier}
</span>
</div>
<div className="col-6">
<span title={descriptor.description}>{descriptor.name}</span>
</div>
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<Popup />, document.getElementById("root"));
......@@ -122,5 +122,47 @@ module.exports = [
{}
)
]
},
{
entry: "./src/popup/main.tsx",
mode: isProd ? "production" : "development",
output: {
path: path.join(__dirname, "build/"),
publicPath: path.join(__dirname, "build/popup/"),
filename: "popup/main.js"
},
externals: {
chrome: "chrome"
},
devtool: "source-map",
resolve: {
extensions: [".ts", ".tsx", ".js", ".json"]
},
module: {
rules: [
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" },
{ enforce: "pre", test: /\.js$/, loader: "source-map-loader" }
]
},
plugins: [
new CopyWebpackPlugin(
[
{
from: "src/popup/index.html",
to: "popup/index.html"
},
{ from: "src/popup/main.css", to: "popup/main.css" },
{
from: "node_modules/bootstrap/dist/css/bootstrap.min.css",
to: "popup/bootstrap.min.css"
},
{
from: "node_modules/bootstrap/dist/js/bootstrap.min.js",
to: "popup/bootstrap.min.js"
}
],
{}
)
]
}
];
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment