Skip to content
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
3 changes: 3 additions & 0 deletions packages/ordinals-plus-explorer/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ExplorerPage from './pages/ExplorerPage';
import LinkedResourcesPage from './pages/LinkedResourcesPage';
import SettingsPage from './pages/SettingsPage';
import CreatePage from './pages/CreatePage';
import CreateHtmlPage from './pages/CreateHtmlPage';
import CreateCollectionPage from './pages/CreateCollectionPage';
import CollectionsListPage from './pages/CollectionsListPage';
import CollectionDetailPage from './pages/CollectionDetailPage';
Expand All @@ -27,6 +28,7 @@ function App() {
<Link to="/explorer" className="text-white hover:text-orange-200 transition-colors">Explorer</Link>
<Link to="/" className="text-white hover:text-orange-200 transition-colors">Resources</Link>
<Link to="/create" className="text-white hover:text-orange-200 transition-colors">Create</Link>
<Link to="/create/html" className="text-white hover:text-orange-200 transition-colors">Create HTML</Link>
<div className="relative dropdown-container">
<Link to="/collections" className="text-white hover:text-orange-200 transition-colors flex items-center dropdown-toggle">
Collections
Expand All @@ -53,6 +55,7 @@ function App() {
<Route path="/" element={<LinkedResourcesPage />} />
<Route path="/explorer" element={<ExplorerPage />} />
<Route path="/create" element={<CreatePage />} />
<Route path="/create/html" element={<CreateHtmlPage />} />
<Route path="/collections" element={<CollectionsGalleryPage />} />
<Route path="/collections/list" element={<CollectionsListPage />} />
<Route path="/collections/create" element={<CreateCollectionPage />} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ bitcoin.initEccLib(ecc);
const supportedContentTypes = [
{ mime: 'text/plain', label: 'Text', isText: true },
{ mime: 'application/json', label: 'JSON', isText: true },
{ mime: 'text/html', label: 'HTML', isText: true },
{ mime: 'image/png', label: 'PNG Image', isText: false },
{ mime: 'image/jpeg', label: 'JPEG Image', isText: false },
{ mime: 'image/svg+xml', label: 'SVG Image', isText: false },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import React, { useRef } from 'react';
import LinkedResourceList from '../LinkedResourceList';
import { LinkedResource } from 'ordinalsplus';

interface HtmlCompositionFormProps {
html: string;
onChange: (value: string) => void;
onSubmit: () => void;
}

const HtmlCompositionForm: React.FC<HtmlCompositionFormProps> = ({ html, onChange, onSubmit }) => {
const textareaRef = useRef<HTMLTextAreaElement>(null);

const insertAtCursor = (text: string) => {
const textarea = textareaRef.current;
if (!textarea) {
onChange(html + text);
return;
}
const start = textarea.selectionStart ?? html.length;
const end = textarea.selectionEnd ?? html.length;
const newValue = html.slice(0, start) + text + html.slice(end);
onChange(newValue);
// Set cursor after inserted text
requestAnimationFrame(() => {
textarea.focus();
const pos = start + text.length;
textarea.selectionStart = textarea.selectionEnd = pos;
});
};

const handleResourceSelect = (resource: LinkedResource) => {
const placeholder = `<div data-resource-id="${resource.id || resource.inscriptionId}"></div>`;
insertAtCursor(placeholder);
};

return (
<form
onSubmit={e => {
e.preventDefault();
onSubmit();
}}
>
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-1">
HTML Content
</label>
<textarea
ref={textareaRef}
value={html}
onChange={e => onChange(e.target.value)}
className="w-full h-64 p-2 border border-gray-300 dark:border-gray-600 rounded-md text-sm bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 font-mono"
/>
</div>
<div className="mb-4">
<h3 className="text-sm font-semibold text-gray-700 dark:text-gray-300 mb-2">Insert Resource</h3>
<LinkedResourceList onResourceSelect={handleResourceSelect} itemsPerPage={8} />
</div>
<div className="flex justify-end">
<button
type="submit"
className="px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-md"
>
Continue
</button>
</div>
</form>
);
};

export default HtmlCompositionForm;
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const truncateMiddle = (str: string | null, length = 10): string => {
const supportedContentTypes = [
{ mime: 'text/plain', label: 'Text', isText: true },
{ mime: 'application/json', label: 'JSON', isText: true },
{ mime: 'text/html', label: 'HTML', isText: true },
{ mime: 'image/png', label: 'PNG Image', isText: false },
{ mime: 'image/jpeg', label: 'JPEG Image', isText: false },
{ mime: 'image/svg+xml', label: 'SVG Image', isText: false },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Upload, FileText } from 'lucide-react';
const supportedContentTypes = [
{ mime: 'text/plain', label: 'Text', isText: true },
{ mime: 'application/json', label: 'JSON', isText: true },
{ mime: 'text/html', label: 'HTML', isText: true },
{ mime: 'image/png', label: 'PNG Image', isText: false },
{ mime: 'image/jpeg', label: 'JPEG Image', isText: false },
{ mime: 'image/svg+xml', label: 'SVG Image', isText: false },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -198,10 +198,22 @@ const ResourceInscriptionContext = createContext<ResourceInscriptionContextType
// Create the provider component
interface ResourceInscriptionProviderProps {
children: ReactNode;
initialContentType?: string;
initialContent?: string;
}

export const ResourceInscriptionProvider: React.FC<ResourceInscriptionProviderProps> = ({ children }) => {
const [state, dispatch] = useReducer(resourceInscriptionReducer, initialState);
export const ResourceInscriptionProvider: React.FC<ResourceInscriptionProviderProps> = ({ children, initialContentType, initialContent }) => {
const [state, dispatch] = useReducer(
resourceInscriptionReducer,
{
...initialState,
contentData: {
...initialState.contentData,
type: initialContentType ?? initialState.contentData.type,
content: initialContent ?? initialState.contentData.content
}
}
);
const [validationErrors, setValidationErrors] = useState<Record<string, string>>({});

// Helper functions for common actions
Expand Down Expand Up @@ -390,12 +402,14 @@ const ErrorFallback: React.FC<{ error: Error; resetErrorBoundary: () => void }>
// Main wizard container component
interface ResourceInscriptionWizardProps {
children: ReactNode;
initialContentType?: string;
initialContent?: string;
}

const ResourceInscriptionWizard: React.FC<ResourceInscriptionWizardProps> = ({ children }) => {
const ResourceInscriptionWizard: React.FC<ResourceInscriptionWizardProps> = ({ children, initialContentType, initialContent }) => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ResourceInscriptionProvider>
<ResourceInscriptionProvider initialContentType={initialContentType} initialContent={initialContent}>
<WizardLayout>
{children}
</WizardLayout>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,20 @@ const WizardContent: React.FC = () => {
* ResourceInscriptionWizardContainer is the main container component that integrates all the steps
* of the resource inscription wizard into a cohesive flow.
*/
const ResourceInscriptionWizardContainer: React.FC = () => {
interface ResourceInscriptionWizardContainerProps {
initialContentType?: string;
initialContent?: string;
}

const ResourceInscriptionWizardContainer: React.FC<ResourceInscriptionWizardContainerProps> = ({ initialContentType, initialContent }) => {
return (
<div className="max-w-4xl mx-auto p-4 sm:p-6 lg:p-8">
<div className="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-6">
Resource Inscription Wizard
</h1>
<ResourceInscriptionWizard>

<ResourceInscriptionWizard initialContentType={initialContentType} initialContent={initialContent}>
<WizardContent />
</ResourceInscriptionWizard>
</div>
Expand Down
32 changes: 32 additions & 0 deletions packages/ordinals-plus-explorer/src/pages/CreateHtmlPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React, { useState } from 'react';
import HtmlCompositionForm from '../components/create/HtmlCompositionForm';
import ResourceInscriptionWizardContainer from '../components/inscription/ResourceInscriptionWizardContainer';

const CreateHtmlPage: React.FC = () => {
const [step, setStep] = useState<'compose' | 'inscribe'>('compose');
const [html, setHtml] = useState<string>('');

if (step === 'compose') {
return (
<div className="container mx-auto p-6 max-w-4xl">
<div className="bg-white dark:bg-gray-800 shadow-sm rounded-lg p-6">
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100 mb-6">Create HTML</h1>
<HtmlCompositionForm
html={html}
onChange={setHtml}
onSubmit={() => setStep('inscribe')}
/>
</div>
</div>
);
}

return (
<ResourceInscriptionWizardContainer
initialContentType="text/html"
initialContent={html}
/>
);
};

export default CreateHtmlPage;
13 changes: 13 additions & 0 deletions packages/ordinals-plus-explorer/tests/create-html-page.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import CreateHtmlPage from '../src/pages/CreateHtmlPage';

describe('CreateHtmlPage', () => {
test('allows composing HTML before launching wizard', () => {
render(<CreateHtmlPage />);
expect(screen.getByText(/Create HTML/i)).toBeInTheDocument();
const textarea = screen.getByLabelText(/HTML Content/i) as HTMLTextAreaElement;
fireEvent.change(textarea, { target: { value: '<p>Hello</p>' } });
expect(textarea.value).toBe('<p>Hello</p>');
});
});