Commit ef955c2d authored by Laurent Wouters's avatar Laurent Wouters
Browse files

Fixed issue #2 - forced selection of a data source

parent dba67c4a8bcf
......@@ -66,7 +66,7 @@ function updateTab(tabId: number): void {
function updateIcon(tabId: number): void {
let data = getTabData(tabId);
chrome.browserAction.setIcon({
path: data.isActive
path: data.command.isActive
? {
48: "icons/app.png"
}
......@@ -80,7 +80,7 @@ function updateIcon(tabId: number): void {
tabId: tabId
});
chrome.browserAction.setBadgeText({
text: data.mainSource.name,
text: data.mainSource == NO_DATA ? "" : "\u2605",
tabId: tabId
});
}
......@@ -91,7 +91,7 @@ function updateIcon(tabId: number): void {
*/
function toggleActive(tab: chrome.tabs.Tab): void {
let data = getTabData(tab.id);
data.isActive = !data.isActive;
data.command.isActive = !data.command.isActive;
updateTab(tab.id);
}
......@@ -270,7 +270,7 @@ function doPreempt(
setHeader(headers, "Content-Type", "text/plain");
let data = getTabData(details.tabId);
data.sources.push(new DataSourcePage(details.url, mime.mime));
data.isActive = true; // activate by default
data.command.isActive = true; // activate by default
data.selectMain();
return { responseHeaders: headers };
}
......@@ -359,13 +359,20 @@ chrome.webRequest.onHeadersReceived.addListener(
F.registerNavigations(onBeforeNavigate, onCompleted);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
let data = getTabData(sender.tab.id);
if (request.reqType == "links") {
let data = getTabData(sender.tab.id);
if (data.primaryTopic == null) {
data.primaryTopic = detectTopicOnlinks(request.links);
}
data.sources = data.sources.concat(detectDataOnLinks(request.links));
data.selectMain();
updateTab(sender.tab.id);
} else if (request.reqType == "GetTabData") {
let data = getTabData(request.tabId);
sendResponse(data);
} else if (request.reqType == "UpdateTabData") {
let data = getTabData(request.tabId);
data.updateWith(request.tabData);
updateTab(request.tabId);
}
});
......@@ -168,7 +168,7 @@ export interface DataSource {
export class DataSourceNone implements DataSource {
constructor() {
this.sourceType = "DataSourceNone";
this.name = "";
this.name = "No data";
this.url = "";
this.contentType = "";
this.priority = Number.MAX_SAFE_INTEGER;
......@@ -185,8 +185,11 @@ export class DataSourceNone implements DataSource {
*/
export class DataSourcePage implements DataSource {
constructor(url: string, contentType: string) {
let mime = MIME[contentType];
this.sourceType = "DataSourcePage";
this.name = "*";
this.name =
"Page's content" +
(mime != undefined && mime != null ? " (" + mime.name + ")" : "");
this.url = url;
this.contentType = contentType;
this.priority = 1;
......@@ -232,7 +235,12 @@ export class DataSourceLinked implements DataSource {
if (mime == null || mime == undefined) throw "Unrecognized link type";
this.tags = link.tags;
this.sourceType = "DataSourceLinked";
this.name = mime.name;
this.name =
"Linked content" +
(mime != undefined && mime != null ? " (" + mime.name + ")" : "") +
" (" +
link.url +
")";
this.url = link.url;
this.contentType = mime.mime;
this.priority = mime.priority;
......@@ -302,23 +310,60 @@ function compareSources(x: DataSource, y: DataSource): number {
*/
export const NO_DATA = new DataSourceNone();
/**
* The user command for a tab
*/
export class TabCommand {
constructor() {
this.isActive = false;
this.isAutomatic = true;
this.selectedSource = null;
this.selectedView = null;
}
/**
* Whether the user activated the extension for this tab
*/
isActive: boolean;
/**
* Whether the user is using automatic selection of data sources and views
*/
isAutomatic: boolean;
/**
* The identifier of the selected data source (if not automatic)
*/
selectedSource: string;
/**
* The identifier of the selected view (if not automatic)
*/
selectedView: string;
/**
* Updates this command with the specified one
* @param command The updated command
*/
updateWith(command: TabCommand) {
this.isActive = command.isActive;
this.isAutomatic = command.isAutomatic;
this.selectedSource = command.selectedSource;
this.selectedView = command.selectedView;
}
}
/**
* Data about a tab
*/
export class TabData {
constructor() {
this.isActive = false;
this.command = new TabCommand();
this.mainSource = NO_DATA;
this.sources = [];
//this.clear = this.clear.bind(this);
this.primaryTopic = null;
//this.selectMain = this.selectMain.bind(this);
}
/**
* Whether the user activated the extension for this tab
* The user command for this tab
*/
isActive: boolean;
command: TabCommand;
/**
* The data associated to this tab
*/
......@@ -332,10 +377,22 @@ export class TabData {
*/
primaryTopic: string;
/**
* Updates this data with the specified one
* @param data The updated data
*/
updateWith(data: TabData) {
this.command.updateWith(data.command);
this.selectMain();
}
/**
* Clears this data
*/
clear(): void {
this.command.isAutomatic = true;
this.command.selectedSource = null;
this.command.selectedView = null;
this.mainSource = NO_DATA;
this.sources = [];
this.primaryTopic = null;
......@@ -345,6 +402,7 @@ export class TabData {
* Selects the main resource among the provided one
*/
selectMain(): DataSource {
if (this.command.isAutomatic || this.command.selectedSource == null) {
if (this.sources.length == 0) {
this.mainSource = NO_DATA;
} else {
......@@ -355,6 +413,17 @@ export class TabData {
.sort(compareSources);
this.mainSource = this.sources.length == 0 ? NO_DATA : this.sources[0];
}
} else {
this.mainSource = this.sources.reduce(
(acc: DataSource, current: DataSource) => {
if (acc != null) return acc;
if (current.url == this.command.selectedSource) return current;
return null;
},
null
);
if (this.mainSource == null) this.mainSource = NO_DATA;
}
return this.mainSource;
}
}
......
......@@ -27,7 +27,7 @@ let _rdf = require("rdflib");
export const $rdf: RDF = _rdf;
export function loadRdfStore(tabData: TabData): Promise<any> {
if (!tabData.isActive) {
if (!tabData.command.isActive) {
return new Promise((resolve, reject) => {
reject("no data");
});
......
......@@ -52,7 +52,7 @@ chrome.runtime.onMessage.addListener(function(
sender,
sendResponse
) {
let activate = request.isActive && request.mainSource != NO_DATA;
let activate = request.command.isActive && request.mainSource != NO_DATA;
if (activate) onUpdateActivate(request);
else onUpdateDeactivate();
});
......
......@@ -14,8 +14,10 @@
"*://*/*"
],
"browser_action": {
"browser_style": true,
"default_icon": "icons/app_disabled.png",
"default_title": "Linked Data Browser"
"default_title": "Linked Data Browser",
"default_popup": "popup/index.html"
},
"content_scripts": [
{
......
/*******************************************************************************
* 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 * as React from "react";
import { TabData, DataSource, NO_DATA } from "../common/data";
import "chrome";
import { stat } from "fs";
export class Menu extends React.Component {
state: TabData;
tabId: number;
constructor(props: any) {
super(props);
this.state = new TabData();
this.onClickActivate = this.onClickActivate.bind(this);
this.onClickAutomatic = this.onClickAutomatic.bind(this);
this.onSelectSource = this.onSelectSource.bind(this);
this.onSelectedView = this.onSelectedView.bind(this);
let self = this;
chrome.tabs.query(
{ active: true, currentWindow: true },
(tabs: chrome.tabs.Tab[]) => {
self.tabId = tabs[0].id;
chrome.runtime.sendMessage(
{ reqType: "GetTabData", tabId: tabs[0].id },
(response: TabData) => {
self.setState(response);
}
);
}
);
}
onClickActivate(event: React.FormEvent<HTMLInputElement>) {
this.state.command.isActive = event.currentTarget.checked;
this.setState(this.state);
chrome.runtime.sendMessage({
reqType: "UpdateTabData",
tabId: this.tabId,
tabData: this.state
});
}
onClickAutomatic(event: React.FormEvent<HTMLInputElement>) {
this.state.command.isAutomatic = event.currentTarget.checked;
if (this.state.command.isAutomatic) {
this.state.command.selectedSource = null;
this.state.command.selectedView == null;
}
this.setState(this.state);
chrome.runtime.sendMessage({
reqType: "UpdateTabData",
tabId: this.tabId,
tabData: this.state
});
}
onSelectSource(event: React.FormEvent<HTMLSelectElement>) {
this.state.command.selectedSource = event.currentTarget.value;
if (this.state.command.isAutomatic) {
this.state.command.selectedSource = null;
this.state.command.selectedView == null;
}
this.setState(this.state);
chrome.runtime.sendMessage({
reqType: "UpdateTabData",
tabId: this.tabId,
tabData: this.state
});
}
onSelectedView(event: React.FormEvent<HTMLSelectElement>) {}
render() {
return (
<div className="container-fluid">
<div className="row">
<div className="col-sm-4">
<label>Active</label>
</div>
<div className="col-sm-8">
<label className="switch">
<input
type="checkbox"
id="fieldActive"
checked={this.state.command.isActive}
onChange={this.onClickActivate}
/>
<span className="slider round" />
</label>
</div>
</div>
<div className="row">
<div className="col-sm-4">
<label>Auto config</label>
</div>
<div className="col-sm-8">
<label className="switch">
<input
type="checkbox"
id="fiedAuto"
checked={this.state.command.isAutomatic}
onChange={this.onClickAutomatic}
/>
<span className="slider round" />
</label>
</div>
</div>
<div className="row">
<div className="col-sm-4">
<label>Data source</label>
</div>
<div className="col-sm-8">
<select
className="form-control"
id="fieldDataSource"
disabled={this.state.command.isAutomatic}
style={{ marginTop: "10pt;" }}
onChange={this.onSelectSource}
value={this.state.command.selectedSource}
>
{this.state.sources
.filter((source: DataSource) => source != NO_DATA)
.map((source: DataSource, index: number) => (
<option key={"source" + index} value={source.url}>
{source.name}
</option>
))}
</select>
</div>
</div>
<div className="row">
<div className="col-sm-4">
<label>View</label>
</div>
<div className="col-sm-8">
<select
className="form-control"
id="fiedView"
disabled={this.state.command.isAutomatic}
style={{ marginTop: "10pt;" }}
onChange={this.onSelectedView}
value={this.state.command.selectedView}
/>
</div>
</div>
</div>
);
}
}
<!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: 500pt;
margin: 10pt;
}
/* The switch - the box around the slider */
.switch {
position: relative;
display: inline-block;
width: 60px;
height: 34px;
}
/* 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: 26px;
width: 26px;
left: 4px;
bottom: 4px;
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(26px);
-ms-transform: translateX(26px);
transform: translateX(26px);
}
/* Rounded sliders */
.slider.round {
border-radius: 34px;
}
.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 { Menu } from "./Menu";
ReactDOM.render(<Menu />, document.getElementById("root"));
const path = require("path");
const isProd = process.env.NODE_ENV === "production";
const CopyWebpackPlugin = require("copy-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = [
{
......@@ -39,7 +40,7 @@ module.exports = [
mode: isProd ? "production" : "development",
output: {
path: path.join(__dirname, "build/"),
publicPath: path.join(__dirname, "build/background/"),
publicPath: path.join(__dirname, "build/content/"),
filename: "content/main.js"
},
externals: {
......@@ -71,5 +72,44 @@ 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: "ts-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"
}
],
{}
)
]
}
];
Markdown is supported
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