Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • dfn2/pling-store-development
  • azubieta/pling-store-development
  • skye111/pling-store-development
  • fischy0487/pling-store-development
  • mussah/pling-store-development
  • visdom/pling-store-development
  • violethaze74/pling-store-development
  • wh0eta/pling-store-development
  • walmar163/pling-store-development
  • eefaerber/pling-store-development
  • mutazawad/pling-store-development
  • petrovgeorgi/pling-store-development
  • alleykatt/pling-store-development
  • 1147008/pling-store-development
  • jc01-onwork/pling-store-development
  • arystan/pling-store-development
  • hasithalakshan596/pling-store-development
  • motrmtl/pling-store-development
  • zakirhossain7745/pling-store-development
  • marcuseier/pling-store-development
  • rakansherif23/pling-store-development
  • badr2872/pling-store-development
  • dado105/pling-store-development
  • foolish13/pling-store-development
  • alex0115201/pling-store-development
  • kungfu8edora/pling-store-development
  • cxslucyfer/pling-store-development
  • khmisa4500/pling-store-development
  • androny22-77/pling-store-development
  • asus-bd24/pling-store-development
30 results
Show changes
Showing
with 1606 additions and 0 deletions
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
</body>
</html>
export default class OcsManagerApi {
constructor(url) {
this._url = url;
this._websocket = null;
this._callback = new Map();
this._autoReconnect = false;
}
get callback() {
return this._callback;
}
get isConnected() {
return (this._websocket && this._websocket.readyState === 1) ? true : false;
}
async connect() {
return new Promise((resolve, reject) => {
if (!this.isConnected) {
this._websocket = new WebSocket(this._url);
this._autoReconnect = true;
this._websocket.addEventListener('open', () => {
resolve(true);
});
this._websocket.addEventListener('message', (event) => {
// console.log(event);
const message = event.data ? JSON.parse(event.data) : {};
if (message.func && this._callback.has(message.func)) {
const callback = this._callback.get(message.func);
callback(message);
}
});
this._websocket.addEventListener('close', () => {
if (this._autoReconnect) {
setTimeout(() => {
this._websocket = null;
this.connect();
}, 3000);
}
});
this._websocket.addEventListener('error', () => {
this._websocket = null;
reject(new Error('WebSocket connection error'));
});
}
else {
reject(new Error('WebSocket is already connected'));
}
});
}
async disconnect() {
return new Promise((resolve, reject) => {
if (this.isConnected) {
this._autoReconnect = false;
this._websocket.addEventListener('close', () => {
this._websocket = null;
resolve(true);
});
this._websocket.close();
}
else {
reject(new Error('WebSocket is not connected'));
}
});
}
async send(func, data = [], id = '') {
return new Promise((resolve, reject) => {
id = id || this._generateId();
if (this.isConnected) {
this._websocket.send(JSON.stringify({
id: id,
func: func,
data: data
}));
resolve(id);
}
else {
reject(new Error('WebSocket is not connected'));
}
});
}
async sendSync(func, data = [], id = '') {
return new Promise((resolve, reject) => {
id = id || this._generateId();
let webSocket = new WebSocket(this._url);
webSocket.addEventListener('open', () => {
webSocket.send(JSON.stringify({
id: id,
func: func,
data: data
}));
});
webSocket.addEventListener('message', (event) => {
const message = event.data ? JSON.parse(event.data) : {};
if (message.id && message.id === id) {
webSocket.close();
resolve(message);
}
});
webSocket.addEventListener('close', () => {
webSocket = null;
});
webSocket.addEventListener('error', () => {
webSocket = null;
reject(new Error(`WebSocket connection error (id: ${id})`));
});
});
}
_generateId() {
const length = 16;
const strings = '0123456789'
+ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
+ 'abcdefghijklmnopqrstuvwxyz';
const stringArray = strings.split('');
let randomString = '';
for (let i = 0; i < length; i++) {
randomString += stringArray[Math.floor(Math.random() * stringArray.length)];
}
return randomString;
}
}
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
import React, {useState} from 'react';
import OcsManagerApi from '../api/OcsManagerApi.js';
import {Context} from '../context-provider';
import appConfig from '../configs/application.json';
import MenuBar from './MenuBar';
import WebView from './WebView';
import AppDialogs from './AppDialogs';
import '../styles/App.css';
function App(){
const { appDispatch, browserDispatch, ocsApiState, ocsApiDispatch } = React.useContext(Context);
const [ isSubscribed, setIsSubscribed ] = useState(false);
const [ loading, setLoading ] = useState(true);
const [ webviewEl, setWebviewEl ] = useState();
const packageJson = ipcRenderer.sendSync('app','package');
React.useEffect(() => { initApp(); },[])
React.useEffect(() => {
if (ocsApiState.loading === false) connectOcsApi();
},[ocsApiState.loading])
React.useEffect(() => {
if (typeof webviewEl !== undefined & !isSubscribed){
if (ocsApiState.ocsManagerApi && ocsApiState.ocsManagerApi.callback){
setIsSubscribed(true);
subscribeToOcsApiCallback();
}
}
},[ocsApiState.ocsManagerApi, webviewEl])
function initApp(){
let homepageUrl = localStorage.getItem('homePageUrl'), homepageTitle = localStorage.getItem('homePageTitle');
if (homepageUrl === null) homepageUrl = packageJson.repoHomepage, homepageTitle = packageJson.productName;
browserDispatch({type:'SET_PAGE',url:homepageUrl,title:homepageTitle});
const ocsManagerApi = new OcsManagerApi(ipcRenderer.sendSync('ocs-manager', 'url'));
ocsApiDispatch({type:'SET_API',ocsManagerApi:ocsManagerApi});
}
function connectOcsApi(){
const connectToOcsApi = ocsApiState.ocsManagerApi.connect();
const getAppConfigInstallTypes = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getAppConfigInstallTypes', [])
const getUserConfigInstalledItems = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getUsrConfigInstalledItems', []);
const getUserConfigUpdateAvailableItems = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getUsrConfigUpdateAvailableItems', []);
const getUserConfigApplication = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getUsrConfigApplication', []);
connectToOcsApi.then(function(value){
return getAppConfigInstallTypes;
}).then(function(res){
ocsApiDispatch({type:'SET_INSTALL_TYPES',installTypes:res.data[0]});
return getUserConfigInstalledItems;
}).then(function(res){
ocsApiDispatch({type:'SET_INSTALLED_ITEMS',installedItems:res.data[0]});
return getUserConfigUpdateAvailableItems;
}).then(function(res){
if (res.data.length > 0){
/*const combinedUpdateAvailableItems = {};
for (const value of Object.values(updateAvailableItems)) {
const itemKey = value.installed_item;
combinedUpdateAvailableItems[itemKey] = installedItems[itemKey];
}*/
}
return getUserConfigApplication;
}).then(function(res){
const updateCheckedAt = res.data[0].update_checked_at;
const updateCheckAfter = ipcRenderer.sendSync('app', 'config').updateCheckAfter;
ocsApiDispatch({type:'SET_UPDATE_CHECK',updateCheckedAt:updateCheckedAt,updateCheckAfter:updateCheckAfter});
const shouldCheckForUpdates = !updateCheckedAt || (updateCheckedAt + updateCheckAfter) < new Date().getTime();
if (shouldCheckForUpdates) ocsApiState.ocsManagerApi.send('UpdateHandler::checkAll', []);
setAppInfo();
});
}
function setAppInfo(){
console.log(ocsApiState);
appDispatch({type:'SET_PACKAGE_JSON',packageJson:packageJson})
appDispatch({type:'SET_DOMAINS',domains:appConfig.domains});
setLoading(false);
}
function subscribeToOcsApiCallback(){
ocsApiState.ocsManagerApi.callback
.set('ItemHandler::metadataSetChanged', () => {
ocsApiState.ocsManagerApi.sendSync('ItemHandler::metadataSet', [])
})
.set('ItemHandler::downloadStarted', (message) => {
console.log('download started')
console.log(message);
ocsApiDispatch({type:'SET_DOWNLOAD_ITEM',item:message.data[0].metadata});
// Download preview picture
const selector = 'meta[property="og:image"]';
webviewEl.getWebContents().executeJavaScript(`document.querySelector('${selector}').content`, false, (result) => {
let previewpicUrl = result || '';
// FIXME: previewpic API maybe deprecated
/*if (!previewpicUrl
&& message.data[0].metadata.command === 'install'
&& message.data[0].metadata.provider
&& message.data[0].metadata.content_id
) {
previewpicUrl = `${message.data[0].metadata.provider}content/previewpic/${message.data[0].metadata.content_id}`;
}*/
console.log(result);
if (previewpicUrl) ipcRenderer.sendSync('previewpic', 'download', message.data[0].metadata.url, previewpicUrl);
});
})
.set('ItemHandler::downloadProgress', (message) => {
console.log('download progress');
ocsApiDispatch({
type:'UPDATE_ITEM_DOWNLOAD_PROGRESS',
itemUrl:message.data[0],
bytesRecieved:message.data[1],
bytesTotal:message.data[2]
})
})
.set('ItemHandler::downloadFinished', (message) => {
console.log('download started');
if (message.data[0].status !== 'success_download') console.error(new Error(message.data[0].message));
console.log(message.data[0].message)
ocsApiDispatch({
type:'UPDATE_ITEM_DOWNLOAD_PROGRESS',
itemUrl:message.data[0].metadata.url,
status:message.data[0].message
})
})
.set('ItemHandler::saveStarted', (message) => {
if (message.data[0].status !== 'success_savestart') console.error(new Error(message.data[0].message));
console.log(message.data[0].message)
ocsApiDispatch({
type:'UPDATE_ITEM_DOWNLOAD_PROGRESS',
itemUrl:message.data[0].metadata.url,
status:message.data[0].message
})
})
.set('ItemHandler::saveFinished', (message) => {
if (message.data[0].status !== 'success_save') console.error(new Error(message.data[0].message));
console.log(message.data[0].message)
ocsApiDispatch({
type:'UPDATE_ITEM_DOWNLOAD_PROGRESS',
itemUrl:message.data[0].metadata.url,
status:message.data[0].message
})
})
.set('ItemHandler::installStarted', (message) => {
if (message.data[0].status !== 'success_installstart') console.error(new Error(message.data[0].message));
console.log(message.data[0].message)
ocsApiDispatch({
type:'UPDATE_ITEM_DOWNLOAD_PROGRESS',
itemUrl:message.data[0].metadata.url,
status:message.data[0].message
})
})
.set('ItemHandler::installFinished', (message) => {
if (message.data[0].status !== 'success_install') console.error(new Error(message.data[0].message));
console.log(message.data[0].message)
ocsApiDispatch({
type:'UPDATE_ITEM_DOWNLOAD_PROGRESS',
itemUrl:message.data[0].metadata.url,
status:message.data[0].message
})
const getAppConfigInstallTypes = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getAppConfigInstallTypes', [])
const getUserConfigInstalledItems = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getUsrConfigInstalledItems', []);
getAppConfigInstallTypes.then(function(res){
ocsApiDispatch({type:'SET_INSTALL_TYPES',installTypes:res.data[0]});
return getUserConfigInstalledItems;
}).then(function(res){
ocsApiDispatch({type:'SET_INSTALLED_ITEMS',installedItems:res.data[0]});
});
});
/*.set('ItemHandler::uninstallStarted', (message) => {
if (message.data[0].status !== 'success_uninstallstart') {
console.error(new Error(message.data[0].message));
}
})
.set('ItemHandler::uninstallFinished', (message) => {
if (message.data[0].status !== 'success_uninstall') {
console.error(new Error(message.data[0].message));
}
this._stateManager.dispatch('ocsManager_installedItems', {});
this._stateManager.dispatch('ocsManager_updateAvailableItems', {});
})
.set('UpdateHandler::checkAllStarted', (message) => {
if (!message.data[0]) {
console.error(new Error('Item update check failed'));
}
})
.set('UpdateHandler::checkAllFinished', (message) => {
if (!message.data[0]) {
console.error(new Error('Item update check failed'));
}
this._stateManager.dispatch('ocsManager_updateAvailableItems', {});
})
.set('UpdateHandler::updateStarted', (message) => {
if (!message.data[1]) {
console.error(new Error('Item update failed'));
}
})
.set('UpdateHandler::updateFinished', (message) => {
if (!message.data[1]) {
console.error(new Error('Item update failed'));
}
this._stateManager.dispatch('ocsManager_installedItems', {});
this._stateManager.dispatch('ocsManager_updateAvailableItems', {});
})
.set('UpdateHandler::updateProgress', (message) => {
this._stateManager.dispatch('ocsManager_updateProgress', {
itemKey: message.data[0],
progress: message.data[1]
});
});*/
}
let menuBarDisplay, webViewDisplay, appDialogsDisplay;
if (!loading){
menuBarDisplay = <MenuBar/>
webViewDisplay = <WebView onSetWebviewEl={(webviewEl) => setWebviewEl(webviewEl)}/>
appDialogsDisplay = <AppDialogs/>
}
return (
<div id="main">
{menuBarDisplay}
{webViewDisplay}
{appDialogsDisplay}
</div>
);
}
export default App;
import React from 'react';
import {Context} from '../context-provider';
import AppLogo from '../images/app-icons/pling-store.png';
import Collections from './Collections';
import '../styles/AppDialogs.css';
import Close from '@material-ui/icons/Close';
function AppDialogs(){
const { appState, appDispatch } = React.useContext(Context);
function onCloseDialogClick(){
appDispatch({type:'HIDE_DIALOG'});
}
let appDialogDisplay,
dialogOverlayClass = "inactive",
dialogBackgroundOverlayDisplay;
if (appState.dialog.show === true){
dialogOverlayClass = "active";
if (appState.dialog.id === "loading") appDialogDisplay = <LoadingDialog/>
else if (appState.dialog.id === "about") appDialogDisplay = <AboutDialog onCloseDialogClick={onCloseDialogClick}/>
else if (appState.dialog.id === "collections") appDialogDisplay = <CollectionsDialog onCloseDialogClick={onCloseDialogClick}/>
if (appState.dialog.id !== "loading") dialogBackgroundOverlayDisplay = <div id="dialog-overlay-background" onClick={onCloseDialogClick}></div>
}
return (
<div id="dialog-overlay" className={dialogOverlayClass}>
{dialogBackgroundOverlayDisplay}
{appDialogDisplay}
</div>
)
}
function DialogHeader(props){
return (
<header>
<div className="header-content">
<h3>{props.title}</h3>
</div>
<div className="header-control">
<button onClick={props.onCloseDialogClick}>
<i className="material-icons md-medium md-dark md-active">
<Close/>
</i>
</button>
</div>
</header>
)
}
function LoadingDialog(){
const { appState } = React.useContext(Context);
return (
<article className="dialog fade-in" id="loading-dialog">
<article className="content">
<div className="inner-content">
<figure><img src={AppLogo}/></figure>
<h3>Welcome to {appState.packageJson.productName}</h3>
<p>Loading ...</p>
</div>
</article>
</article>
)
}
function AboutDialog(props){
const { appState } = React.useContext(Context);
const packageJson = appState.packageJson;
return (
<article className="dialog fade-in" id="about-dialog">
<DialogHeader
title={"About This App"}
onCloseDialogClick={props.onCloseDialogClick}
/>
<article className="content">
<div className="inner-content">
<figure>
<img src={AppLogo}/>
</figure>
<h4>{packageJson.productName}</h4>
<p>{packageJson.version}</p>
<p>{packageJson.description}</p>
<p>
Author: {packageJson.author}<br/>
License: {packageJson.license}
</p>
<p>
Website: <a href={packageJson.repoHomepage}>{packageJson.repoHomepage}</a><br/>
Project page: <a href={packageJson.repository}>{packageJson.repository}</a><br/>
Report a bug: <a href={packageJson.repository}>{packageJson.bugs}</a>
</p>
</div>
</article>
</article>
)
}
function CollectionsDialog(props){
return (
<article id="collections-dialog" className="dialog fade-in">
<DialogHeader
title={"My Collections"}
onCloseDialogClick={props.onCloseDialogClick}
/>
<article className="content">
<Collections/>
</article>
</article>
)
}
export default AppDialogs;
\ No newline at end of file
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
import React, { useState} from 'react';
import { Context } from '../context-provider';
import { ConvertByteToHumanReadable } from '../helpers/AppHelpers';
import '../styles/Collections.css';
import LinearProgress from '@material-ui/core/LinearProgress';
import CloudDownload from '@material-ui/icons/CloudDownload';
function Collections(){
const [ view, setView ] = useState({section:'download',type:'all'});
return (
<div id="collections-container">
<CollectionsSidebar
view={view}
onSetView={(newView) => setView(newView)}
/>
<CollectionsSwitchView view={view} />
</div>
)
}
function CollectionsSidebar(props){
const { ocsApiState, ocsApiDispatch } = React.useContext(Context);
function onSetView(view){
props.onSetView(view);
}
let installedTypesMenuDisplay;
if (ocsApiState.installedItemTypes && ocsApiState.installedItemTypes.length > 0){
const InstalledTypesMenu = ocsApiState.installedItemTypes.sort().map((iit,index) => {
const countItems = ocsApiState.installedItems.filter((i) => i.install_type === iit).length;
const menuItemCssClass = props.view.section === "installed" && props.view.type === iit ? "selected" : "";
return (
<li key={index}>
<a className={menuItemCssClass} onClick={() => onSetView({section:'installed',type:iit})}>
<span className="name">{iit}</span>
<span className="app-badge low-emphasis">{countItems}</span>
</a>
</li>
)
});
installedTypesMenuDisplay = (
<nav>
<h4>Installed</h4>
<ul>{InstalledTypesMenu}</ul>
</nav>
)
}
let downloadItemsCounterDisplay;
if (ocsApiState.downloadItems.length > 0){
const filteredDownloadItems = ocsApiState.downloadItems.filter((di) => di.status === "Downloading");
if (filteredDownloadItems.length > 0){
downloadItemsCounterDisplay = <span className="app-badge medium-emphasis">{filteredDownloadItems.length}</span>
}
}
return (
<aside>
<ul className="task">
<li>
<a className={props.view.section === "download" ? "selected" : ""} onClick={() => onSetView({section:'download',type:'all'})}>
<span className="name">Download</span>
{downloadItemsCounterDisplay}
</a>
</li>
</ul>
{installedTypesMenuDisplay}
</aside>
)
}
function CollectionsSwitchView(props){
const { ocsApiState } = React.useContext(Context);
const arrayName = props.view.section + "Items";
let items = ocsApiState[arrayName];
if (props.view.type !== "all") items = items.filter((item) => item.install_type === props.view.type);
const collectionitemsListDisplay = items.map((item,index) => (
<CollectionItem key={index} item={item} section={props.view.section}/>
));
return (
<main id="collections-switch-view">{collectionitemsListDisplay}</main>
)
}
function CollectionItem(props){
const item = props.item;
let collectionItemDisplay;
if (props.section === "download"){
collectionItemDisplay = <CollectionDownloadItem item={item} />
} else {
collectionItemDisplay = item.files.map((file,index) => (
<CollectionItemFile key={index} item={item} file={file}/>
))
}
return (
<ul className="item-sublist">
{collectionItemDisplay}
</ul>
)
}
function CollectionDownloadItem(props){
const item = props.item;
let progressBarDisplay, messageDisplay = "Downloading"
if (item.status === "Downloading"){
if (item.bytesRecieved && item.bytesTotal){
const downloadedPercent = item.bytesRecieved / item.bytesTotal;
progressBarDisplay = <div className="progress-bar-container"><progress data-progress value={downloadedPercent} max="1"/></div>
messageDisplay = "Downloading... " + ConvertByteToHumanReadable(item.bytesRecieved) + "/" + ConvertByteToHumanReadable(item.bytesTotal);
}
} else {
messageDisplay = "the file has been installed";
}
return (
<li>
<figure className="preview-pic">
<i className="material-icons md-larger md-dark"><CloudDownload/></i>
</figure>
<div className="item-main">
<h4>{item.filename}</h4>
{progressBarDisplay}
<p className="message">{messageDisplay}</p>
</div>
</li>
)
}
function CollectionItemFile(props){
const { ocsApiState, ocsApiDispatch } = React.useContext(Context);
const [ isApplicableType, setIsApplicableType ] = useState(false);
const [ isApplying, setIsApplying ] = useState(false);
const [ isOpening, setIsOpening ] = useState(false);
const [ isDeleting, setIsDeleting ] = useState(false);
const item = props.item;
const file = props.file;
React.useEffect(() => { checkIfIsApplicable(); },[])
React.useEffect(() => { checkIfIsApplicable(); },[props.item])
function checkIfIsApplicable(){
ocsApiState.ocsManagerApi.sendSync('DesktopThemeHandler::isApplicableType', [item.install_type]).then(function(res){
setIsApplicableType(res.data[0]);
})
}
function onApplyClick(filePath,installType){
setIsApplying(true);
setTimeout(() => {
ocsApiState.ocsManagerApi.send('DesktopThemeHandler::applyTheme', [filePath, installType]).then(function(data){
setIsApplying(false);
})
}, 1000);
}
function onOpenClick(fileUrl){
setIsOpening(true);
setTimeout(() => {
ocsApiState.ocsManagerApi.send('SystemHandler::openUrl', [fileUrl]).then(function(data){
setIsOpening(false);
})
}, 1000);
}
function onDeleteClick(itemUrl){
setIsDeleting(true);
setTimeout(() => {
ocsApiState.ocsManagerApi.send('ItemHandler::uninstall', [itemUrl]);
ipcRenderer.sendSync('previewpic', 'remove', itemUrl);
const getAppConfigInstallTypes = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getAppConfigInstallTypes', [])
const getUserConfigInstalledItems = ocsApiState.ocsManagerApi.sendSync('ConfigHandler::getUsrConfigInstalledItems', []);
getAppConfigInstallTypes.then(function(res){
ocsApiDispatch({type:'SET_INSTALL_TYPES',installTypes:res.data[0]});
return getUserConfigInstalledItems;
}).then(function(res){
ocsApiDispatch({type:'SET_INSTALLED_ITEMS',installedItems:res.data[0]});
});
setIsDeleting(false);
});
}
const previewpicDirectory = ipcRenderer.sendSync('previewpic', 'directory');
const previewpicUrl = 'file://' + previewpicDirectory + '/' + ( btoa(item.url).slice(-255) );
const destination = ocsApiState.installTypes[item.install_type].destination;
const filePath = destination + '/' + file;
const fileUrl = 'file://'+filePath;
let applyButtonDisplay;
if (isApplicableType === true){
let applyProgressBar;
if (isApplying === true) applyProgressBar = <div id="progress-bar-container"><LinearProgress/></div>
applyButtonDisplay = (
<button onClick={e => onApplyClick(filePath,item.install_type)}>
Apply
{applyProgressBar}
</button>
)
}
let openProgressBar;
if (isOpening === true) openProgressBar = <div id="progress-bar-container"><LinearProgress/></div>;
let deleteProgressBar;
if (isDeleting === true) deleteProgressBar = <div id="progress-bar-container"><LinearProgress/></div>;
return (
<li>
<figure className="preview-pic" style={{"backgroundImage":"url('"+previewpicUrl+"')"}}></figure>
<div className="item-main"><h4>{file}</h4></div>
<nav className="actions">
{applyButtonDisplay}
<button onClick={e => onOpenClick(fileUrl)}>
{item.install_type === "bin" ? "Run" : "Open"}
{openProgressBar}
</button>
<button onClick={e => onDeleteClick(item.url)}>
Delete
{deleteProgressBar}
</button>
</nav>
</li>
)
}
export default Collections;
\ No newline at end of file
import React, { useState } from 'react';
import {Context} from '../context-provider';
import '../styles/default/material-icons.css';
import '../styles/MenuBar.css';
import ArrowBack from '@material-ui/icons/ArrowBack';
import ArrowForward from '@material-ui/icons/ArrowForward';
import Refresh from '@material-ui/icons/Refresh';
import Home from '@material-ui/icons/Home';
import Folder from '@material-ui/icons/Folder';
import MoreVert from '@material-ui/icons/MoreVert';
import OpenInBrowser from '@material-ui/icons/OpenInBrowser';
import Close from '@material-ui/icons/Close';
import AccountCircle from '@material-ui/icons/AccountCircle';
import LinearProgress from '@material-ui/core/LinearProgress';
function MenuBar(){
/* COMPONENT */
const { appDispatch, browserState, browserDispatch, ocsApiState } = React.useContext(Context);
const [ backButtonActive, setBackButtonActive ] = useState(false);
const [ forwardButtonActive, setForwardButtonActive ] = useState(false);
React.useEffect(() => {
if (browserState.canGoBack === true) setBackButtonActive(true);
else setBackButtonActive(false);
if (browserState.canGoForward === true) setForwardButtonActive(true);
else setForwardButtonActive(false);
},[browserState])
function onBackButtonClick(){
browserDispatch({type:'GO_BACK',goBack:true});
}
function onForwardButtonClick(){
browserDispatch({type:'GO_FORWARD',goForward:true});
}
function onRefreshButtonClick(){
browserDispatch({type:'REFRESH',refresh:true});
}
function onStopButtonClick(){
browserDispatch({type:'STOP_LOADING',stopLoading:true})
}
function onHomeButtonClick(){
browserDispatch({type:'GO_HOME',goHome:true});
}
function onCollectionsButtonClick(){
appDispatch({type:'SHOW_DIALOG',id:'collections'})
}
function onLoginButtonClick(){
browserDispatch({type:'LOAD_URL',newUrl:"https://www.pling.com/login",loadUrl:true});
}
/* /COMPONENT */
/* RENDER */
let backButtonDisplay;
if (backButtonActive === true){
backButtonDisplay = (
<button title="Back" onClick={onBackButtonClick}>
<i className="material-icons md-medium md-dark md-active"><ArrowBack/></i>
</button>
)
} else {
backButtonDisplay = (
<button title="Back">
<i className="material-icons md-medium md-dark md-inactive"><ArrowBack/></i>
</button>
)
}
let forwardButtonDisplay;
if (forwardButtonActive === true){
forwardButtonDisplay = (
<button title="Forward" onClick={onForwardButtonClick}>
<i className="material-icons md-medium md-dark md-active"><ArrowForward/></i>
</button>
)
} else {
forwardButtonDisplay = (
<button title="Forward" disabled="">
<i className="material-icons md-medium md-dark md-inactive"><ArrowForward/></i>
</button>
)
}
let refreshButtonDisplay;
if (browserState.loading === false){
refreshButtonDisplay = (
<button title="Refresh" onClick={onRefreshButtonClick}>
<i className="material-icons md-medium md-dark md-active"><Refresh/></i>
</button>
)
} else {
refreshButtonDisplay = (
<button title="Stop" onClick={onStopButtonClick}>
<i className="material-icons md-medium md-dark md-active"><Close/></i>
</button>
)
}
/* /RENDER */
let downloadItemsCounterDisplay;
if (ocsApiState.downloadItems.length > 0){
const filteredDownloadItems = ocsApiState.downloadItems.filter((di) => di.status === "Downloading");
if (filteredDownloadItems.length > 0){
downloadItemsCounterDisplay = <span className="app-badge medium-emphasis">{filteredDownloadItems.length}</span>
}
}
return (
<nav id="menu-bar">
<ul id="main-menu">
<li>{backButtonDisplay}</li>
<li>{forwardButtonDisplay}</li>
<li>{refreshButtonDisplay}</li>
<li>
<button title="Home" onClick={onHomeButtonClick}>
<i className="material-icons md-medium md-dark md-active">
<Home/>
</i>
</button>
</li>
<li>
<button title="Collections" onClick={onCollectionsButtonClick}>
<i className="material-icons md-medium md-dark md-active">
<Folder/>
{downloadItemsCounterDisplay}
</i>
</button>
</li>
<li id="omnibox-menu"><OmniBox/></li>
<li><UserMenu/></li>
<li>
<button title="Login" onClick={onLoginButtonClick}>
<i className="material-icons md-medium md-dark md-active">
<AccountCircle/>
</i>
</button>
</li>
</ul>
</nav>
)
}
function OmniBox(){
const { appState, browserState, browserDispatch, ocsApiState } = React.useContext(Context);
const [ showOmniBox, setShowOmniBox ] = useState(false);
function toggleOmniBox(){
const newShowOmniBox = showOmniBox === false ? true : false;
setShowOmniBox(newShowOmniBox);
}
function setAppHomePage(domain){
const domainUrl = domain.url;
const domainTitle = domain.title;
localStorage.setItem('homePageUrl', domainUrl);
localStorage.setItem('homePageTitle', domainTitle);
setShowOmniBox(false);
browserDispatch({type:'LOAD_URL',newUrl:domain.url,loadUrl:true});
}
function onOpenInBrowserClick(){
console.log(browserState.url);
ocsApiState.ocsManagerApi.send('SystemHandler::openUrl', [browserState.url])
}
let linearProgressionDisplay;
if (browserState.loading === true) linearProgressionDisplay = <div id="progress-bar-container"><LinearProgress/></div>
const domainsListDisplay = appState.domains.map((d,index) => (
<li key={index}>
<button onClick={() => setAppHomePage(d)} className={d.homepage === true ? "active" : ""}>
{d.title}
</button>
</li>
));
let omniboxCssClasses = "";
if (ocsApiState.downloadItems){
const filteredDownloadItems = ocsApiState.downloadItems.filter((di) => di.status === "Downloading");
if (filteredDownloadItems.length > 0) omniboxCssClasses += " download-active";
}
return (
<div id="omnibox-container">
<div id="omnibox" className={omniboxCssClasses}>
<div id="omnibox-wrapper">
<div id="omnibox-content">
<div></div>
<h3 onClick={toggleOmniBox}>{browserState.title}</h3>
<div id="open-in-browser-container">
<button title="open in browser" onClick={() => onOpenInBrowserClick()}>
<i className="material-icons md-small md-dark md-active"><OpenInBrowser/></i>
</button>
</div>
</div>
{linearProgressionDisplay}
</div>
</div>
<div id="omnibox-palette" className={"fade-in " + (showOmniBox === true ? "active" : "inactive")}>
<div className="palette-content">
<h4><i className="material-icons md-small"><Home/></i> Choose Startpage</h4>
<nav>
<ul>
{domainsListDisplay}
</ul>
</nav>
</div>
</div>
<div id="overlay"></div>
</div>
)
}
function UserMenu(){
const { appState, appDispatch, ocsApiState } = React.useContext(Context);
const { browserDispatch } = React.useContext(Context);
const [ showMenu, updateShowMenu ] = useState(false);
function onToggleMenuVisiblity(){
const newShowMenu = showMenu === true ? false : true;
updateShowMenu(newShowMenu);
}
function onReportBugClick(){
browserDispatch({type:'LOAD_URL',newUrl:appState.packageJson.bugs,loadUrl:true});
updateShowMenu(false);
}
function onCheckForUpdates(){
ocsApiState.ocsManagerApi.send('UpdateHandler::checkAppUpdate', []).then(function(res){
console.log(res)
});
updateShowMenu(false);
}
function onAboutClick(){
appDispatch({type:'SHOW_DIALOG',id:'about'});
updateShowMenu(false);
}
let userMenuDisplay, backgroundOverlayDisplay;
if (showMenu === true){
userMenuDisplay = (
<ul id="user-menu">
<li><a onClick={() => onReportBugClick()}>Report a Bug</a></li>
<li><a onClick={() => onCheckForUpdates()}>Check for Updates</a></li>
<li><a onClick={() => onAboutClick()}>About This App</a></li>
</ul>
);
backgroundOverlayDisplay = <div onClick={e => updateShowMenu(false)} className="background-overlay"></div>
}
return (
<div id="user-menu-container">
<button title="more operations..." onClick={onToggleMenuVisiblity}>
<i className="material-icons md-medium md-dark md-active">
<MoreVert/>
</i>
</button>
{userMenuDisplay}
{backgroundOverlayDisplay}
</div>
)
}
export default MenuBar;
const electron = window.require('electron');
const ipcRenderer = electron.ipcRenderer;
import React, {useState} from 'react';
import {Context} from '../context-provider';
function WebView(props){
const { appState, appDispatch, browserState, browserDispatch, ocsApiState } = React.useContext(Context);
const [ webviewEl, setWebviewEl ] = useState();
const [ showWebviewMask, setShowWebviewMask ] = useState(true);
React.useEffect(() => { initWebView(browserState.url); },[])
React.useEffect(() => {
if (typeof webviewEl !== undefined) props.onSetWebviewEl(webviewEl);
},[webviewEl])
React.useEffect(() => {
if (browserState.goBack === true){
browserDispatch({type:'GO_BACK',goBack:false})
webviewEl.goBack();
setShowWebviewMask(true);
}
},[browserState.goBack])
React.useEffect(() => {
if (browserState.goForward === true){
browserDispatch({type:'GO_FORWARD',goForward:false})
webviewEl.goForward();
}
},[browserState.goForward])
React.useEffect(() => {
if (browserState.goHome === true){
browserDispatch({type:'GO_HOME', goHome:false})
webviewEl.loadURL('https://www.pling.com');
}
},[browserState.goHome])
React.useEffect(() => {
if (browserState.refresh === true){
browserDispatch({type:'REFRESH', refresh:false})
webviewEl.loadURL(browserState.url);
}
},[browserState.refresh])
React.useEffect(() => {
if (browserState.loadUrl === true){
browserDispatch({type:'LOAD_URL', loadUrl:false})
webviewEl.loadURL(browserState.newUrl);
}
},[browserState.loadUrl])
React.useEffect(() => {
if (browserState.stopLoading === true){
browserDispatch({type:'STOP_LOADING',stopLoading:false});
webviewEl.stop();
}
},[browserState.stopLoading])
function initWebView(url){
const webview = document.createElement('webview');
webview.setAttribute('src', url);
webview.setAttribute('id',"webview-frame");
webview.addEventListener('did-start-loading', () => {
browserDispatch({type:'SET_LOADING',loading:true})
});
webview.addEventListener('did-stop-loading', () => {
browserDispatch({type:'SET_LOADING',loading:false});
if (appState.dialog.show === true) appDispatch({type:'HIDE_DIALOG'});
const webViewInitiScript =
`var metaheader = document.querySelector('#metaheader');
if (metaheader){ metaheader.parentNode.removeChild(metaheader); }
var odMetaHeader = document.querySelector('opendesktop-metaheader');
if (odMetaHeader){ odMetaHeader.parentNode.removeChild(odMetaHeader);}
var gitLabNavBar = document.querySelector('header.navbar-gitlab');
if (gitLabNavBar){ gitLabNavBar.style.top = 0;}
var forumsNavBar = document.querySelector('header.d-header');
if (forumsNavBar){ forumsNavBar.style.top = 0;}
var body = document.querySelector('body');
body.style.paddingTop = 0;`;
webview.getWebContents().executeJavaScript( webViewInitiScript , false, (result) => {
setShowWebviewMask(false);
});
});
webview.addEventListener('dom-ready', (event) => {
browserDispatch({
type:'SET_PAGE',
url:webview.getURL(),
title:webview.getTitle(),
canGoBack:webview.canGoBack(),
canGoForward:webview.canGoForward()
});
webview.send('ipc-message');
});
webview.addEventListener('new-window', (event) => {
console.log('new window');
/*if (event.url.startsWith('http://') || event.url.startsWith('https://')) {
this.dispatch('ocsManager_openUrl', {url: event.url});
}*/
});
webview.addEventListener('will-navigate', (event) => {
console.log('webview element: will navigate');
// See also "will-navigate" event handling in main.js
if (event.url.startsWith('ocs://') || event.url.startsWith('ocss://')) {
console.log('if event.url starts with ocs://')
const info = detectOcsApiInfo(webview.getURL());
console.log('info:');
console.log(info);
console.log('ocs manager - send: item handler::getItemByOcsUrl');
ocsApiState.ocsManagerApi.send('ItemHandler::getItemByOcsUrl', [event.url, info.providerKey, info.contentId]);
}
});
webview.addEventListener('ipc-message', (event) => {
console.log('ipc message');
});
document.querySelector('#webview-container').appendChild(webview);
setWebviewEl(webview);
}
function detectOcsApiInfo(url) {
// Detect provider key and content id from page url
// https://www.opendesktop.org/s/Gnome/p/123456789/?key=val#hash
//
// providerKey = https://www.opendesktop.org/ocs/v1/
// contentId = 123456789
const info = {
providerKey: '',
contentId: ''
};
const matches = url.match(/(https?:\/\/[^/]+).*\/p\/([^/?#]+)/);
if (matches) {
info.providerKey = `${matches[1]}/ocs/v1/`;
info.contentId = matches[2];
}
return info;
}
let webviewMaskDisplay;
if (showWebviewMask === true){
webviewMaskDisplay = <div id="webview-mask"></div>
}
return (
<div id="webview-wrapper">
<div id="webview-container"></div>
{webviewMaskDisplay}
</div>
)
}
export default WebView;
\ No newline at end of file
{
"defaults": {
"startPage": "https://www.pling.com/",
"windowBounds": {
"x": 0,
"y": 0,
"width": 1024,
"height": 768
}
},
"domains":[
{"url":"https://www.pling.com/","title":"pling.com"},
{"url":"https://www.appimagehub.com/","title":"Appimagehub.com"},
{"url":"https://store.kde.org/","title":"KDE"},
{"url":"https://www.pling.com/s/Artwork","title":"Artwork"},
{"url":"https://www.pling.com/s/Gnome","title":"Gnpome"},
{"url":"https://www.pling.com/s/Comics","title":"Comics"},
{"url":"https://www.pling.com/s/XFCE","title":"XFCE"},
{"url":"https://www.pling.com/s/Videos","title":"Videos"}
],
"updateCheckAfter": 86400000, "//": "milliseconds"
}
\ No newline at end of file
{
"bin": "ocs-manager",
"port": 0
}
import React from 'react';
import AppReducer, { AppReducerInitialState } from './reducers/appReducer';
import BrowserReducer, {BrowserReducerInitialState} from './reducers/browserReducer';
import OcsApiReducer, {OcsApiReducerInitialState} from './reducers/ocsApiReducer';
export const Context = React.createContext();
const Provider = Context.Provider;
const StoreContextProvider = (props) => {
const [ appState, appDispatch ] = React.useReducer(AppReducer,AppReducerInitialState);
const [ browserState, browserDispatch ] = React.useReducer(BrowserReducer,BrowserReducerInitialState);
const [ ocsApiState, ocsApiDispatch ] = React.useReducer(OcsApiReducer,OcsApiReducerInitialState);
return (
<Provider {...props} value={{
appState,appDispatch,
browserState,browserDispatch,
ocsApiState,ocsApiDispatch
}}/>
)
}
export default StoreContextProvider;
\ No newline at end of file
const fs = require('fs');
const {spawn} = require('child_process');
const {app, BrowserWindow, ipcMain} = require('electron');
const ElectronStore = require('electron-store');
const request = require('request');
// Set configs dir
app.setPath("userData", app.getPath("appData") + "/OCS-Store")
const appPackage = require('../package.json');
const appConfig = require('./configs/application.json');
const ocsManagerConfig = require('./configs/ocs-manager.json');
const isDebugMode = process.argv.includes('--debug');
const previewpicDirectory = `${app.getPath('userData')}/previewpic`;
const windowIcon = `${__dirname}/images/app-icons/pling-store.png`;
const indexFileUrl = `file://${__dirname}/../build/index.html`;
const appConfigStoreStorage = 'application';
let mainWindow = null;
let ocsManager = null;
let ocsManagerUrl = '';
async function startOcsManager() {
return new Promise((resolve) => {
const resolveOcsManagerUrl = (data) => {
const matches = data.toString().match(/Websocket server started at: "(wss?:\/\/.+)"/);
if (matches) {
ocsManagerUrl = matches[1];
resolve(true);
}
};
ocsManager = spawn(ocsManagerConfig.bin, ['-p', ocsManagerConfig.port, '--appFile', process.env.APPIMAGE]);
ocsManager.stdout.on('data', (data) => {
console.log(`[${ocsManagerConfig.bin}] ${data}`);
if (!ocsManagerUrl) {
resolveOcsManagerUrl(data);
}
});
ocsManager.stderr.on('data', (data) => {
console.error(`[${ocsManagerConfig.bin}] ${data}`);
if (!ocsManagerUrl) {
resolveOcsManagerUrl(data);
}
});
ocsManager.on('close', (code) => {
console.log(`${ocsManagerConfig.bin} exited with code ${code}`);
});
ocsManager.on('error', () => {
console.error(`Failed to start ${ocsManagerConfig.bin}`);
resolve(false);
});
});
}
function stopOcsManager() {
if (ocsManager) {
ocsManager.kill();
ocsManagerUrl = '';
}
}
function createWindow() {
const appConfigStore = new ElectronStore({
name: appConfigStoreStorage,
defaults: appConfig.defaults
});
const windowBounds = appConfigStore.get('windowBounds');
mainWindow = new BrowserWindow({
title: appPackage.productName,
icon: windowIcon,
x: windowBounds.x,
y: windowBounds.y,
width: windowBounds.width,
height: windowBounds.height,
webPreferences: {
nodeIntegration: true
}
});
if (!isDebugMode) {
mainWindow.setMenu(null);
}
mainWindow.loadURL(indexFileUrl);
mainWindow.maximize();
mainWindow.on('close', () => {
const appConfigStore = new ElectronStore({name: appConfigStoreStorage});
appConfigStore.set('windowBounds', mainWindow.getBounds());
});
mainWindow.on('closed', () => {
mainWindow = null;
});
if (isDebugMode) {
mainWindow.webContents.openDevTools();
}
}
function isFile(path) {
try {
const stats = fs.statSync(path);
return stats.isFile();
} catch (error) {
console.error(error);
return false;
}
}
function isDirectory(path) {
try {
const stats = fs.statSync(path);
return stats.isDirectory();
} catch (error) {
console.error(error);
return false;
}
}
function btoa(string) {
const buffer = (string instanceof Buffer) ? string : Buffer.from(string.toString(), 'binary');
return buffer.toString('base64');
}
//function atob(string) {
// return Buffer.from(string, 'base64').toString('binary');
//}
function previewpicFilename(itemKey) {
// "itemKey" will be URL to product file
return btoa(itemKey).slice(-255);
}
function downloadPreviewpic(itemKey, url) {
if (!isDirectory(previewpicDirectory)) {
fs.mkdirSync(previewpicDirectory);
}
const path = `${previewpicDirectory}/${previewpicFilename(itemKey)}`;
request.get(url).on('error', (error) => {
console.error(error);
}).pipe(fs.createWriteStream(path));
}
function removePreviewpic(itemKey) {
const path = `${previewpicDirectory}/${previewpicFilename(itemKey)}`;
if (isFile(path)) {
fs.unlinkSync(path);
}
}
app.on('ready', async () => {
if (await startOcsManager()) {
createWindow();
} else {
app.quit();
}
});
app.on('quit', () => {
stopOcsManager();
});
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
if (mainWindow === null) {
createWindow();
}
});
app.on('web-contents-created', (event, webContents) => {
if (webContents.getType() === 'webview') {
webContents.on('will-navigate', (event, url) => {
if (url.startsWith('ocs://') || url.startsWith('ocss://')) {
// Cancel ocs protocol navigation
event.preventDefault();
}
});
}
});
ipcMain.on('app', (event, key) => {
const data = {
package: appPackage,
config: appConfig,
isDebugMode: isDebugMode
};
event.returnValue = key ? data[key] : data;
});
ipcMain.on('ocs-manager', (event, key) => {
const data = {
config: ocsManagerConfig,
url: ocsManagerUrl
};
event.returnValue = key ? data[key] : data;
});
ipcMain.on('store', (event, key, value) => {
const appConfigStore = new ElectronStore({name: appConfigStoreStorage});
if (key && value) {
appConfigStore.set(key, value);
}
event.returnValue = key ? appConfigStore.get(key) : appConfigStore.store;
});
ipcMain.on('previewpic', (event, kind, itemKey, url) => {
if (kind === 'directory') {
event.returnValue = previewpicDirectory;
} else if (kind === 'path' && itemKey) {
event.returnValue = `${previewpicDirectory}/${previewpicFilename(itemKey)}`;
} else if (kind === 'download' && itemKey && url) {
downloadPreviewpic(itemKey, url);
event.returnValue = undefined;
} else if (kind === 'remove' && itemKey) {
removePreviewpic(itemKey);
event.returnValue = undefined;
} else {
event.returnValue = false;
}
});
const net = require('net');
const port = process.env.PORT ? (process.env.PORT - 100) : 3000;
process.env.ELECTRON_START_URL = `http://localhost:${port}`;
const client = new net.Socket();
let startedElectron = false;
const tryConnection = () => client.connect({port: port}, () => {
client.end();
if(!startedElectron) {
console.log('starting electron');
startedElectron = true;
const exec = require('child_process').exec;
const electron = exec('npm run electron');
electron.stdout.on("data", function(data) {
console.log("stdout: " + data.toString());
});
}
}
);
tryConnection();
client.on('error', (error) => {
setTimeout(tryConnection, 1000);
});
export function ConvertByteToHumanReadable(byte) {
byte = parseFloat(byte);
const kb = 1024;
const mb = 1024 * kb;
const gb = 1024 * mb;
const tb = 1024 * gb;
const pb = 1024 * tb;
const eb = 1024 * pb;
const zb = 1024 * eb;
const yb = 1024 * zb;
let text = '';
if (byte < kb) {
text = `${byte.toFixed(0)} B`;
}
else if (byte < mb) {
text = `${(byte / kb).toFixed(2)} KB`;
}
else if (byte < gb) {
text = `${(byte / mb).toFixed(2)} MB`;
}
else if (byte < tb) {
text = `${(byte / gb).toFixed(2)} GB`;
}
else if (byte < pb) {
text = `${(byte / tb).toFixed(2)} TB`;
}
else if (byte < eb) {
text = `${(byte / pb).toFixed(2)} PB`;
}
else if (byte < zb) {
text = `${(byte / eb).toFixed(2)} EB`;
}
else if (byte < yb) {
text = `${(byte / zb).toFixed(2)} ZB`;
}
else if (byte >= yb) {
text = `${(byte / yb).toFixed(2)} YB`;
}
return text;
}
\ No newline at end of file
src/images/app-icons/opendesktop-app.png

64 KiB

src/images/app-icons/pling-store.png

5.21 KiB

This diff is collapsed.
.icon-ocs-store {
background-image: url(app-icons/pling-store.svg);
}
import React from 'react';
import ReactDOM from 'react-dom';
import StoreContextProvider from './context-provider';
import App from './components/App';
import './styles/Index.css';
function AppWrapper(){
return (
<StoreContextProvider>
<App/>
</StoreContextProvider>
)
}
ReactDOM.render(
<AppWrapper />,
document.getElementById('root')
);