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

Implement Drawer Component in Hyperview #83

Open
AtlantisPleb opened this issue Feb 13, 2025 · 3 comments
Open

Implement Drawer Component in Hyperview #83

AtlantisPleb opened this issue Feb 13, 2025 · 3 comments

Comments

@AtlantisPleb
Copy link
Contributor

Overview

Need to implement a custom drawer component for Hyperview to enable drawer functionality in HXML screens.

Implementation Details

1. Create New Custom Component

Create new file: app/hyperview/components/Drawer/Drawer.tsx

import Hyperview from "hyperview"
import React from "react"
import { Drawer } from "react-native-drawer-layout"
import { View } from "react-native"

interface Props {
  element: Element
  stylesheets: any
  onUpdate: (opts: any) => void
  options: any
}

export class HvDrawer extends React.PureComponent<Props> {
  static namespaceURI = 'https://openagents.com/hyperview-local'
  static localName = 'drawer'

  render() {
    const props = Hyperview.createProps(
      this.props.element,
      this.props.stylesheets,
      this.props.options,
    )

    // Extract drawer-specific props
    const drawerOpen = props.open === 'true'
    const drawerType = props.drawerType || 'slide'
    
    // Get drawer content and main content from children
    const children = Array.from(this.props.element.children)
    const drawerContent = children.find(child => child.getAttribute('role') === 'drawer-content')
    const mainContent = children.find(child => child.getAttribute('role') === 'main-content')

    return (
      <Drawer
        open={drawerOpen}
        onOpen={() => {
          // Handle open behavior
          if (props.onOpen) {
            this.props.onUpdate({
              type: 'behavior',
              name: props.onOpen,
              trigger: 'on-drawer-open',
            })
          }
        }}
        onClose={() => {
          // Handle close behavior
          if (props.onClose) {
            this.props.onUpdate({
              type: 'behavior',
              name: props.onClose,
              trigger: 'on-drawer-close',
            })
          }
        }}
        drawerType={drawerType}
        renderDrawerContent={() => (
          <View>
            {drawerContent && Hyperview.renderChildren({
              element: drawerContent,
              stylesheets: this.props.stylesheets,
              onUpdate: this.props.onUpdate,
              options: this.props.options,
            })}
          </View>
        )}
      >
        <View>
          {mainContent && Hyperview.renderChildren({
            element: mainContent,
            stylesheets: this.props.stylesheets,
            onUpdate: this.props.onUpdate,
            options: this.props.options,
          })}
        </View>
      </Drawer>
    )
  }
}

2. Create Index File

Create new file: app/hyperview/components/Drawer/index.ts

export { HvDrawer } from './Drawer'

3. Register Component

Update app/hyperview/components/index.ts:

import { LocalImage } from "./LocalImage"
import { LocalSvg } from "./LocalSvg"
import { HvDrawer } from "./Drawer"

export default [
  LocalImage,
  LocalSvg,
  HvDrawer,
]

Usage Example

The drawer can then be used in HXML like this:

<drawer xmlns:local="https://openagents.com/hyperview-local"
        open="false"
        drawerType="slide"
        onOpen="show-drawer"
        onClose="hide-drawer">
  <view role="drawer-content">
    <!-- Drawer content here -->
  </view>
  <view role="main-content">
    <!-- Main content here -->
  </view>
</drawer>

Testing

  1. Create the component files as specified
  2. Test basic drawer functionality (open/close)
  3. Test drawer behaviors trigger correctly
  4. Test drawer content renders properly
  5. Test main content renders properly
  6. Verify drawer styling works with Hyperview stylesheets

Notes

  • The drawer component uses react-native-drawer-layout which is already installed
  • Component follows the same pattern as LocalImage for consistency
  • Uses the same namespace as other local components
  • Supports basic drawer props like open state and type
  • Allows for custom open/close behaviors
  • Maintains Hyperview's component structure and update patterns
@AtlantisPleb
Copy link
Contributor Author

Here's how to integrate the drawer with the existing main.xml template in Rust/OpenAgents:

<?xml version="1.0" encoding="UTF-8" ?>
<doc xmlns="https://hyperview.org/hyperview"
     xmlns:local="https://openagents.com/hyperview-local">
  <screen style="screen">
    <styles>
      <!-- Existing styles -->
      
      <!-- Add drawer-specific styles -->
      <style id="drawerContent"
        backgroundColor="#000"
        flex="1"
        width="100%"
        padding="24" />
        
      <style id="drawerItem"
        flexDirection="row"
        alignItems="center"
        paddingVertical="12"
        paddingHorizontal="16" />
        
      <style id="drawerItemText"
        color="#fff"
        fontSize="16"
        marginLeft="12" />
    </styles>

    <local:drawer>
      <!-- Drawer Content -->
      <view slot="drawer-content" style="drawerContent">
        <!-- New Chat Button -->
        <view style="drawerItem" href="/new-chat">
          <local:svg src="plus" width="20" height="20" color="#fff" />
          <text style="drawerItemText">New Chat</text>
        </view>
        
        <!-- Chat List -->
        <list>
          <item key="1" style="drawerItem" href="/chat/1">
            <local:svg src="message" width="20" height="20" color="#fff" />
            <text style="drawerItemText">Chat #1</text>
          </item>
          <!-- More chat items -->
        </list>
        
        <!-- Bottom Section -->
        <view style="menuContainerBottom">
          <view style="drawerItem" href="/profile">
            <local:svg src="user" width="20" height="20" color="#fff" />
            <text style="drawerItemText">Profile</text>
          </view>
          <view style="drawerItem" href="/wallet">
            <local:svg src="wallet" width="20" height="20" color="#fff" />
            <text style="drawerItemText">Wallet</text>
          </view>
        </view>
      </view>

      <!-- Main Content -->
      <view slot="main-content">
        <body style="body">
          <view style="device">
            <!-- Top Section with menu button -->
            <view style="menuContainerTop">
              <behavior trigger="press" action="open-drawer" />
              <local:svg src="menu" width="20" height="20" />
            </view>

            <!-- Existing Middle Section -->
            <view style="conversationContainer">
              <view>
                <local:image src="loading" style="logo" />
              </view>
            </view>

            <!-- Existing Bottom Section -->
            <view style="inputBox">
              <view style="voiceContainer">
                <local:svg src="voice" width="26" height="26" color="#515151" />
                <text style="askAnything">Ask anything</text>
              </view>
              <view style="sendContainer">
                <local:svg src="send" width="19" height="19" color="#515151" />
              </view>
            </view>
          </view>
        </body>
      </view>
    </local:drawer>
  </screen>
</doc>

In your Rust backend:

use askama::Template;

#[derive(Template)]
#[template(path = "pages/main.xml")]
struct MainTemplate {
    chats: Vec<Chat>,
    // other template data
}

async fn render_main() -> impl Responder {
    let template = MainTemplate {
        chats: get_chats().await,
        // populate other data
    };
    
    HttpResponse::Ok()
        .content_type("application/xml")
        .body(template.render().unwrap())
}

Key changes:

  1. Wrapped the entire content in <local:drawer>
  2. Added drawer-specific styles
  3. Used slots for content organization
  4. Added behavior trigger for opening drawer
  5. Maintained existing color scheme and styling patterns
  6. Integrated with existing SVG icons

The drawer content follows the same structure as the React Native implementation but in HXML format.

(Comment from OpenAgents)

@AtlantisPleb
Copy link
Contributor Author

Behavior Analysis

Looking at the logs and implementation, here's what's happening:

  1. Behavior Trigger

    • The behavior is triggered correctly on press
    • The behavior finds the drawer element successfully
    • The drawer element is found at depth 4 in the DOM tree
  2. Behavior Chain

Click -> behavior trigger -> find drawer -> return update action -> ???

The chain breaks after returning the update action. We're not seeing any logs from:

  • Drawer componentDidUpdate
  • Drawer handleBehavior
  • State changes
  • RNDrawer open/close events
  1. Possible Issues

a) Behavior Return Format
Current:

return {
  action: 'update',
  behaviorElement: current,
  options: {
    behavior: {
      action: 'set-drawer-state',
      state: element.getAttribute('state')
    }
  }
}

We might need:

return {
  action: 'update',
  element: current,
  updates: [{
    action: 'set-drawer-state',
    state: element.getAttribute('state')
  }]
}

b) Component Registration
The drawer component might not be properly registered with Hyperview's update system. Need to check:

static behaviorRegistry = {
  'set-drawer-state': (element: Element, args: any) => {
    // This might not be getting called
  }
}

c) Update Propagation
The update action might not be reaching the component. Need to verify:

  1. How Hyperview handles 'update' actions

  2. How updates are propagated to custom components

  3. Whether we need to register a custom update handler

  4. Next Steps

  5. Add logging to Hyperview's update handler:

<Hyperview
  onUpdate={(update) => {
    console.log("Hyperview update:", update)
  }}
  {...props}
/>
  1. Try different behavior formats:
<behavior 
  trigger="press" 
  action="update"
  target="closest"
  updates="set-drawer-state:open"
/>
  1. Add behavior registry directly to Hyperview:
<Hyperview
  behaviors={[
    ...Behaviors,
    {
      action: 'set-drawer-state',
      callback: (element) => {
        console.log("Direct behavior callback")
        // Handle state
      }
    }
  ]}
  {...props}
/>
  1. Check if we need to implement a custom update handler in the Drawer component:
static updateRegistry = {
  'set-drawer-state': (element: Element, value: string) => {
    // Handle update
  }
}

Let me know which approach you'd like me to implement first.

(Comment from OpenAgents)

@AtlantisPleb
Copy link
Contributor Author

Current Blocker

The react-native-drawer-layout approach is not working - we're hitting several issues:

  1. The behavior chain is finding the drawer but not triggering state changes
  2. The drawer component's behaviorRegistry isn't being called
  3. The component updates aren't propagating correctly through Hyperview's update system

New Approach

Going to try a simpler implementation:

  1. Create our own drawer using basic Views that slide in from the left
  2. Initially just position a view absolutely over the main content
  3. Later add animations using react-native-reanimated
  4. This gives us more direct control over the behavior chain and state management

Benefits:

  • Simpler implementation
  • Direct control over the view hierarchy
  • No dependencies on external drawer libraries
  • Better integration with Hyperview's behavior system
  • Easier to debug and maintain

Will start with basic positioning and visibility, then add animations once the core functionality works.

(Comment from OpenAgents)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant