Add object properties sidebar. Closes #68
This commit is contained in:
commit
3bba33e788
|
@ -20,6 +20,7 @@ import {localNodeConfig} from "./localnodeconfig"
|
|||
|
||||
import './components/navbar.css';
|
||||
import { EulaScreen } from './components/eula';
|
||||
import {ObjectPropertiesSidebar} from "./components/objectpropertiessidebar";
|
||||
|
||||
|
||||
//I gave up on trying to make rollup work correctly, just put electron-main dependencies here:
|
||||
|
@ -154,7 +155,6 @@ function App()
|
|||
<Notifications appContext={appContext} />
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<Overlays appContext={appContext} />
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
.object-properties-sidebar {
|
||||
background-color: #260C3C;
|
||||
width: 300px;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.object-properties-sidebar > p, h1, h2 {
|
||||
color: white;
|
||||
word-wrap: break-word;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
import React, {useEffect, useState} from 'react';
|
||||
import {StatorContext} from "../statorobject";
|
||||
import {useWatchSidebarStator} from "./sidebarstator";
|
||||
import {getBinderListStator} from "./openbinders";
|
||||
import {BinderContext} from "./bindercontext";
|
||||
import "./objectpropertiessidebar.css"
|
||||
import {getMetaApi} from "../ipcbindings";
|
||||
|
||||
interface ObjectPropertiesSidebarProps {
|
||||
appContext: StatorContext;
|
||||
hide: boolean;
|
||||
objId: string;
|
||||
binderContext: BinderContext;
|
||||
imageResolution: string | undefined;
|
||||
}
|
||||
|
||||
function ObjectPropertiesSidebar(props: ObjectPropertiesSidebarProps) {
|
||||
const [sizeInBytes, setSizeInBytes] = useState<number>();
|
||||
const [mimeString, setMimeString] = useState("...");
|
||||
const [mainMimeType, setMainMimeType] = useState("");
|
||||
|
||||
useEffect(() => {
|
||||
if(!props.objId) return;
|
||||
|
||||
(async () => {
|
||||
const path = await props.binderContext.db.getObjectPath(props.objId);
|
||||
if(path == "") return;
|
||||
|
||||
const stats = await getMetaApi().readFileStats(path.path);
|
||||
|
||||
if(!stats) return;
|
||||
|
||||
setSizeInBytes(Number(stats.size));
|
||||
|
||||
const mime = await props.binderContext.db.getMimeGenIfNotExists(props.objId);
|
||||
|
||||
setMimeString(mime);
|
||||
const splits = mime.split("/");
|
||||
|
||||
if (splits.length > 1) {
|
||||
setMainMimeType(splits[0]);
|
||||
}
|
||||
})();
|
||||
}, [props.objId]);
|
||||
|
||||
const sizeString = (() => {
|
||||
const sizeInKilobytes = sizeInBytes / 1024;
|
||||
const sizeInMegabytes = sizeInKilobytes / 1024;
|
||||
const sizeInGigabytes = sizeInMegabytes / 1024;
|
||||
|
||||
if(sizeInKilobytes < 1)
|
||||
return sizeInBytes.toFixed(2) + " bytes";
|
||||
if(sizeInMegabytes < 1)
|
||||
return sizeInKilobytes.toFixed(2) + " kilobytes";
|
||||
if(sizeInGigabytes < 1)
|
||||
return sizeInMegabytes.toFixed(2) + " megabytes";
|
||||
|
||||
return String(sizeInGigabytes) + " gigabytes";
|
||||
})();
|
||||
|
||||
|
||||
if(props.hide) return <></>
|
||||
else return <div className={"object-properties-sidebar"}>
|
||||
<h1>Properties</h1>
|
||||
<p>File Size: {sizeString}</p>
|
||||
<p>Type: {mimeString}</p>
|
||||
{mainMimeType == "image" ? <p>Resolution: {props.imageResolution}</p> : ""}
|
||||
</div>
|
||||
}
|
||||
|
||||
|
||||
export {ObjectPropertiesSidebar}
|
|
@ -1,9 +1,15 @@
|
|||
.object-viewer-and-properties-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
.object-viewer
|
||||
{
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
color: white;
|
||||
position:relative;
|
||||
overflow: hidden;
|
||||
|
@ -31,13 +37,16 @@
|
|||
background-color: aliceblue;
|
||||
}
|
||||
|
||||
.object-viewer-controls-left-right-base-style {
|
||||
height: 40%;
|
||||
width: 90px;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.object-viewer-side-control-overlay
|
||||
{
|
||||
position:absolute;
|
||||
display: flex;
|
||||
height: 40%;
|
||||
width: 90px;
|
||||
padding: 20px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
|
@ -70,6 +79,15 @@
|
|||
border-radius: 10px 0 0 10px;
|
||||
}
|
||||
|
||||
.object-viewer-side-control-overlay-top-right
|
||||
{
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-radius: 0 0 0 10px;
|
||||
height: 3%;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*so... If we don't do this... At least in chromium...
|
||||
|
|
|
@ -4,6 +4,8 @@ import { StatorContext } from '../statorobject';
|
|||
import "./objectviewer.css"
|
||||
import { getObjectViewerStator } from './objectviewercontroller';
|
||||
import { getBinderListStator } from './openbinders';
|
||||
import {ObjectPropertiesSidebar} from "./objectpropertiessidebar";
|
||||
import {getSidebarStator, useWatchSidebarStator} from "./sidebarstator";
|
||||
|
||||
export interface ObjectViewerProps {
|
||||
theBinderId: BinderConnectionId;
|
||||
|
@ -40,10 +42,12 @@ function VideoPlayerHead(props: ObjectViewerProps)
|
|||
|
||||
interface ViewerImplProps extends ObjectViewerProps {
|
||||
objId: string;
|
||||
resolutionPropogator: (res: string) => void;
|
||||
}
|
||||
|
||||
|
||||
function ImageViewer(props: ViewerImplProps) {
|
||||
const imageRef = useRef<HTMLImageElement>(null);
|
||||
const context = getBinderListStator(props.appContext).getBinder(props.theBinderId).context;
|
||||
const [imageUrl, setImageUrl] = useState<string>();
|
||||
|
||||
|
@ -66,12 +70,18 @@ function ImageViewer(props: ViewerImplProps) {
|
|||
controller.startOrContinueSlideshow();
|
||||
}
|
||||
*/
|
||||
|
||||
const width = imageRef.current.naturalWidth
|
||||
const height = imageRef.current.naturalHeight;
|
||||
|
||||
props.resolutionPropogator("" + width + "x" + height + "px");
|
||||
}
|
||||
|
||||
const wrappedURI = imageUrl ? "file:///" + imageUrl : undefined;
|
||||
|
||||
return (
|
||||
<img
|
||||
ref={imageRef}
|
||||
className="object-viewer-content-area"
|
||||
src={wrappedURI}
|
||||
onLoad={handleLoad}
|
||||
|
@ -117,20 +127,25 @@ function PlainTextViewer(props: ViewerImplProps)
|
|||
|
||||
function ObjectViewer(props: ObjectViewerProps)
|
||||
{
|
||||
const myRef = useRef<HTMLDivElement>(null)
|
||||
const myRef = useRef<HTMLDivElement>(null);
|
||||
//const myRef = useRef(null);
|
||||
const stator = getObjectViewerStator(props.appContext);
|
||||
const context = getBinderListStator(props.appContext).getBinder(props.theBinderId).context;
|
||||
const [mime, setMime] = useState<string>();
|
||||
const [imageResolution, setImageResolution] = useState<string>("");
|
||||
const [objId, setObjId] = useState<string>();
|
||||
const [doRender, setDoRender] = useState<boolean>(false);
|
||||
//const objectOpenStator = getObjectViewerOpenStator(props.appContext);
|
||||
const sidebarStator = useWatchSidebarStator(props.appContext);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setImageResolution("");//reset on object change
|
||||
if (mime) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
(async () => {
|
||||
if (!objId) {
|
||||
return;
|
||||
|
@ -151,6 +166,7 @@ function ObjectViewer(props: ObjectViewerProps)
|
|||
}, [objId]);
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const currentOpenObjectCont = context.currentOpenObject;
|
||||
|
||||
|
@ -281,7 +297,7 @@ function ObjectViewer(props: ObjectViewerProps)
|
|||
|
||||
|
||||
if (mainMimeType == "image") {
|
||||
myJsx = <ImageViewer {...props} objId={objId} />
|
||||
myJsx = <ImageViewer {...props} objId={objId} resolutionPropogator={setImageResolution}/>
|
||||
} else if (mainMimeType == "application") {
|
||||
let val = secondMimeType;
|
||||
if (secondMimeType == "octet-stream") {
|
||||
|
@ -306,30 +322,56 @@ function ObjectViewer(props: ObjectViewerProps)
|
|||
e.stopPropagation();
|
||||
};
|
||||
|
||||
const propertiesToggle = (e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
sidebarStator.toggleObjectProperties();
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className={`object-viewer ${checkboardBack ? 'object-viewer-checkerboard' : ''}`}
|
||||
onClick={clickHandler}
|
||||
onKeyDown={downHandler}
|
||||
tabIndex={1}
|
||||
<div className={"object-viewer-and-properties-container"}
|
||||
style={style}
|
||||
ref={myRef}
|
||||
>
|
||||
<div className={"object-viewer-container"}>
|
||||
{myJsx}
|
||||
<div className={`object-viewer ${checkboardBack ? 'object-viewer-checkerboard' : ''}`}
|
||||
onClick={clickHandler}
|
||||
onKeyDown={downHandler}
|
||||
tabIndex={1}
|
||||
ref={myRef}
|
||||
>
|
||||
<div className={"object-viewer-container"}>
|
||||
{myJsx}
|
||||
</div>
|
||||
<img className={'object-viewer-side-control-overlay ' +
|
||||
'object-viewer-controls-left-right-base-style ' +
|
||||
'object-viewer-side-control-overlay-left'}
|
||||
src={'static/rewind-button.svg'}
|
||||
draggable={false}
|
||||
onClick={backClickHandler}
|
||||
/>
|
||||
<img className={'object-viewer-side-control-overlay ' +
|
||||
'object-viewer-controls-left-right-base-style ' +
|
||||
'object-viewer-side-control-overlay-right'}
|
||||
src={'static/fast-forward-button.svg'}
|
||||
draggable={false}
|
||||
onClick={forwardClickHandler}
|
||||
/>
|
||||
|
||||
|
||||
<img className={'object-viewer-side-control-overlay object-viewer-side-control-overlay-top-right'}
|
||||
src={'static/kebaab.svg'}
|
||||
draggable={false}
|
||||
onClick={propertiesToggle}
|
||||
/>
|
||||
|
||||
</div>
|
||||
<img className={'object-viewer-side-control-overlay object-viewer-side-control-overlay-left'}
|
||||
src={'static/rewind-button.svg'}
|
||||
draggable={false}
|
||||
onClick={backClickHandler}
|
||||
/>
|
||||
<img className={'object-viewer-side-control-overlay object-viewer-side-control-overlay-right'}
|
||||
src={'static/fast-forward-button.svg'}
|
||||
draggable={false}
|
||||
onClick={forwardClickHandler}
|
||||
/>
|
||||
<ObjectPropertiesSidebar appContext={props.appContext}
|
||||
hide={sidebarStator.objectPropertiesHidden}
|
||||
objId={objId}
|
||||
imageResolution={imageResolution}
|
||||
binderContext={context}/>
|
||||
</div>
|
||||
);
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
export {ObjectViewer}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import {StatorContext, StatorObjectBase} from "../statorobject";
|
||||
|
||||
class SidebarStator extends StatorObjectBase {
|
||||
tagSidebarHidden = false;
|
||||
objectPropertiesHidden = true;
|
||||
|
||||
toggleTagSidebar() {
|
||||
this.tagSidebarHidden = !this.tagSidebarHidden;
|
||||
this.publishChanges();
|
||||
}
|
||||
|
||||
toggleObjectProperties() {
|
||||
this.objectPropertiesHidden = !this.objectPropertiesHidden;
|
||||
this.publishChanges();
|
||||
}
|
||||
}
|
||||
|
||||
const SidebarStatorKey = "SidebarStatorKeyKey";
|
||||
|
||||
function useWatchSidebarStator(appContext: StatorContext)
|
||||
{
|
||||
return appContext.useWatchStatorLegacy(SidebarStatorKey, new SidebarStator(appContext)) as SidebarStator;
|
||||
}
|
||||
|
||||
function getSidebarStator(appContext: StatorContext)
|
||||
{
|
||||
return appContext.getStatorLegacy(SidebarStatorKey, new SidebarStator(appContext)) as SidebarStator;
|
||||
}
|
||||
|
||||
|
||||
export { useWatchSidebarStator, getSidebarStator }
|
|
@ -10,39 +10,7 @@ import { BinderContextPair, getBinderListStator, useWatchBinderListStator } from
|
|||
import { reserved } from '../tagdatabase'
|
||||
import { BinderContext, ContextStatus } from './bindercontext'
|
||||
import { useWatchObservableValue } from '../observermanager'
|
||||
|
||||
class TagSidebarStator extends StatorObjectBase
|
||||
{
|
||||
hidden = false;
|
||||
|
||||
setHiddenness(h: boolean)
|
||||
{
|
||||
if(h == this.hidden)
|
||||
return;
|
||||
|
||||
this.hidden = h;
|
||||
this.publishChanges();
|
||||
}
|
||||
|
||||
toggle()
|
||||
{
|
||||
this.hidden = !this.hidden;
|
||||
this.publishChanges();
|
||||
}
|
||||
}
|
||||
|
||||
const TagSidebarStatorKey = "TagBarSideBarStatorKeyKey";
|
||||
|
||||
function useWatchTagSidebarStator(appContext: StatorContext)
|
||||
{
|
||||
return appContext.useWatchStatorLegacy(TagSidebarStatorKey, new TagSidebarStator(appContext)) as TagSidebarStator;
|
||||
}
|
||||
|
||||
function getTagSidebarStator(appContext: StatorContext)
|
||||
{
|
||||
return appContext.getStatorLegacy(TagSidebarStatorKey, new TagSidebarStator(appContext)) as TagSidebarStator;
|
||||
}
|
||||
|
||||
import { useWatchSidebarStator} from "./sidebarstator";
|
||||
|
||||
function Header(props: {context: BinderContext})
|
||||
{
|
||||
|
@ -152,7 +120,7 @@ function TagSidebar(props: TagSidebarProps)
|
|||
{
|
||||
//Fairly innefficient to re-render this errytime a selection changes...
|
||||
const binderListStator = useWatchBinderListStator(props.appContext);
|
||||
const tagSidebarStator = useWatchTagSidebarStator(props.appContext); //This is just for the show/hide button thing
|
||||
const tagSidebarStator = useWatchSidebarStator(props.appContext); //This is just for the show/hide button thing
|
||||
|
||||
if(binderListStator.getOpenBinderList().length == 0)
|
||||
{
|
||||
|
@ -169,7 +137,7 @@ function TagSidebar(props: TagSidebarProps)
|
|||
/>);
|
||||
});
|
||||
|
||||
const style = tagSidebarStator.hidden ? {display: "none"} : {};
|
||||
const style = tagSidebarStator.tagSidebarHidden ? {display: "none"} : {};
|
||||
|
||||
return <div style={style}>{tagSidebars}</div>;
|
||||
}
|
||||
|
@ -181,15 +149,15 @@ interface SidebarViewToggleButtonProps
|
|||
|
||||
function SidebarViewToggleButton(props: SidebarViewToggleButtonProps)
|
||||
{
|
||||
const sidebarStator = useWatchTagSidebarStator(props.appContext);//Cause this is for global stuff like show/hide
|
||||
const sidebarStator = useWatchSidebarStator(props.appContext);//Cause this is for global stuff like show/hide
|
||||
|
||||
|
||||
const handleClick = (e: any) =>
|
||||
{
|
||||
sidebarStator.toggle();
|
||||
sidebarStator.toggleTagSidebar();
|
||||
};
|
||||
|
||||
const imgSrc = sidebarStator.hidden ? "static/show-tag-sidebar.svg"
|
||||
const imgSrc = sidebarStator.tagSidebarHidden ? "static/show-tag-sidebar.svg"
|
||||
: "static/hide-tag-sidebar.svg";
|
||||
|
||||
return (<img
|
||||
|
|
|
@ -99,11 +99,14 @@ function setupCommonIPC(authKey, win) {
|
|||
ipcMain.handle("readFileModTime", async (event, path) => {
|
||||
return pathUtils.readFileModTime(path);
|
||||
});
|
||||
ipcMain.handle("readFileStats", async (event, path) => {
|
||||
return pathUtils.readFileStats(path);
|
||||
});
|
||||
ipcMain.handle("listPaths", async (event, path) => {
|
||||
return pathUtils.listPaths(path);
|
||||
});
|
||||
ipcMain.handle("copyPath", async (event, from, to) => {
|
||||
pathUtils.copyPath(from, to);
|
||||
await pathUtils.copyPath(from, to);
|
||||
});
|
||||
ipcMain.handle("renamePath", async (event, path, newPath) => {
|
||||
return pathUtils.renamePath(path, newPath);
|
||||
|
|
|
@ -41,6 +41,7 @@ interface MetaApi {
|
|||
readMime: (path: string) => Promise<string>;
|
||||
createDirectories: (path: string) => Promise<any>;
|
||||
readFileModTime: (path: string) => Promise<string>;
|
||||
readFileStats: (path: string) => Promise<any>; //todo make typed right
|
||||
listPaths: (path: string) => Promise<DirectoryListing | undefined>;
|
||||
copyPath: (from: string, to: string) => Promise<void>;
|
||||
renamePath: (path: string, newPath: string) => Promise<any>;
|
||||
|
|
|
@ -237,6 +237,18 @@ async function readFileModTime(path) {
|
|||
});
|
||||
}
|
||||
|
||||
async function readFileStats(path) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
try {
|
||||
const stats = await fsPromises.stat(path, {bigint: true});
|
||||
resolve(stats);
|
||||
} catch(e) {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
async function listPaths(path) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
let dir;
|
||||
|
@ -370,6 +382,7 @@ export {
|
|||
readMime,
|
||||
createDirectories,
|
||||
readFileModTime,
|
||||
readFileStats,
|
||||
listPaths,
|
||||
copyPath,
|
||||
renamePath,
|
||||
|
|
|
@ -83,6 +83,10 @@ contextBridge.exposeInMainWorld('metaApi',
|
|||
return ipcRenderer.invoke('readFileModTime', path);
|
||||
},
|
||||
|
||||
readFileStats: async (path) => {
|
||||
return ipcRenderer.invoke('readFileStats', path);
|
||||
},
|
||||
|
||||
listPaths: async (path) => {
|
||||
return ipcRenderer.invoke('listPaths', path);
|
||||
},
|
||||
|
|
|
@ -56,6 +56,11 @@ const b2bSumToObjectId = "b2bSumToObjectId";
|
|||
const tagsTable = "tags";
|
||||
const objectsTable = "objects";
|
||||
const mimesTableV1 = "mimesV1";
|
||||
//For historical reasons, the above are stored at the root but future stuff
|
||||
// should be stored in the object properties table
|
||||
const objectPropertiesV1 = "objectPropertiesV1";
|
||||
const objectPropertyImageWidth = "imageWidth";
|
||||
const objectPropertyImageHeight = "imageHeight";
|
||||
|
||||
const tagChildSortListTable = "tagChildSortList";
|
||||
|
||||
|
|
Loading…
Reference in New Issue