Skip to content

Commit

Permalink
Add GUI file dialog component (#199)
Browse files Browse the repository at this point in the history
* Add text divider and space for file browser

* Add initial file browser component

* Make file browser functional and add warning

* Support dragging onto custom file browser

* Fix file browser dragging bug

* Add explanation to big file warning

* More informative oversize file warning

* Bump GUI version to match OFRAK version

* Remove file button but make header clickable

* Make big div clickable instead of just header

* Prevent dropdown and butotn clicks from bubbling

* Update CHANGELOG

* Add additional file browser text

* Fix styling

---------

Co-authored-by: Edward Larson <[email protected]>
  • Loading branch information
rbs-jacob and EdwardLarson authored Feb 3, 2023
1 parent b602045 commit d0d58af
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 22 deletions.
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ofrak-app",
"version": "0.6.0",
"version": "2.1.2",
"description": "The graphical front-end for OFRAK.",
"homepage": "https://ofrak.com",
"private": true,
Expand Down
86 changes: 86 additions & 0 deletions frontend/src/FileBrowser.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
<style>
button {
padding-top: 0.5em;
padding-bottom: 0.5em;
padding-left: 1em;
padding-right: 1em;
margin-right: 2ch;
}
button:hover,
button:focus {
outline: none;
box-shadow: inset 1px 1px 0 currentColor, inset -1px -1px 0 currentColor;
}
button:active {
box-shadow: inset 2px 2px 0 currentColor, inset -2px -2px 0 currentColor;
}
.filelabel {
cursor: pointer;
font-family: inherit;
font-size: inherit;
color: inherit;
background: inherit;
border-color: inherit;
box-shadow: none;
user-select: none;
line-height: inherit;
}
.filelabel span {
width: 100%;
margin-left: 2ch;
background: inherit;
color: inherit;
/* border-bottom: 1px solid var(--main-fg-color); */
}
input[type="file"] {
display: none;
}
label {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: space-evenly;
align-items: baseline;
align-content: center;
white-space: nowrap;
}
</style>

<script>
export let files, input;
let dragging = false;
</script>

<div
on:dragover|preventDefault="{() => (dragging = true)}"
on:dragleave|preventDefault="{() => (dragging = false)}"
on:drop|preventDefault="{(e) => {
files = e.dataTransfer.files;
dragging = false;
}}"
>
{#if !dragging}
<label class="filelabel">
<slot />
<input type="file" bind:this="{input}" bind:files="{files}" />
<span>
<button on:click="{() => input.click()}"> Browse... </button>
{#if files}
{Array.from(files)
.map((f) => f?.name)
.join(", ")}
{:else}
No file selected.
{/if}
</span>
</label>
{:else}
Drop the files to upload.
{/if}
</div>
115 changes: 96 additions & 19 deletions frontend/src/StartView.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
font-weight: bold;
color: inherit;
font-size: xxx-large;
line-height: 1;
margin: 0;
max-width: 50%;
text-align: center;
}
form {
max-width: 50%;
}
.center {
Expand Down Expand Up @@ -55,31 +63,70 @@
width: calc(100% - 6em);
height: calc(100% - 6em);
}
input[type="file"] {
display: none;
}
.maxwidth {
max-width: 50%;
width: 50%;
margin: 1em 0;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.clickable {
cursor: pointer;
}
.underline {
text-decoration: underline;
}
</style>

<script>
import Animals from "./Animals.svelte";
import LoadingAnimation from "./LoadingAnimation.svelte";
import LoadingText from "./LoadingText.svelte";
import TextDivider from "./TextDivider.svelte";
import { animals } from "./animals.js";
import { selected } from "./stores.js";
import { remote_model_to_resource } from "./ofrak/remote_resource";
import { onMount } from "svelte";
import { numBytesToQuantity } from "./helpers";
export let rootResourceLoadPromise,
showRootResource,
resources,
rootResource,
resourceNodeDataMap;
resourceNodeDataMap,
browsedFiles,
fileinput;
let dragging = false,
selectedPreExistingRoot = null,
preExistingRootsPromise = new Promise(() => {}),
tryHash = !!window.location.hash;
let mouseX, selectedAnimal;
const warnFileSize = 250 * 1024 * 1024;
async function createRootResource(f) {
if (
f.size > warnFileSize &&
!window.confirm(
`Loading a large file (${numBytesToQuantity(
f.size
)} > ${numBytesToQuantity(warnFileSize)}) may be slow. Are you sure?`
)
) {
showRootResource = false;
return;
}
const rootModel = await fetch(`/create_root_resource?name=${f.name}`, {
method: "POST",
body: await f.arrayBuffer(),
Expand Down Expand Up @@ -114,6 +161,12 @@
}
}
$: if (browsedFiles && browsedFiles.length > 0) {
showRootResource = true;
const f = browsedFiles[0];
rootResourceLoadPromise = createRootResource(f);
}
async function getResourcesFromHash(resourceId) {
const root = await fetch(`/${resourceId}/get_root`).then((r) => {
if (!r.ok) {
Expand Down Expand Up @@ -168,8 +221,9 @@
</script>

{#if !tryHash}
<!-- svelte-ignore a11y-click-events-have-key-events -->
<div
class="center {dragging ? 'dragging' : ''}"
class="center clickable {dragging ? 'dragging' : ''}"
on:dragover|preventDefault="{(e) => {
dragging = true;
mouseX = e.clientX;
Expand All @@ -178,36 +232,59 @@
on:drop|preventDefault="{handleDrop}"
on:mousemove="{(e) => (mouseX = e.clientX)}"
on:mouseleave="{() => (mouseX = undefined)}"
on:click="{() => fileinput.click()}"
style:border-color="{animals[selectedAnimal]?.color ||
"var(--main-fg-color)"}"
style:color="{animals[selectedAnimal]?.color || "var(--main-fg-color)"}"
>
{#if !dragging}
<h1>Drag in a file to analyze</h1>
<p style:margin-bottom="0">Click anwyhere to browse your computer</p>
{:else}
<h1>Drop the file!</h1>
{/if}

<input type="file" bind:this="{fileinput}" bind:files="{browsedFiles}" />

<div class="maxwidth">
<TextDivider
color="{animals[selectedAnimal]?.color || 'var(--main-fg-color)'}"
>
OR
</TextDivider>
</div>

{#await preExistingRootsPromise}
<LoadingText />
{:then preExistingRootResources}
<form on:submit|preventDefault="{choosePreExistingRoot}">
<select bind:value="{selectedPreExistingRoot}">
<option value="{null}">None</option>
{#each preExistingRootResources as preExistingRoot}
<option value="{preExistingRoot}">
{preExistingRoot.id} &ndash;
{#if preExistingRoot.caption}
{preExistingRoot.caption}
{:else}
Untagged
{/if}
</option>
{/each}
</select>

<button disabled="{!selectedPreExistingRoot}" type="submit">Go!</button>
</form>
{#if preExistingRootsPromise && preExistingRootsPromise.length > 0}
<form on:submit|preventDefault="{choosePreExistingRoot}">
<select
on:click|stopPropagation="{() => undefined}"
bind:value="{selectedPreExistingRoot}"
>
<option value="{null}">Open existing resource</option>
{#each preExistingRootResources as preExistingRoot}
<option value="{preExistingRoot}">
{preExistingRoot.id} &ndash;
{#if preExistingRoot.caption}
{preExistingRoot.caption}
{:else}
Untagged
{/if}
</option>
{/each}
</select>

<button
on:click|stopPropagation="{() => undefined}"
disabled="{!selectedPreExistingRoot}"
type="submit">Go!</button
>
</form>
{:else}
No resources loaded yet.
{/if}
{:catch}
<p>Failed to get any pre-existing root resources!</p>
<p>The back end server may be down.</p>
Expand Down
32 changes: 32 additions & 0 deletions frontend/src/TextDivider.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<style>
div {
width: 100%;
margin: 2em 0;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
align-items: center;
justify-content: center;
}
hr {
flex-grow: 1;
border: none;
color: inherit;
border-bottom: 1px solid currentColor;
}
span {
padding: 0 2ch;
}
</style>

<script>
export let color;
</script>

<div>
<hr style:color="{color}" />
<span><slot /></span>
<hr style:color="{color}" />
</div>
20 changes: 19 additions & 1 deletion frontend/src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,31 @@ export function buf2hex(buffer, joinchar) {
.join(joinchar ? joinchar : "");
}

/**
/***
* Asynchronously sleep for a certain number of milliseconds.
*/
export async function sleep(ms) {
await new Promise((resolve) => setTimeout(resolve, ms));
}

/**
* Turn a number of bytes into a text-based file size
*/
export function numBytesToQuantity(bytes) {
if (bytes < 1024) {
return `${bytes}B`;
} else if (bytes < 1024 * 1024) {
return `${(bytes / 1024).toFixed(2)}KB`;
} else if (bytes < 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
} else if (bytes < 1024 * 1024 * 1024 * 1024) {
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)}GB`;
} else {
// TODO if necessary
return `${bytes}B`;
}
}

/***
* Evaluate an input arithmetic string consisting of (possibly hex) numbers
* and the given binary operators using the Shunting Yard algorithm
Expand Down
3 changes: 2 additions & 1 deletion ofrak_core/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- Fix bug in `SegmentInjectorModifier` that resulted in deleting more resources than necessary [#200](https://github.com/redballoonsecurity/ofrak/pull/200)

### Added
- Replace unofficial p7zip with official 7zip package.
- Replace unofficial p7zip with official 7zip package
- File browser dialog in the GUI
- Area in the GUI to jump to a given data offset
- GUI command line now has a flag to not automatically open the browser

Expand Down

0 comments on commit d0d58af

Please sign in to comment.