Commit 230f763f authored by Laurent Wouters's avatar Laurent Wouters
Browse files

[feature] Implementing options page for the extension

parent 13041f9d9c47
......@@ -28,6 +28,10 @@
"background": {
"scripts": ["background/main.js"]
},
"options_ui": {
"page": "options/index.html",
"browser_style": true
},
"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 = "tab";
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
className="btn btn-outline-light"
onClick={this.onPaneViews}
title="Available views"
>
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
<img width="24px" src="../icons/view.svg" />
</span>
</a>
</div>
<div className="col-2">
<a
className="btn btn-outline-light"
onClick={this.onButtonReloadRegistry}
title="Reload registry"
>
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
<img width="24px" src="../icons/refresh.svg" />
</span>
</a>
</div>
<div className="col-2" />
<div className="col-6">
<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
className="btn btn-outline-light"
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
className="btn btn-outline-light"
onClick={this.onPaneSources}
title="Back"
>
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
<img width="24px" src="../icons/back.svg" />
</span>
</a>
</div>
<div className="col-2">
<a
className="btn btn-outline-light"
onClick={this.onButtonReloadRegistry}
title="Reload registry"
>
<span style={{ fontSize: "20pt", cursor: "pointer" }}>
<img width="24px" src="../icons/refresh.svg" />
</span>
</a>
</div>
<div className="col-2" />
<div className="col-6">
<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 key={"descriptor-" + index} 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"));
......@@ -170,5 +170,47 @@ module.exports = [
{}
)
]
},
{
entry: "./src/options/main.tsx",
mode: isProd ? "production" : "development",
output: {
path: path.join(__dirname, "build/"),
publicPath: path.join(__dirname, "build/options/"),
filename: "options/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/options/index.html",
to: "options/index.html"
},
{ from: "src/options/main.css", to: "options/main.css" },
{
from: "node_modules/bootstrap/dist/css/bootstrap.min.css",
to: "options/bootstrap.min.css"
},
{
from: "node_modules/bootstrap/dist/js/bootstrap.min.js",
to: "options/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