Skip to content

Commit

Permalink
Add reactivity with [email protected]
Browse files Browse the repository at this point in the history
  • Loading branch information
thejohnhoffer committed Oct 8, 2024
1 parent 4a7a33c commit 4371614
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 18 deletions.
3 changes: 2 additions & 1 deletion src/components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Main } from "./content";
import type { OptSW } from "./waypoint/content";
import type { Waypoint as WaypointType } from "../lib/exhibit";
import type { HashContext } from "../lib/hashUtil";
import type { Loader } from "../lib/viv";
import type { Exhibit } from "../lib/exhibit";
import type { ConfigGroup } from "../lib/config";
import type { ConfigWaypoint } from "../lib/config";
Expand All @@ -15,7 +16,7 @@ import type { ConfigSourceChannel } from "../lib/config";

type Props = HashContext & {
in_f: string;
loader: any;
loader: Loader;
exhibit: Exhibit;
handle: Handle.Dir;
title: string;
Expand Down
2 changes: 1 addition & 1 deletion src/components/vivView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@ const VivView = (props: Props) => {

const viewerProps = {
...{
loader,
...shape,
loader: loader.data,
...(settings as any),
},
};
Expand Down
150 changes: 145 additions & 5 deletions src/lib/config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { reactive } from '@arrow-js/core';

Check failure on line 1 in src/lib/config.ts

View workflow job for this annotation

GitHub Actions / build

Cannot find module '@arrow-js/core' or its corresponding type declarations.

import type { Loader } from './viv';

type ExpandedState = {
Expanded: boolean;
};
type GroupState = ExpandedState;
type GroupChannelState = ExpandedState;
type WaypointState = ExpandedState;

type ID = { ID: string; };
type UUID = { UUID: string; };
type NameProperty = { Name: string; };
type GroupProperties = NameProperty;
Expand All @@ -19,12 +24,14 @@ type WaypointProperties = NameProperty & {
Content: string;
};

type Associations<T extends string> = Record<T, UUID>;
type SourceChannelAssociations = Associations<
'SourceDataType' | 'SourceImage'
type SourceChannelAssociations = Record<
'SourceDataType', ID
> & Record<
'SourceImage', UUID
>;
type GroupChannelAssociations = Associations<
'SourceChannel' | 'Group'
type GroupChannelAssociations = Record<
'SourceChannel' | 'Group',
UUID
>;

export type ConfigSourceChannel = UUID & {
Expand All @@ -44,3 +51,136 @@ export type ConfigWaypoint = UUID & {
State: WaypointState;
Properties: WaypointProperties;
};
interface ExtractChannels {
(loader: Loader): {
SourceChannels: ConfigSourceChannel[];
GroupChannels: ConfigGroupChannel[];
Groups: ConfigGroup[];
}
}

const asID = (k: string): ID => ({ ID: k });
const asUUID = (k: string): UUID => ({ UUID: k });

const extractChannels: ExtractChannels = (loader) => {
const { Channels, Type } = loader.metadata.Pixels;
const SourceChannels = Channels.map(
(channel, index) => ({
UUID: crypto.randomUUID(),
Properties: {
Name: channel.Name,
SourceIndex: index,
},
Associations: {
SourceDataType: asID(Type),
SourceImage: asUUID('TODO')
}
})
);
const group_size = 4;
const Groups = [...Array(Math.ceil(
SourceChannels.length / group_size
)).keys()].map(
index => ({
UUID: crypto.randomUUID(),
State: { Expanded: false },
Properties: {
Name: `Group ${index}`
}
})
)
const GroupChannels = SourceChannels.map(
(channel, index) => ({
UUID: crypto.randomUUID(),
State: { Expanded: false },
Properties: {
LowerRange: 0, UpperRange: 65535
},
Associations: {
SourceChannel: asUUID(channel.UUID),
Group: asUUID(Groups[
Math.floor(index / group_size)
].UUID)
}
})
)
return {
SourceChannels,
GroupChannels,
Groups
}
}

const mutableConfigArrayItem = (
item, namespace, array, index
) => {
return [
namespace, new Proxy(
item[namespace], {
has(target, k) {
if (k == '$on')
return true;
return k in target;
},
get(target, k) {
if (k == '$on')
return () => {};
return target[k];
},
set(target, k, v) {
if (k in target) {
target[k] = v;
array.splice(index, 1, item);
}
return true;
}
}
)
];
}

const mutableConfigArray = (
state_array, set_state,
) => {
const methods = [
'pop', 'push', 'shift', 'unshift',
'splice', 'sort', 'reverse'
];
const namespaces = [
/*'State', */'Properties', 'Associations'
];
return new Proxy(state_array, {
get(_, key, receiver) {
const item = state_array[key];
if (methods.includes(String(key))) {
// Let specific array methods set the array state
return new Proxy(item, {
apply(fn, _, ...args) {
const new_state = [...state_array];
const output = fn.apply(new_state, args);
set_state(new_state);
return output;
}
});
}
if (typeof key == 'symbol') {
return item;
}
const index = parseInt(key as string);
if (isNaN(index) || typeof item != 'object') {
return item;
}
// Let specific properties be modified
const entries = namespaces.map(
namespace => mutableConfigArrayItem(
item, namespace, receiver, index
)
);
return {
...item, ...Object.fromEntries(entries)
}
}
});
}

export { extractChannels, mutableConfigArray }
7 changes: 3 additions & 4 deletions src/lib/filesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import {
loadOmeTiff,
} from "@hms-dbmi/viv";

import type { Loader } from './viv';

type ListDirIn = {
handle: Handle.Dir,
}
Expand All @@ -25,11 +27,8 @@ type LoaderIn = {
in_f: string,
handle: Handle.Dir
}
type LoaderOut = {
data: LoaderPlane[]
}
interface ToLoader {
(i: LoaderIn): Promise<LoaderOut>;
(i: LoaderIn): Promise<Loader>;
}
export type Selection = {
t: number,
Expand Down
52 changes: 50 additions & 2 deletions src/lib/viv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,56 @@ type Settings = {
colors: Color[];
};

type Channel = {
ID: string;
SamplesPerPixel: number;
Name: string;
}

type TiffDatum = {
IFD: number;
PlaneCount: number;
FirstT: number;
FirstC: number;
FirstZ: number;
UUID: {
FileName: string;
};
}

type Pixels = {
Channels: Channel[];
ID: string;
DimensionOrder: string;
Type: string;
SizeT: number;
SizeC: number;
SizeZ: number;
SizeY: number;
SizeX: number;
PhysicalSizeX: number;
PhysicalSizeY: number;
PhysicalSizeXUnit: string;
PhysicalSizeYUnit: string;
PhysicalSizeZUnit: string;
BigEndian: boolean;
TiffData: TiffDatum[];
}

type Metadata = {
ID: string;
AquisitionDate: string;
Description: string;
Pixels: Pixels;
}

export type Loader = {
data: any[];
metadata: any;
}

export type Config = {
toSettings: (h: HashState, l?: any, g?: any) => Settings;
toSettings: (h: HashState, l?: Loader, g?: any) => Settings;
};

const toDefaultSettings = (n) => {
Expand Down Expand Up @@ -63,7 +111,7 @@ const toSettings = (opts) => {
const channels = group?.channels || [];
// Defaults
if (!loader) return toDefaultSettings(3);
const full_level = loader[0];
const full_level = loader.data[0];
if (!loader) return toDefaultSettings(3);
const { labels, shape } = full_level;
const c_idx = labels.indexOf("c");
Expand Down
26 changes: 21 additions & 5 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { useState, useEffect } from "react";
import { useHash } from "./lib/hashUtil";
import { hasFileSystemAccess, toDir, toLoader } from "./lib/filesystem";
import { isOpts, validate } from './lib/validate';
import {
extractChannels, mutableConfigArray
} from './lib/config';
import { Upload } from './components/upload';
import { readConfig } from "./lib/exhibit";
import { Index } from "./components";
Expand Down Expand Up @@ -57,6 +60,16 @@ const Content = (props: Props) => {
const [url, setUrl] = useState(window.location.href);
const hashContext = useHash(url, exhibit.stories);
const [handle, setHandle] = useState(null);
const [sourceChannels, setSourceChannels] = useState([]);
const [groupChannels, setGroupChannels] = useState([]);
const [groups, setGroups] = useState([]);
const configState = {
configGroups: groups,
configGroupChannels: mutableConfigArray(
groupChannels, setGroupChannels
),
configSourceChannels: sourceChannels
};
const [loader, setLoader] = useState(null);
const [fileName, setFileName] = useState('');
// Create ome-tiff loader
Expand All @@ -82,7 +95,13 @@ const Content = (props: Props) => {
(async () => {
if (handle === null) return;
const loader = await toLoader({ handle, in_f });
setLoader(loader.data);
const {
SourceChannels, GroupChannels, Groups
} = extractChannels(loader);
setSourceChannels(SourceChannels);
setGroupChannels(GroupChannels);
setGroups(Groups);
setLoader(loader);
setFileName(in_f);
})();
}
Expand All @@ -93,15 +112,12 @@ const Content = (props: Props) => {
});
}, [])
const { marker_names, title, configWaypoints } = props;
const {
configGroups, configGroupChannels, configSourceChannels
} = props;

// Actual image viewer
const imager = loader === null ? '' : (
<Full>
<Index {...{
configGroups, configGroupChannels, configSourceChannels,
...configState,
title, configWaypoints, exhibit, setExhibit, loader,
marker_names, in_f: fileName, handle, ...hashContext
}} />
Expand Down

0 comments on commit 4371614

Please sign in to comment.