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 { ...@@ -66,7 +66,7 @@ function updateTab(tabId: number): void {
function updateIcon(tabId: number): void { function updateIcon(tabId: number): void {
let data = getTabData(tabId); let data = getTabData(tabId);
chrome.browserAction.setIcon({ chrome.browserAction.setIcon({
path: data.isActive path: data.command.isActive
? { ? {
48: "icons/app.png" 48: "icons/app.png"
} }
...@@ -80,7 +80,7 @@ function updateIcon(tabId: number): void { ...@@ -80,7 +80,7 @@ function updateIcon(tabId: number): void {
tabId: tabId tabId: tabId
}); });
chrome.browserAction.setBadgeText({ chrome.browserAction.setBadgeText({
text: data.mainSource.name, text: data.mainSource == NO_DATA ? "" : "\u2605",
tabId: tabId tabId: tabId
}); });
} }
...@@ -91,7 +91,7 @@ function updateIcon(tabId: number): void { ...@@ -91,7 +91,7 @@ function updateIcon(tabId: number): void {
*/ */
function toggleActive(tab: chrome.tabs.Tab): void { function toggleActive(tab: chrome.tabs.Tab): void {
let data = getTabData(tab.id); let data = getTabData(tab.id);
data.isActive = !data.isActive; data.command.isActive = !data.command.isActive;
updateTab(tab.id); updateTab(tab.id);
} }
...@@ -270,7 +270,7 @@ function doPreempt( ...@@ -270,7 +270,7 @@ function doPreempt(
setHeader(headers, "Content-Type", "text/plain"); setHeader(headers, "Content-Type", "text/plain");
let data = getTabData(details.tabId); let data = getTabData(details.tabId);
data.sources.push(new DataSourcePage(details.url, mime.mime)); data.sources.push(new DataSourcePage(details.url, mime.mime));
data.isActive = true; // activate by default data.command.isActive = true; // activate by default
data.selectMain(); data.selectMain();
return { responseHeaders: headers }; return { responseHeaders: headers };
} }
...@@ -359,13 +359,20 @@ chrome.webRequest.onHeadersReceived.addListener( ...@@ -359,13 +359,20 @@ chrome.webRequest.onHeadersReceived.addListener(
F.registerNavigations(onBeforeNavigate, onCompleted); F.registerNavigations(onBeforeNavigate, onCompleted);
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
let data = getTabData(sender.tab.id);
if (request.reqType == "links") { if (request.reqType == "links") {
let data = getTabData(sender.tab.id);
if (data.primaryTopic == null) { if (data.primaryTopic == null) {
data.primaryTopic = detectTopicOnlinks(request.links); data.primaryTopic = detectTopicOnlinks(request.links);
} }
data.sources = data.sources.concat(detectDataOnLinks(request.links)); data.sources = data.sources.concat(detectDataOnLinks(request.links));
data.selectMain(); data.selectMain();
updateTab(sender.tab.id); 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 { ...@@ -168,7 +168,7 @@ export interface DataSource {
export class DataSourceNone implements DataSource { export class DataSourceNone implements DataSource {
constructor() { constructor() {
this.sourceType = "DataSourceNone"; this.sourceType = "DataSourceNone";
this.name = ""; this.name = "No data";
this.url = ""; this.url = "";
this.contentType = ""; this.contentType = "";
this.priority = Number.MAX_SAFE_INTEGER; this.priority = Number.MAX_SAFE_INTEGER;
...@@ -185,8 +185,11 @@ export class DataSourceNone implements DataSource { ...@@ -185,8 +185,11 @@ export class DataSourceNone implements DataSource {
*/ */
export class DataSourcePage implements DataSource { export class DataSourcePage implements DataSource {
constructor(url: string, contentType: string) { constructor(url: string, contentType: string) {
let mime = MIME[contentType];
this.sourceType = "DataSourcePage"; this.sourceType = "DataSourcePage";
this.name = "*"; this.name =
"Page's content" +
(mime != undefined && mime != null ? " (" + mime.name + ")" : "");
this.url = url; this.url = url;
this.contentType = contentType; this.contentType = contentType;
this.priority = 1; this.priority = 1;
...@@ -232,7 +235,12 @@ export class DataSourceLinked implements DataSource { ...@@ -232,7 +235,12 @@ export class DataSourceLinked implements DataSource {
if (mime == null || mime == undefined) throw "Unrecognized link type"; if (mime == null || mime == undefined) throw "Unrecognized link type";
this.tags = link.tags; this.tags = link.tags;
this.sourceType = "DataSourceLinked"; this.sourceType = "DataSourceLinked";
this.name = mime.name; this.name =
"Linked content" +
(mime != undefined && mime != null ? " (" + mime.name + ")" : "") +
" (" +
link.url +
")";
this.url = link.url; this.url = link.url;
this.contentType = mime.mime; this.contentType = mime.mime;
this.priority = mime.priority; this.priority = mime.priority;
...@@ -302,23 +310,60 @@ function compareSources(x: DataSource, y: DataSource): number { ...@@ -302,23 +310,60 @@ function compareSources(x: DataSource, y: DataSource): number {
*/ */
export const NO_DATA = new DataSourceNone(); 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 * Data about a tab
*/ */
export class TabData { export class TabData {
constructor() { constructor() {
this.isActive = false; this.command = new TabCommand();
this.mainSource = NO_DATA; this.mainSource = NO_DATA;
this.sources = []; this.sources = [];
//this.clear = this.clear.bind(this);
this.primaryTopic = null; 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 * The data associated to this tab
*/ */
...@@ -332,10 +377,22 @@ export class TabData { ...@@ -332,10 +377,22 @@ export class TabData {
*/ */
primaryTopic: string; 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 * Clears this data
*/ */
clear(): void { clear(): void {
this.command.isAutomatic = true;
this.command.selectedSource = null;
this.command.selectedView = null;
this.mainSource = NO_DATA; this.mainSource = NO_DATA;
this.sources = []; this.sources = [];
this.primaryTopic = null; this.primaryTopic = null;
...@@ -345,15 +402,27 @@ export class TabData { ...@@ -345,15 +402,27 @@ export class TabData {
* Selects the main resource among the provided one * Selects the main resource among the provided one
*/ */
selectMain(): DataSource { selectMain(): DataSource {
if (this.sources.length == 0) { if (this.command.isAutomatic || this.command.selectedSource == null) {
this.mainSource = NO_DATA; if (this.sources.length == 0) {
this.mainSource = NO_DATA;
} else {
this.sources = this.sources
.filter((source: DataSource) => {
return source != NO_DATA;
})
.sort(compareSources);
this.mainSource = this.sources.length == 0 ? NO_DATA : this.sources[0];
}
} else { } else {
this.sources = this.sources this.mainSource = this.sources.reduce(
.filter((source: DataSource) => { (acc: DataSource, current: DataSource) => {
return source != NO_DATA; if (acc != null) return acc;
}) if (current.url == this.command.selectedSource) return current;
.sort(compareSources); return null;
this.mainSource = this.sources.length == 0 ? NO_DATA : this.sources[0]; },
null
);
if (this.mainSource == null) this.mainSource = NO_DATA;
} }
return this.mainSource; return this.mainSource;
} }
......
...@@ -27,7 +27,7 @@ let _rdf = require("rdflib"); ...@@ -27,7 +27,7 @@ let _rdf = require("rdflib");
export const $rdf: RDF = _rdf; export const $rdf: RDF = _rdf;
export function loadRdfStore(tabData: TabData): Promise<any> { export function loadRdfStore(tabData: TabData): Promise<any> {
if (!tabData.isActive) { if (!tabData.command.isActive) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
reject("no data"); reject("no data");
}); });
......
...@@ -52,7 +52,7 @@ chrome.runtime.onMessage.addListener(function( ...@@ -52,7 +52,7 @@ chrome.runtime.onMessage.addListener(function(
sender, sender,
sendResponse sendResponse
) { ) {
let activate = request.isActive && request.mainSource != NO_DATA; let activate = request.command.isActive && request.mainSource != NO_DATA;
if (activate) onUpdateActivate(request); if (activate) onUpdateActivate(request);
else onUpdateDeactivate(); else onUpdateDeactivate();
}); });
......
...@@ -14,8 +14,10 @@ ...@@ -14,8 +14,10 @@
"*://*/*" "*://*/*"
], ],
"browser_action": { "browser_action": {
"browser_style": true,
"default_icon": "icons/app_disabled.png", "default_icon": "icons/app_disabled.png",
"default_title": "Linked Data Browser" "default_title": "Linked Data Browser",
"default_popup": "popup/index.html"
}, },
"content_scripts": [ "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"));