Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] make nodes collapsable #6

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This application uses [device_tree-rs](https://github.com/platform-system-interf

## Screenshot

![grafik](https://github.com/m0veax/dtvis/assets/2205193/f15c087a-81d9-4935-9126-4d788e68459b)
![screenshot](assets/screenshot.png)


## Local Development
Expand Down
8 changes: 7 additions & 1 deletion app/DTNode.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { memo, useState } from "react";
import { Handle, NodeProps, Position } from "reactflow";


const style = {
// wordWrap: "break-word",
whiteSpace: "pre-wrap" as "pre-wrap", // This is weird, TypoScripto...
Expand All @@ -11,6 +12,7 @@ const style = {
width: 150,
fontSize: 11,
fontFamily: "Fira Code",
display: "block",
};

const DTNode = ({
Expand All @@ -20,12 +22,16 @@ const DTNode = ({
sourcePosition = Position.Bottom
}: NodeProps) => {
const [hovered, setHovered] = useState(false);
const [collapsed, setCollapsed] = useState(false);

const hoverOn = () => setHovered(true);
const hoverOff = () => setHovered(false);
const collapseOn = () => setCollapsed(collapsed ? false : true);

const borderColor = hovered ? "#987987" : "#789789";
const borderStyle = hovered ? "dotted" : "solid";
const collapseText = collapsed ? "[+]" : "[-]";

return (
<>
<Handle
Expand All @@ -38,7 +44,7 @@ const DTNode = ({
onMouseEnter={hoverOn}
onMouseLeave={hoverOff}
>
{data?.label}
{data?.label} <button onClick={() => collapseOn()} >{collapseText}</button>
</div>
<Handle
type="source"
Expand Down
22 changes: 22 additions & 0 deletions app/StoreProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use client'
import { useRef } from 'react'
import { Provider } from 'react-redux'
import { makeStore, AppStore } from './store'
import { initializeState } from './features/tree/treeSlice'

export default function StoreProvider({
tree,
children
}: {
tree: any,
children: React.ReactNode
}) {
const storeRef = useRef<AppStore>()
if (!storeRef.current) {
// Create the store instance the first time this renders
storeRef.current = makeStore()
storeRef.current.dispatch(initializeState(tree))
}

return <Provider store={storeRef.current}>{children}</Provider>
}
68 changes: 68 additions & 0 deletions app/features/tree/tree.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React, { useState } from 'react';

import { useAppSelector, useAppDispatch } from '../../hooks';
import {
decrement,
increment,
incrementByAmount,
incrementAsync,
incrementIfOdd,
selectCount,
} from './treeSlice';
import styles from './Counter.module.css';

export function Counter() {
const count = useAppSelector(selectCount);
const dispatch = useAppDispatch();
const [incrementAmount, setIncrementAmount] = useState('2');

const incrementValue = Number(incrementAmount) || 0;

return (
<div>
<div className={styles.row}>
<button
className={styles.button}
aria-label="Decrement value"
onClick={() => dispatch(decrement())}
>
-
</button>
<span className={styles.value}>{count}</span>
<button
className={styles.button}
aria-label="Increment value"
onClick={() => dispatch(increment())}
>
+
</button>
</div>
<div className={styles.row}>
<input
className={styles.textbox}
aria-label="Set increment amount"
value={incrementAmount}
onChange={(e) => setIncrementAmount(e.target.value)}
/>
<button
className={styles.button}
onClick={() => dispatch(incrementByAmount(incrementValue))}
>
Add Amount
</button>
<button
className={styles.asyncButton}
onClick={() => dispatch(incrementAsync(incrementValue))}
>
Add Async
</button>
<button
className={styles.button}
onClick={() => dispatch(incrementIfOdd(incrementValue))}
>
Add If Odd
</button>
</div>
</div>
);
}
7 changes: 7 additions & 0 deletions app/features/tree/treeApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

// A mock function to mimic making an async request for data
export function fetchTree(amount = 1) {
return new Promise<{ data: any }>((resolve) =>
setTimeout(() => resolve({ data: amount }), 500)
);
}
85 changes: 85 additions & 0 deletions app/features/tree/treeSlice.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RootState, AppThunk } from '../../store';
import { fetchTree } from './treeApi';


export interface TreeState {
value: number;
status: 'idle' | 'loading' | 'failed';
}

const initialState: TreeState = {
value: 0,
status: 'idle',
};

// The function below is called a thunk and allows us to perform async logic. It
// can be dispatched like a regular action: `dispatch(incrementAsync(10))`. This
// will call the thunk with the `dispatch` function as the first argument. Async
// code can then be executed and other actions can be dispatched. Thunks are
// typically used to make async requests.
export const incrementAsync = createAsyncThunk(
'tree/fetchCount',
async (amount: number) => {
const response = await fetchCount(amount);
// The value we return becomes the `fulfilled` action payload
return response.data;
}
);

export const treeSlice = createSlice({
name: 'tree',
initialState,
// The `reducers` field lets us define reducers and generate associated actions
reducers: {
increment: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
// Use the PayloadAction type to declare the contents of `action.payload`
incrementByAmount: (state, action: PayloadAction<number>) => {
state.value += action.payload;
},
},
// The `extraReducers` field lets the slice handle actions defined elsewhere,
// including actions generated by createAsyncThunk or in other slices.
extraReducers: (builder) => {
builder
.addCase(incrementAsync.pending, (state) => {
state.status = 'loading';
})
.addCase(incrementAsync.fulfilled, (state, action) => {
state.status = 'idle';
state.value += action.payload;
})
.addCase(incrementAsync.rejected, (state) => {
state.status = 'failed';
});
},
});

export const { increment, decrement, incrementByAmount } = treeSlice.actions;

// The function below is called a selector and allows us to select a value from
// the state. Selectors can also be defined inline where they're used instead of
// in the slice file. For example: `useSelector((state: RootState) => state.tree.value)`
export const selectCount = (state: RootState) => state.tree.value;

// We can also write thunks by hand, which may contain both sync and async logic.
// Here's an example of conditionally dispatching actions based on current state.
export const incrementIfOdd =
(amount: number): AppThunk =>
(dispatch, getState) => {
const currentValue = selectCount(getState());
if (currentValue % 2 === 1) {
dispatch(incrementByAmount(amount));
}
};

export default treeSlice.reducer;
8 changes: 8 additions & 0 deletions app/hooks.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { useDispatch, useSelector, useStore } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import type { RootState, AppDispatch, AppStore } from './store'

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch: () => AppDispatch = useDispatch
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
export const useAppStore: () => AppStore = useStore
1 change: 0 additions & 1 deletion app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { Metadata } from 'next'
import { Inter } from 'next/font/google'
import 'reactflow/dist/style.css';
import './globals.css'

const inter = Inter({ subsets: ['latin'] })

export const metadata: Metadata = {
Expand Down
20 changes: 20 additions & 0 deletions app/store.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
import treeReducer from './features/tree/treeSlice';

export const makeStore = () => {
return configureStore({
reducer: {}
})
}

// Infer the type of makeStore
export type AppStore = ReturnType<typeof makeStore>
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
Binary file added assets/screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 8 additions & 8 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"lint": "next lint"
},
"dependencies": {
"@reduxjs/toolkit": "^1.9.5",
"@reduxjs/toolkit": "^1.9.7",
"@types/node": "20.5.9",
"@types/react": "18.2.21",
"@types/react-dom": "18.2.7",
Expand All @@ -18,7 +18,7 @@
"next": "^14.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-redux": "^8.1.2",
"react-redux": "^8.1.3",
"reactflow": "^11.8.3",
"redux": "^4.2.1",
"typescript": "5.2.2",
Expand Down