Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 75 additions & 0 deletions kalman_gs/node_project/src/common/background-images.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
export type BackgroundImage = {
name: string;
dataUrl: string;
};

const BACKGROUND_IMAGES_KEY = 'panel-manager-background-images';
const LEGACY_BACKGROUND_IMAGE_KEY = 'panel-manager-background-image';
const LEGACY_BACKGROUND_IMAGE_NAME_KEY = 'panel-manager-background-image-name';

function isBackgroundImage(value: unknown): value is BackgroundImage {
return (
typeof value === 'object' &&
value !== null &&
typeof (value as BackgroundImage).name === 'string' &&
typeof (value as BackgroundImage).dataUrl === 'string'
);
}

export function getBackgroundImages(): BackgroundImage[] {
const storedImages = localStorage.getItem(BACKGROUND_IMAGES_KEY);

if (storedImages) {
try {
const parsedImages = JSON.parse(storedImages);
if (Array.isArray(parsedImages)) {
return parsedImages.filter(isBackgroundImage);
}
} catch {
return [];
}
}

const legacyImage = localStorage.getItem(LEGACY_BACKGROUND_IMAGE_KEY);
const legacyImageName = localStorage.getItem(LEGACY_BACKGROUND_IMAGE_NAME_KEY);
if (!legacyImage || !legacyImageName) {
return [];
}

return [
{
name: legacyImageName,
dataUrl: legacyImage
}
];
}

export function setBackgroundImages(images: BackgroundImage[]) {
if (images.length > 0) {
localStorage.setItem(BACKGROUND_IMAGES_KEY, JSON.stringify(images));
} else {
localStorage.removeItem(BACKGROUND_IMAGES_KEY);
}

localStorage.removeItem(LEGACY_BACKGROUND_IMAGE_KEY);
localStorage.removeItem(LEGACY_BACKGROUND_IMAGE_NAME_KEY);
}

export function getBackgroundImagesLabel(images = getBackgroundImages()) {
if (images.length === 0) {
return null;
}
if (images.length === 1) {
return images[0].name;
}
return `${images.length} images`;
}

export function getRandomBackgroundImage() {
const images = getBackgroundImages();
if (images.length === 0) {
return null;
}

return images[Math.floor(Math.random() * images.length)];
}
67 changes: 67 additions & 0 deletions kalman_gs/node_project/src/components/file-input.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
.file-input-container {
display: flex;
align-items: stretch;
margin: 5px;
height: calc(1em + 10px);
}

.file-hidden {
display: none;
}

.file-main,
.file-clear {
background-color: var(--dark-background);
border: none;
color: var(--strong-foreground);
height: 100%;
display: flex;
align-items: center;
}

.file-main {
flex: 1;
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;

border-right: 1px solid var(--background);

padding: 0 10px;
min-width: 0;
}

.file-main-inner {
display: flex;
align-items: center;
gap: 8px;
width: 100%;
min-width: 0;
}

.file-main-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.file-clear {
width: 38px;
justify-content: center;

border-top-right-radius: 5px;
border-bottom-right-radius: 5px;

padding: 0;
}

.file-main:hover:not(:disabled),
.file-clear:hover:not(:disabled) {
filter: brightness(1.08);
}

.file-main:disabled,
.file-clear:disabled {
opacity: 0.45;
cursor: not-allowed;
filter: none;
}
120 changes: 120 additions & 0 deletions kalman_gs/node_project/src/components/file-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import styles from './file-input.module.css';

import Button from './button';
import { faXmark, faUpload } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Component, createRef } from 'react';

type Props = {
accept?: string;
multiple?: boolean;

emptyLabel?: string | null;
onChange?: (files: FileList | null) => void;
onClear?: () => void;

className?: string;

canClearEmpty?: boolean;
autoFocus?: boolean;
disabled?: boolean;
[key: string]: any;
};

type State = {
label: string;
hasFiles: boolean;
};

export default class FileInput extends Component<Props, State> {
private ref = createRef<HTMLInputElement>();

state: State = {
label: this.emptyLabel,
hasFiles: false
};

private get emptyLabel() {
return this.props.emptyLabel ?? 'Choose image...';
}

componentDidUpdate(prevProps: Props) {
if (prevProps.emptyLabel !== this.props.emptyLabel && !this.state.hasFiles) {
this.setState({ label: this.emptyLabel });
}
}

private updateLabelFromInput() {
const files = this.ref.current?.files;
if (!files || files.length === 0) {
this.setState({
label: this.emptyLabel,
hasFiles: false
});
return;
}

const label = files.length === 1 ? files[0].name : `${files.length} files`;
this.setState({ label, hasFiles: true });
}

openPicker() {
if (this.props.disabled) return;
this.ref.current?.click();
}

clear() {
if (!this.ref.current) return;
this.ref.current.value = '';
this.updateLabelFromInput();
this.props.onChange?.(null);
this.props.onClear?.();
}

render() {
const { accept, multiple, onChange, className, autoFocus, disabled, emptyLabel, onClear, ...props } = this.props;

const { label, hasFiles } = this.state;

return (
<div className={`${styles['file-input-container']}${className ? ` ${className}` : ''}`} {...props}>
{/* hidden real input */}
<input
ref={this.ref}
type='file'
accept={accept}
multiple={multiple}
className={styles['file-hidden']}
autoFocus={autoFocus}
onChange={(e) => {
this.updateLabelFromInput();
onChange?.(e.currentTarget.files);
}}
/>

{/* main button */}
<Button
className={styles['file-main']}
tooltip={hasFiles ? 'Change file' : 'Choose file'}
onClick={() => this.openPicker()}
disabled={disabled}
>
<span className={styles['file-main-inner']}>
<FontAwesomeIcon icon={faUpload} />
<span className={styles['file-main-text']}>{label}</span>
</span>
</Button>

{/* clear button (X) */}
<Button
className={styles['file-clear']}
tooltip='Usuń'
onClick={() => this.clear()}
disabled={(disabled || !hasFiles) && !this.props.canClearEmpty}
>
<FontAwesomeIcon icon={faXmark} />
</Button>
</div>
);
}
}
2 changes: 1 addition & 1 deletion kalman_gs/node_project/src/components/navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function Navbar() {

const rerenderNavbarAndPanelManager = useCallback(() => {
setRerenderCounter(rerenderCounter + 1);
window.dispatchEvent(new CustomEvent('rerender-panel-manager'));
window.dispatchEvent(new CustomEvent('panel-manager-rerender'));
}, [rerenderCounter]);

return (
Expand Down
14 changes: 14 additions & 0 deletions kalman_gs/node_project/src/components/panel-manager.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,18 @@
overflow: hidden;
flex: 1;
background-color: var(--background);
position: relative;
}

.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
opacity: 0.03;
pointer-events: none;
user-select: none;
z-index: 1;
}
21 changes: 17 additions & 4 deletions kalman_gs/node_project/src/components/panel-manager.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import styles from './panel-manager.module.css';

import { getRandomBackgroundImage } from '../common/background-images';
import {
HorizontalPanelLayout,
LeafPanelLayout,
Expand All @@ -19,6 +20,8 @@ function clamp(value: number, min: number, max: number) {
return Math.min(Math.max(value, min), max);
}

const BACKGROUND_IMAGE_DISABLED_PANELS = new Set<PanelID>(['map', 'imu']);

export default function PanelManager() {
const [rerenderCounter, setRerenderCounter] = useState(0);

Expand All @@ -34,9 +37,9 @@ export default function PanelManager() {

// Expose a custom event that allows to re-render the panel manager.
useEffect(() => {
window.addEventListener('rerender-panel-manager', rerender);
window.addEventListener('panel-manager-rerender', rerender);
return () => {
window.removeEventListener('rerender-panel-manager', rerender);
window.removeEventListener('panel-manager-rerender', rerender);
};
}, [rerender]);

Expand Down Expand Up @@ -178,10 +181,21 @@ export default function PanelManager() {
</div>
);
} else {
const panel = panelInfos[(layout as LeafPanelLayout).panel];
const panelId = (layout as LeafPanelLayout).panel;
const panel = panelInfos[panelId];
const panelRef = createRef(); // TODO: Is this the right way? We can't call useRef in a loop.
const backgroundImage = BACKGROUND_IMAGE_DISABLED_PANELS.has(panelId) ? null : getRandomBackgroundImage();
return (
<>
{backgroundImage && (
<img
src={backgroundImage.dataUrl}
alt=''
className={styles['background-image']}
aria-hidden='true'
draggable='false'
/>
)}
<div className={styles['panel-header']}>
<div className={styles['panel-selector']}>
<Dropdown
Expand Down Expand Up @@ -306,7 +320,6 @@ export default function PanelManager() {
</div>
</div>
<div className={styles['panel-container']}>
{/* <panel.Component props={(layout as LeafPanelLayout).props} /> */}
{/* If component is a class component, assign a ref */}
{panel.Component.prototype instanceof Component ? (
<panel.Component ref={panelRef} props={(layout as LeafPanelLayout).props} />
Expand Down
6 changes: 6 additions & 0 deletions kalman_gs/node_project/src/components/settings.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,12 @@
font-size: 18px;
}

.background-selector{
display: flex;
flex-direction: column;
margin-top: -5px;
}

.no-search-results {
height: 100%;
width: 100%;
Expand Down
Loading
Loading