Visueller Renderer fuer aria_view: Orb (reanimated-Puls je Zustand), CardView (text/image/list/map/code, Sci-Fi-Chrome), AriaViewCanvas (pannbare Flaeche, 2-Finger-Pan + Pinch, Karten materialisieren gestaffelt). WorkspaceScreen blendet die Flaeche als Overlay ueber Chat/Cockpit ein, sobald ARIA fuers fokussierte Projekt eine Ansicht komponiert. v1/Vorgeschmack, tsc-clean; Markdown=Klartext, Map=Marker-Liste. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
115 lines
4.0 KiB
TypeScript
115 lines
4.0 KiB
TypeScript
/**
|
|
* CardView — rendert EINE Karte einer aria_view-Spec (M1). Schaltet nach
|
|
* card.type auf den passenden Renderer. Unbekannte Typen werden als Text-
|
|
* Fallback gezeigt (nie crashen).
|
|
*
|
|
* Bewusst dependency-leicht (v1): Markdown wird als Klartext dargestellt, Map
|
|
* als Marker-Liste (kein Karten-Lib), Code als Monospace-Block. Spaeter koennen
|
|
* einzelne Renderer aufgebohrt werden, ohne die Spec/den Fluss zu aendern.
|
|
*/
|
|
|
|
import React from 'react';
|
|
import { Image, ScrollView, StyleSheet, Text, View } from 'react-native';
|
|
import { ViewCard, ViewMarker } from '../services/ariaView';
|
|
|
|
const ImageBody: React.FC<{ src?: string }> = ({ src }) => {
|
|
const isUrl = !!src && /^https?:\/\//i.test(src);
|
|
if (isUrl) {
|
|
return <Image source={{ uri: src }} style={styles.image} resizeMode="contain" />;
|
|
}
|
|
return <Text style={styles.muted}>🖼️ {src || '(kein Bild)'}</Text>;
|
|
};
|
|
|
|
const ListBody: React.FC<{ md?: string }> = ({ md }) => {
|
|
const lines = (md || '')
|
|
.split('\n')
|
|
.map((l) => l.replace(/^\s*[-*•]\s?/, '').trim())
|
|
.filter(Boolean);
|
|
if (lines.length === 0) return <Text style={styles.muted}>(leer)</Text>;
|
|
return (
|
|
<View>
|
|
{lines.map((l, i) => (
|
|
<View key={i} style={styles.listRow}>
|
|
<Text style={styles.bullet}>•</Text>
|
|
<Text style={styles.text}>{l}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const MapBody: React.FC<{ markers?: ViewMarker[] }> = ({ markers }) => {
|
|
const ms = Array.isArray(markers) ? markers : [];
|
|
return (
|
|
<View style={styles.map}>
|
|
<Text style={styles.mapHint}>🗺️ Karte ({ms.length} Orte)</Text>
|
|
{ms.map((m, i) => (
|
|
<Text key={i} style={styles.text}>
|
|
📍 {m.label || `${m.lat?.toFixed?.(4)}, ${m.lon?.toFixed?.(4)}`}
|
|
</Text>
|
|
))}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const CodeBody: React.FC<{ md?: string; path?: string; lang?: string }> = ({ md, path, lang }) => (
|
|
<View>
|
|
{(path || lang) && (
|
|
<Text style={styles.codeCaption}>
|
|
{path || ''}{lang ? ` · ${lang}` : ''}
|
|
</Text>
|
|
)}
|
|
<ScrollView horizontal style={styles.codeScroll}>
|
|
<Text style={styles.code}>{md || ''}</Text>
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
|
|
const CardView: React.FC<{ card: ViewCard }> = ({ card }) => {
|
|
return (
|
|
<View style={styles.card}>
|
|
{!!card.title && <Text style={styles.cardTitle}>{card.title}</Text>}
|
|
{card.type === 'image' ? (
|
|
<ImageBody src={card.src} />
|
|
) : card.type === 'list' ? (
|
|
<ListBody md={card.md} />
|
|
) : card.type === 'map' ? (
|
|
<MapBody markers={card.markers} />
|
|
) : card.type === 'code' ? (
|
|
<CodeBody md={card.md} path={card.path} lang={card.lang} />
|
|
) : (
|
|
<Text style={styles.text}>{card.md || ''}</Text>
|
|
)}
|
|
</View>
|
|
);
|
|
};
|
|
|
|
const styles = StyleSheet.create({
|
|
card: {
|
|
backgroundColor: 'rgba(18,18,42,0.92)',
|
|
borderColor: 'rgba(123,92,255,0.35)',
|
|
borderWidth: 1,
|
|
borderRadius: 14,
|
|
padding: 14,
|
|
marginVertical: 8,
|
|
shadowColor: '#7B5CFF',
|
|
shadowOpacity: 0.25,
|
|
shadowRadius: 12,
|
|
shadowOffset: { width: 0, height: 2 },
|
|
elevation: 6,
|
|
},
|
|
cardTitle: { color: '#C9C9FF', fontSize: 15, fontWeight: '700', marginBottom: 8 },
|
|
text: { color: '#E6E6F0', fontSize: 14, lineHeight: 20, flexShrink: 1 },
|
|
muted: { color: '#8A8AB0', fontSize: 13, fontStyle: 'italic' },
|
|
image: { width: '100%', height: 200, borderRadius: 8, backgroundColor: '#0D0D1A' },
|
|
listRow: { flexDirection: 'row', alignItems: 'flex-start', marginVertical: 2 },
|
|
bullet: { color: '#7B5CFF', marginRight: 8, fontSize: 14, lineHeight: 20 },
|
|
map: { backgroundColor: '#0D0D1A', borderRadius: 8, padding: 10 },
|
|
mapHint: { color: '#00B4D8', fontSize: 13, fontWeight: '600', marginBottom: 6 },
|
|
codeCaption: { color: '#8A8AB0', fontSize: 12, marginBottom: 6 },
|
|
codeScroll: { backgroundColor: '#0A0A14', borderRadius: 8, padding: 10 },
|
|
code: { color: '#B9F5C9', fontFamily: 'monospace', fontSize: 12.5, lineHeight: 18 },
|
|
});
|
|
|
|
export default React.memo(CardView);
|