960 lines
44 KiB
HTML
960 lines
44 KiB
HTML
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en">
|
||
|
|
<head>
|
||
|
|
<meta charset="UTF-8">
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
|
|
<title>TabData Visual Explanation</title>
|
||
|
|
<style>
|
||
|
|
* {
|
||
|
|
margin: 0;
|
||
|
|
padding: 0;
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
|
||
|
|
body {
|
||
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||
|
|
background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
|
||
|
|
color: #333;
|
||
|
|
padding: 20px;
|
||
|
|
line-height: 1.6;
|
||
|
|
}
|
||
|
|
|
||
|
|
.container {
|
||
|
|
max-width: 1400px;
|
||
|
|
margin: 0 auto;
|
||
|
|
}
|
||
|
|
|
||
|
|
h1 {
|
||
|
|
text-align: center;
|
||
|
|
color: white;
|
||
|
|
margin-bottom: 30px;
|
||
|
|
font-size: 2.5em;
|
||
|
|
text-shadow: 2px 2px 4px rgba(0,0,0,0.3);
|
||
|
|
}
|
||
|
|
|
||
|
|
.section {
|
||
|
|
background: white;
|
||
|
|
border-radius: 12px;
|
||
|
|
padding: 25px;
|
||
|
|
margin-bottom: 25px;
|
||
|
|
box-shadow: 0 8px 16px rgba(0,0,0,0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-title {
|
||
|
|
font-size: 1.8em;
|
||
|
|
color: #0175C2;
|
||
|
|
margin-bottom: 20px;
|
||
|
|
border-bottom: 3px solid #0175C2;
|
||
|
|
padding-bottom: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.blueprint {
|
||
|
|
background: #f8f9fa;
|
||
|
|
border: 2px dashed #0175C2;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 20px;
|
||
|
|
margin: 20px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.property {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
margin: 12px 0;
|
||
|
|
padding: 12px;
|
||
|
|
background: white;
|
||
|
|
border-radius: 6px;
|
||
|
|
border-left: 4px solid #0175C2;
|
||
|
|
transition: transform 0.2s;
|
||
|
|
}
|
||
|
|
|
||
|
|
.property:hover {
|
||
|
|
transform: translateX(5px);
|
||
|
|
box-shadow: 0 2px 8px rgba(1,117,194,0.2);
|
||
|
|
}
|
||
|
|
|
||
|
|
.property-name {
|
||
|
|
font-weight: bold;
|
||
|
|
color: #0175C2;
|
||
|
|
min-width: 140px;
|
||
|
|
font-family: 'Courier New', monospace;
|
||
|
|
}
|
||
|
|
|
||
|
|
.property-type {
|
||
|
|
background: #e3f2fd;
|
||
|
|
color: #1976d2;
|
||
|
|
padding: 4px 10px;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 0.85em;
|
||
|
|
font-weight: bold;
|
||
|
|
margin: 0 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.property-desc {
|
||
|
|
color: #555;
|
||
|
|
flex: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
.example-card {
|
||
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
|
|
color: white;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 20px;
|
||
|
|
margin: 20px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-header {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
margin-bottom: 15px;
|
||
|
|
font-size: 1.2em;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-icon {
|
||
|
|
font-size: 2em;
|
||
|
|
margin-right: 15px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-data {
|
||
|
|
background: rgba(255,255,255,0.1);
|
||
|
|
border-radius: 6px;
|
||
|
|
padding: 15px;
|
||
|
|
margin-top: 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-data-item {
|
||
|
|
margin: 8px 0;
|
||
|
|
font-family: 'Courier New', monospace;
|
||
|
|
}
|
||
|
|
|
||
|
|
.card-data-label {
|
||
|
|
color: #ffd700;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
.constructor-flow {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-around;
|
||
|
|
margin: 20px 0;
|
||
|
|
flex-wrap: wrap;
|
||
|
|
}
|
||
|
|
|
||
|
|
.flow-step {
|
||
|
|
background: #f0f7ff;
|
||
|
|
border: 2px solid #0175C2;
|
||
|
|
border-radius: 8px;
|
||
|
|
padding: 15px 20px;
|
||
|
|
margin: 10px;
|
||
|
|
text-align: center;
|
||
|
|
position: relative;
|
||
|
|
min-width: 200px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.flow-arrow {
|
||
|
|
font-size: 2em;
|
||
|
|
color: #0175C2;
|
||
|
|
margin: 0 10px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.code-block {
|
||
|
|
background: #1e1e1e;
|
||
|
|
color: #d4d4d4;
|
||
|
|
padding: 20px;
|
||
|
|
border-radius: 8px;
|
||
|
|
font-family: 'Courier New', monospace;
|
||
|
|
overflow-x: auto;
|
||
|
|
margin: 15px 0;
|
||
|
|
position: relative;
|
||
|
|
}
|
||
|
|
|
||
|
|
.code-comment {
|
||
|
|
color: #6a9955;
|
||
|
|
}
|
||
|
|
|
||
|
|
.code-keyword {
|
||
|
|
color: #569cd6;
|
||
|
|
}
|
||
|
|
|
||
|
|
.code-string {
|
||
|
|
color: #ce9178;
|
||
|
|
}
|
||
|
|
|
||
|
|
.code-class {
|
||
|
|
color: #4ec9b0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.json-example {
|
||
|
|
background: #2d2d2d;
|
||
|
|
padding: 20px;
|
||
|
|
border-radius: 8px;
|
||
|
|
color: #d4d4d4;
|
||
|
|
font-family: 'Courier New', monospace;
|
||
|
|
margin: 15px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.json-key {
|
||
|
|
color: #9cdcfe;
|
||
|
|
}
|
||
|
|
|
||
|
|
.json-value {
|
||
|
|
color: #ce9178;
|
||
|
|
}
|
||
|
|
|
||
|
|
.comparison {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: 20px;
|
||
|
|
margin: 20px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.comparison-item {
|
||
|
|
background: #f8f9fa;
|
||
|
|
padding: 20px;
|
||
|
|
border-radius: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.comparison-title {
|
||
|
|
font-weight: bold;
|
||
|
|
margin-bottom: 10px;
|
||
|
|
font-size: 1.1em;
|
||
|
|
}
|
||
|
|
|
||
|
|
.required-badge {
|
||
|
|
background: #f44336;
|
||
|
|
color: white;
|
||
|
|
padding: 2px 8px;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 0.75em;
|
||
|
|
margin-left: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.optional-badge {
|
||
|
|
background: #4caf50;
|
||
|
|
color: white;
|
||
|
|
padding: 2px 8px;
|
||
|
|
border-radius: 4px;
|
||
|
|
font-size: 0.75em;
|
||
|
|
margin-left: 8px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.visual-flow {
|
||
|
|
background: linear-gradient(to right, #f8f9fa, white, #f8f9fa);
|
||
|
|
padding: 30px;
|
||
|
|
border-radius: 8px;
|
||
|
|
margin: 20px 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.highlight {
|
||
|
|
background: #fff3cd;
|
||
|
|
padding: 2px 6px;
|
||
|
|
border-radius: 3px;
|
||
|
|
font-weight: bold;
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 768px) {
|
||
|
|
.comparison {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
|
||
|
|
.constructor-flow {
|
||
|
|
flex-direction: column;
|
||
|
|
}
|
||
|
|
|
||
|
|
.flow-arrow {
|
||
|
|
transform: rotate(90deg);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
</style>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
<div class="container">
|
||
|
|
<h1>📊 TabData Class - Visual Guide</h1>
|
||
|
|
|
||
|
|
<!-- SECTION 1: What is TabData? -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🎯 What is TabData?</h2>
|
||
|
|
<p style="font-size: 1.1em; margin-bottom: 20px;">
|
||
|
|
<strong>TabData</strong> is a <span class="highlight">blueprint</span> (or template) that defines how we store information about tabs, bookmarks, and history items.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<div class="example-card">
|
||
|
|
<div class="card-header">
|
||
|
|
<span class="card-icon">🔖</span>
|
||
|
|
<span>Example: A YouTube Tab</span>
|
||
|
|
</div>
|
||
|
|
<div class="card-data">
|
||
|
|
<div class="card-data-item"><span class="card-data-label">id:</span> "tab_123"</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">title:</span> "Flutter - YouTube"</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">url:</span> "https://youtube.com"</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">favicon:</span> "https://youtube.com/favicon.ico"</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">lastAccessed:</span> 2025-10-20 14:30:00</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">isPinned:</span> true</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">type:</span> "tab"</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">visitCount:</span> 47</div>
|
||
|
|
<div class="card-data-item"><span class="card-data-label">folder:</span> null</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<p style="margin-top: 20px;">
|
||
|
|
Every tab, bookmark, or history item in your app is stored as a <strong>TabData object</strong> containing this information.
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 2: The Properties -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">📋 The Properties (Variables)</h2>
|
||
|
|
<p style="margin-bottom: 15px;">These are the pieces of information each TabData object holds:</p>
|
||
|
|
|
||
|
|
<div class="blueprint">
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String id</span>
|
||
|
|
<span class="property-type">Text</span>
|
||
|
|
<span class="property-desc">Unique identifier (like a social security number for the tab)</span>
|
||
|
|
<span class="required-badge">REQUIRED</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String title</span>
|
||
|
|
<span class="property-type">Text</span>
|
||
|
|
<span class="property-desc">The page title (e.g., "Google")</span>
|
||
|
|
<span class="required-badge">REQUIRED</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String url</span>
|
||
|
|
<span class="property-type">Text</span>
|
||
|
|
<span class="property-desc">Web address (e.g., "https://google.com")</span>
|
||
|
|
<span class="required-badge">REQUIRED</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String favicon</span>
|
||
|
|
<span class="property-type">Text</span>
|
||
|
|
<span class="property-desc">The small icon for the website (defaults to empty '')</span>
|
||
|
|
<span class="optional-badge">OPTIONAL</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">DateTime lastAccessed</span>
|
||
|
|
<span class="property-type">Date/Time</span>
|
||
|
|
<span class="property-desc">When you last opened this (defaults to now)</span>
|
||
|
|
<span class="optional-badge">OPTIONAL</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">bool isPinned</span>
|
||
|
|
<span class="property-type">True/False</span>
|
||
|
|
<span class="property-desc">Is the tab pinned? (defaults to false)</span>
|
||
|
|
<span class="optional-badge">OPTIONAL</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String type</span>
|
||
|
|
<span class="property-type">Text</span>
|
||
|
|
<span class="property-desc">What kind: 'tab', 'bookmark', or 'history' (defaults to 'tab')</span>
|
||
|
|
<span class="optional-badge">OPTIONAL</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">int? visitCount</span>
|
||
|
|
<span class="property-type">Number?</span>
|
||
|
|
<span class="property-desc">How many times visited (can be null)</span>
|
||
|
|
<span class="optional-badge">OPTIONAL</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String? folder</span>
|
||
|
|
<span class="property-type">Text?</span>
|
||
|
|
<span class="property-desc">Bookmark folder name (can be null)</span>
|
||
|
|
<span class="optional-badge">OPTIONAL</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #e3f2fd; padding: 15px; border-radius: 6px; margin-top: 20px;">
|
||
|
|
<strong>💡 Note:</strong> The <code>?</code> after a type (like <code>int?</code> or <code>String?</code>) means the value can be <strong>null</strong> (empty/missing).
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 3: The Constructor -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🏗️ The Constructor - Creating TabData Objects</h2>
|
||
|
|
<p style="margin-bottom: 15px;">The constructor is like a <strong>factory that builds TabData objects</strong>.</p>
|
||
|
|
|
||
|
|
<div class="visual-flow">
|
||
|
|
<h3 style="margin-bottom: 15px;">How to Create a TabData:</h3>
|
||
|
|
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-comment">// Creating a new TabData object</span>
|
||
|
|
<span class="code-keyword">final</span> myTab = <span class="code-class">TabData</span>(
|
||
|
|
id: <span class="code-string">'tab_456'</span>, <span class="code-comment">// REQUIRED ✅</span>
|
||
|
|
title: <span class="code-string">'Flutter Docs'</span>, <span class="code-comment">// REQUIRED ✅</span>
|
||
|
|
url: <span class="code-string">'https://flutter.dev'</span>, <span class="code-comment">// REQUIRED ✅</span>
|
||
|
|
favicon: <span class="code-string">'https://flutter.dev/icon.png'</span>, <span class="code-comment">// Optional</span>
|
||
|
|
isPinned: <span class="code-keyword">true</span>, <span class="code-comment">// Optional (defaults to false)</span>
|
||
|
|
visitCount: <span class="code-string">23</span>, <span class="code-comment">// Optional (can be null)</span>
|
||
|
|
);
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="constructor-flow">
|
||
|
|
<div class="flow-step">
|
||
|
|
<strong>📝 You provide data</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
id, title, url<br/>
|
||
|
|
+ optional fields
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flow-arrow">➜</div>
|
||
|
|
|
||
|
|
<div class="flow-step">
|
||
|
|
<strong>🏭 Constructor runs</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
Fills in defaults<br/>
|
||
|
|
Sets lastAccessed
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flow-arrow">➜</div>
|
||
|
|
|
||
|
|
<div class="flow-step">
|
||
|
|
<strong>✨ TabData object created</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
Ready to use!
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #fff3cd; padding: 15px; border-radius: 6px; margin-top: 20px;">
|
||
|
|
<strong>🎯 Key Feature:</strong> The line <code>: lastAccessed = lastAccessed ?? DateTime.now()</code> means:
|
||
|
|
<br/><br/>
|
||
|
|
"If lastAccessed is provided, use it. Otherwise (<code>??</code>), use the current time (<code>DateTime.now()</code>)."
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 4: The fromJson Factory -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🔄 The fromJson Factory - Converting JSON to TabData</h2>
|
||
|
|
<p style="margin-bottom: 15px;">
|
||
|
|
When we get data from the browser API, it comes as <strong>JSON</strong> (a text format).
|
||
|
|
The <code>fromJson</code> method converts that JSON into a TabData object.
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<div class="comparison">
|
||
|
|
<div class="comparison-item">
|
||
|
|
<div class="comparison-title" style="color: #f44336;">❌ JSON (from browser)</div>
|
||
|
|
<div class="json-example">
|
||
|
|
{
|
||
|
|
<span class="json-key">"id"</span>: <span class="json-value">"tab_789"</span>,
|
||
|
|
<span class="json-key">"title"</span>: <span class="json-value">"GitHub"</span>,
|
||
|
|
<span class="json-key">"url"</span>: <span class="json-value">"https://github.com"</span>,
|
||
|
|
<span class="json-key">"favIconUrl"</span>: <span class="json-value">"..."</span>,
|
||
|
|
<span class="json-key">"lastVisitTime"</span>: <span class="json-value">"2025-10-20T..."</span>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
<p style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
This is just text - we can't use it directly!
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="comparison-item">
|
||
|
|
<div class="comparison-title" style="color: #4caf50;">✅ TabData Object (in our app)</div>
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-class">TabData</span> {
|
||
|
|
id: <span class="code-string">"tab_789"</span>
|
||
|
|
title: <span class="code-string">"GitHub"</span>
|
||
|
|
url: <span class="code-string">"https://github.com"</span>
|
||
|
|
favicon: <span class="code-string">"..."</span>
|
||
|
|
lastAccessed: DateTime
|
||
|
|
isPinned: <span class="code-keyword">false</span>
|
||
|
|
...
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
<p style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
This is a proper object we can work with!
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="visual-flow" style="margin-top: 30px;">
|
||
|
|
<h3>How fromJson Works:</h3>
|
||
|
|
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-comment">// Usage:</span>
|
||
|
|
<span class="code-keyword">final</span> jsonData = {<span class="code-string">'id'</span>: <span class="code-string">'123'</span>, <span class="code-string">'title'</span>: <span class="code-string">'Test'</span>, <span class="code-string">'url'</span>: <span class="code-string">'...'</span>};
|
||
|
|
<span class="code-keyword">final</span> tab = <span class="code-class">TabData</span>.fromJson(jsonData);
|
||
|
|
|
||
|
|
<span class="code-comment">// The method does this:</span>
|
||
|
|
id: json[<span class="code-string">'id'</span>].toString() <span class="code-comment">// Get 'id' from JSON</span>
|
||
|
|
title: json[<span class="code-string">'title'</span>] ?? <span class="code-string">'Untitled'</span> <span class="code-comment">// Get 'title', or use 'Untitled' if missing</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 5: Handling Different Date Fields -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">📅 Smart Date Handling</h2>
|
||
|
|
<p style="margin-bottom: 15px;">
|
||
|
|
Different browser APIs send dates with <strong>different field names</strong>.
|
||
|
|
Our fromJson method is smart enough to handle all of them!
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-comment">// The code checks multiple possible date fields:</span>
|
||
|
|
|
||
|
|
lastAccessed: json[<span class="code-string">'lastAccessed'</span>] != <span class="code-keyword">null</span>
|
||
|
|
? DateTime.parse(json[<span class="code-string">'lastAccessed'</span>]) <span class="code-comment">// Try 'lastAccessed' first</span>
|
||
|
|
: (json[<span class="code-string">'lastVisitTime'</span>] != <span class="code-keyword">null</span>
|
||
|
|
? DateTime.parse(json[<span class="code-string">'lastVisitTime'</span>]) <span class="code-comment">// If not, try 'lastVisitTime'</span>
|
||
|
|
: (json[<span class="code-string">'dateAdded'</span>] != <span class="code-keyword">null</span>
|
||
|
|
? DateTime.parse(json[<span class="code-string">'dateAdded'</span>]) <span class="code-comment">// If not, try 'dateAdded'</span>
|
||
|
|
: (json[<span class="code-string">'timestamp'</span>] != <span class="code-keyword">null</span>
|
||
|
|
? DateTime.parse(json[<span class="code-string">'timestamp'</span>]) <span class="code-comment">// If not, try 'timestamp'</span>
|
||
|
|
: DateTime.now()))) <span class="code-comment">// If none exist, use current time</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="constructor-flow" style="margin-top: 30px;">
|
||
|
|
<div class="flow-step" style="background: #e8f5e9;">
|
||
|
|
<strong>Try 1:</strong><br/>
|
||
|
|
lastAccessed?
|
||
|
|
</div>
|
||
|
|
<div class="flow-arrow">→</div>
|
||
|
|
<div class="flow-step" style="background: #fff3cd;">
|
||
|
|
<strong>Try 2:</strong><br/>
|
||
|
|
lastVisitTime?
|
||
|
|
</div>
|
||
|
|
<div class="flow-arrow">→</div>
|
||
|
|
<div class="flow-step" style="background: #ffe0b2;">
|
||
|
|
<strong>Try 3:</strong><br/>
|
||
|
|
dateAdded?
|
||
|
|
</div>
|
||
|
|
<div class="flow-arrow">→</div>
|
||
|
|
<div class="flow-step" style="background: #ffcdd2;">
|
||
|
|
<strong>Try 4:</strong><br/>
|
||
|
|
timestamp?
|
||
|
|
</div>
|
||
|
|
<div class="flow-arrow">→</div>
|
||
|
|
<div class="flow-step" style="background: #f3e5f5;">
|
||
|
|
<strong>Fallback:</strong><br/>
|
||
|
|
Use now!
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #e3f2fd; padding: 15px; border-radius: 6px; margin-top: 20px;">
|
||
|
|
<strong>💡 Why?</strong> Different browsers and APIs use different names for the same thing.
|
||
|
|
This ensures our app works with all of them!
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 6: The ?? Operator -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🛡️ The ?? Operator - Providing Defaults</h2>
|
||
|
|
<p style="margin-bottom: 15px;">
|
||
|
|
The <code>??</code> operator means: <strong>"if null (missing), use this default value instead"</strong>
|
||
|
|
</p>
|
||
|
|
|
||
|
|
<div class="comparison">
|
||
|
|
<div class="comparison-item">
|
||
|
|
<div class="comparison-title">Without ?? (Would Crash)</div>
|
||
|
|
<div class="code-block">
|
||
|
|
title: json[<span class="code-string">'title'</span>]
|
||
|
|
<span class="code-comment">// If 'title' is missing → ERROR! 💥</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="comparison-item">
|
||
|
|
<div class="comparison-title">With ?? (Safe)</div>
|
||
|
|
<div class="code-block">
|
||
|
|
title: json[<span class="code-string">'title'</span>] ?? <span class="code-string">'Untitled'</span>
|
||
|
|
<span class="code-comment">// If 'title' is missing → use 'Untitled' ✅</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="margin-top: 20px;">
|
||
|
|
<h3 style="margin-bottom: 15px;">All the defaults in fromJson:</h3>
|
||
|
|
<div class="property">
|
||
|
|
<code style="background: #f5f5f5; padding: 4px 8px; border-radius: 4px;">
|
||
|
|
json['title'] ?? 'Untitled'
|
||
|
|
</code>
|
||
|
|
<span style="margin-left: 15px;">→ If no title, use "Untitled"</span>
|
||
|
|
</div>
|
||
|
|
<div class="property">
|
||
|
|
<code style="background: #f5f5f5; padding: 4px 8px; border-radius: 4px;">
|
||
|
|
json['url'] ?? ''
|
||
|
|
</code>
|
||
|
|
<span style="margin-left: 15px;">→ If no URL, use empty string</span>
|
||
|
|
</div>
|
||
|
|
<div class="property">
|
||
|
|
<code style="background: #f5f5f5; padding: 4px 8px; border-radius: 4px;">
|
||
|
|
json['favicon'] ?? json['favIconUrl'] ?? ''
|
||
|
|
</code>
|
||
|
|
<span style="margin-left: 15px;">→ Try 'favicon', then 'favIconUrl', then empty</span>
|
||
|
|
</div>
|
||
|
|
<div class="property">
|
||
|
|
<code style="background: #f5f5f5; padding: 4px 8px; border-radius: 4px;">
|
||
|
|
json['isPinned'] ?? false
|
||
|
|
</code>
|
||
|
|
<span style="margin-left: 15px;">→ If not specified, assume not pinned</span>
|
||
|
|
</div>
|
||
|
|
<div class="property">
|
||
|
|
<code style="background: #f5f5f5; padding: 4px 8px; border-radius: 4px;">
|
||
|
|
json['type'] ?? 'tab'
|
||
|
|
</code>
|
||
|
|
<span style="margin-left: 15px;">→ If no type, assume it's a tab</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 7: Real-World Example -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🌍 Real-World Example</h2>
|
||
|
|
|
||
|
|
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); padding: 30px; border-radius: 12px; color: white;">
|
||
|
|
<h3 style="margin-bottom: 20px;">📱 What happens when you open a tab:</h3>
|
||
|
|
|
||
|
|
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px; margin: 15px 0;">
|
||
|
|
<strong>Step 1:</strong> Browser sends JSON data
|
||
|
|
<div class="json-example" style="margin-top: 10px;">
|
||
|
|
{
|
||
|
|
<span class="json-key">"id"</span>: <span class="json-value">"tab_999"</span>,
|
||
|
|
<span class="json-key">"title"</span>: <span class="json-value">"Claude AI"</span>,
|
||
|
|
<span class="json-key">"url"</span>: <span class="json-value">"https://claude.ai"</span>,
|
||
|
|
<span class="json-key">"favIconUrl"</span>: <span class="json-value">"https://claude.ai/icon.png"</span>,
|
||
|
|
<span class="json-key">"lastVisitTime"</span>: <span class="json-value">"2025-10-20T14:30:00"</span>,
|
||
|
|
<span class="json-key">"visitCount"</span>: <span class="json-value">15</span>
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="text-align: center; font-size: 2em; margin: 20px 0;">⬇️</div>
|
||
|
|
|
||
|
|
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px; margin: 15px 0;">
|
||
|
|
<strong>Step 2:</strong> fromJson converts it
|
||
|
|
<div class="code-block" style="margin-top: 10px;">
|
||
|
|
<span class="code-keyword">final</span> tab = <span class="code-class">TabData</span>.fromJson(browserData);
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="text-align: center; font-size: 2em; margin: 20px 0;">⬇️</div>
|
||
|
|
|
||
|
|
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px;">
|
||
|
|
<strong>Step 3:</strong> We have a usable TabData object!
|
||
|
|
<div style="margin-top: 15px; font-family: 'Courier New', monospace;">
|
||
|
|
tab.id → "tab_999"<br/>
|
||
|
|
tab.title → "Claude AI"<br/>
|
||
|
|
tab.url → "https://claude.ai"<br/>
|
||
|
|
tab.favicon → "https://claude.ai/icon.png"<br/>
|
||
|
|
tab.lastAccessed → DateTime(2025, 10, 20, 14, 30)<br/>
|
||
|
|
tab.isPinned → false (default)<br/>
|
||
|
|
tab.type → "tab" (default)<br/>
|
||
|
|
tab.visitCount → 15<br/>
|
||
|
|
tab.folder → null
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="text-align: center; font-size: 2em; margin: 20px 0;">⬇️</div>
|
||
|
|
|
||
|
|
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px;">
|
||
|
|
<strong>Step 4:</strong> Display in UI
|
||
|
|
<div style="margin-top: 15px;">
|
||
|
|
Now we can show: tab.title, tab.url, tab.favicon, etc. in our card widgets!
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 8: Why This Design? -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🤔 Why Is TabData Designed This Way?</h2>
|
||
|
|
|
||
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-top: 20px;">
|
||
|
|
<div style="background: #e8f5e9; padding: 20px; border-radius: 8px; border-left: 4px solid #4caf50;">
|
||
|
|
<h3 style="color: #2e7d32; margin-bottom: 10px;">✅ Type Safety</h3>
|
||
|
|
<p>Each property has a specific type (String, bool, DateTime). This prevents errors like putting a number where text should be.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #e3f2fd; padding: 20px; border-radius: 8px; border-left: 4px solid #2196f3;">
|
||
|
|
<h3 style="color: #1565c0; margin-bottom: 10px;">🛡️ Error Handling</h3>
|
||
|
|
<p>The ?? operators and multiple date field checks ensure the app doesn't crash if data is missing or in a different format.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #fff3e0; padding: 20px; border-radius: 8px; border-left: 4px solid #ff9800;">
|
||
|
|
<h3 style="color: #e65100; margin-bottom: 10px;">🔄 Flexibility</h3>
|
||
|
|
<p>Works with different browser APIs (Chrome, Firefox, Edge) because it handles different field names and formats.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #f3e5f5; padding: 20px; border-radius: 8px; border-left: 4px solid #9c27b0;">
|
||
|
|
<h3 style="color: #6a1b9a; margin-bottom: 10px;">📦 Organization</h3>
|
||
|
|
<p>All tab/bookmark/history data is in one place, making it easy to work with throughout the app.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #fce4ec; padding: 20px; border-radius: 8px; border-left: 4px solid #e91e63;">
|
||
|
|
<h3 style="color: #880e4f; margin-bottom: 10px;">🔍 Readability</h3>
|
||
|
|
<p>Instead of messy JSON strings everywhere, we have clean objects: tab.title, tab.url, etc.</p>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: #e0f2f1; padding: 20px; border-radius: 8px; border-left: 4px solid #009688;">
|
||
|
|
<h3 style="color: #00695c; margin-bottom: 10px;">⚡ Efficiency</h3>
|
||
|
|
<p>Creating TabData objects is fast, and we can easily create lists of them: List<TabData>.</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 9: How It's Used in main.dart -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">🔗 How TabData Is Used in main.dart</h2>
|
||
|
|
|
||
|
|
<div class="visual-flow">
|
||
|
|
<h3 style="margin-bottom: 15px;">The Complete Data Flow:</h3>
|
||
|
|
|
||
|
|
<div class="constructor-flow" style="margin: 30px 0;">
|
||
|
|
<div class="flow-step" style="background: #ffebee;">
|
||
|
|
<strong>1. Browser API</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
Returns JSON data<br/>
|
||
|
|
about tabs/bookmarks
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flow-arrow">➜</div>
|
||
|
|
|
||
|
|
<div class="flow-step" style="background: #fff3e0;">
|
||
|
|
<strong>2. fromJson()</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
Converts JSON<br/>
|
||
|
|
to TabData objects
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flow-arrow">➜</div>
|
||
|
|
|
||
|
|
<div class="flow-step" style="background: #e8f5e9;">
|
||
|
|
<strong>3. State Lists</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
Stored in allItems[]<br/>
|
||
|
|
and filteredItems[]
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="flow-arrow">➜</div>
|
||
|
|
|
||
|
|
<div class="flow-step" style="background: #e3f2fd;">
|
||
|
|
<strong>4. UI Display</strong>
|
||
|
|
<div style="margin-top: 10px; font-size: 0.9em;">
|
||
|
|
ItemCard widgets<br/>
|
||
|
|
show the data
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-comment">// 1. Get JSON from browser</span>
|
||
|
|
<span class="code-keyword">final</span> result = <span class="code-keyword">await</span> _callBrowserAPI(<span class="code-string">'getTabs'</span>);
|
||
|
|
<span class="code-keyword">final</span> List<<span class="code-keyword">dynamic</span>> data = json.decode(result);
|
||
|
|
|
||
|
|
<span class="code-comment">// 2. Convert each JSON item to TabData</span>
|
||
|
|
<span class="code-keyword">return</span> data.map((item) {
|
||
|
|
item[<span class="code-string">'type'</span>] = <span class="code-string">'tab'</span>;
|
||
|
|
<span class="code-keyword">return</span> <span class="code-class">TabData</span>.fromJson(item); <span class="code-comment">// ← Using fromJson here!</span>
|
||
|
|
}).toList();
|
||
|
|
|
||
|
|
<span class="code-comment">// 3. Store in state</span>
|
||
|
|
setState(() {
|
||
|
|
allItems = [...tabs, ...bookmarks, ...history]; <span class="code-comment">// All TabData objects</span>
|
||
|
|
});
|
||
|
|
|
||
|
|
<span class="code-comment">// 4. Display in UI</span>
|
||
|
|
<span class="code-keyword">return</span> ItemCard(
|
||
|
|
item: filteredItems[index], <span class="code-comment">// ← Pass TabData to card</span>
|
||
|
|
icon: _getIcon(filteredItems[index]),
|
||
|
|
);
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 10: Key Statements Breakdown -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">📝 Statement-by-Statement Breakdown</h2>
|
||
|
|
|
||
|
|
<div style="margin-top: 20px;">
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">class TabData {</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Declare a new class (blueprint) named TabData
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">String id;</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Declare a variable named 'id' that holds text (String)
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">int? visitCount;</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Declare a variable that holds a number, but can also be null (empty)
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">TabData({...})</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Define the constructor - how to create TabData objects
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">required this.id</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> This parameter MUST be provided when creating a TabData
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">this.favicon = ''</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> This parameter is optional, defaults to empty string if not provided
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">: lastAccessed = lastAccessed ?? DateTime.now()</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Initializer list - set lastAccessed to the provided value, or current time if null
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">factory TabData.fromJson(...)</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Define a factory method - an alternative way to create TabData from JSON
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">json['id'].toString()</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Get the 'id' field from JSON and convert it to a string
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">json['title'] ?? 'Untitled'</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Get 'title' from JSON, or use 'Untitled' if it's null/missing
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div class="property">
|
||
|
|
<span class="property-name">DateTime.parse(json['lastAccessed'])</span>
|
||
|
|
<span class="property-desc">
|
||
|
|
<strong>STATEMENT:</strong> Convert the date string from JSON into a DateTime object
|
||
|
|
</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 11: Common Operations -->
|
||
|
|
<div class="section">
|
||
|
|
<h2 class="section-title">⚙️ Common Operations with TabData</h2>
|
||
|
|
|
||
|
|
<div style="margin-top: 20px;">
|
||
|
|
<h3 style="margin-bottom: 15px;">Creating a new TabData:</h3>
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-keyword">final</span> newTab = <span class="code-class">TabData</span>(
|
||
|
|
id: <span class="code-string">'tab_001'</span>,
|
||
|
|
title: <span class="code-string">'My Website'</span>,
|
||
|
|
url: <span class="code-string">'https://example.com'</span>,
|
||
|
|
);
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h3 style="margin: 30px 0 15px 0;">Converting JSON to TabData:</h3>
|
||
|
|
<div class="code-block">
|
||
|
|
<span class="code-keyword">final</span> jsonData = {<span class="code-string">'id'</span>: <span class="code-string">'123'</span>, <span class="code-string">'title'</span>: <span class="code-string">'Test'</span>, <span class="code-string">'url'</span>: <span class="code-string">'...'</span>};
|
||
|
|
<span class="code-keyword">final</span> tab = <span class="code-class">TabData</span>.fromJson(jsonData);
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h3 style="margin: 30px 0 15px 0;">Accessing properties:</h3>
|
||
|
|
<div class="code-block">
|
||
|
|
print(tab.title); <span class="code-comment">// Print the title</span>
|
||
|
|
print(tab.url); <span class="code-comment">// Print the URL</span>
|
||
|
|
<span class="code-keyword">if</span> (tab.isPinned) { <span class="code-comment">// Check if pinned</span>
|
||
|
|
print(<span class="code-string">'This tab is pinned!'</span>);
|
||
|
|
}
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h3 style="margin: 30px 0 15px 0;">Creating a list of TabData:</h3>
|
||
|
|
<div class="code-block">
|
||
|
|
List<<span class="code-class">TabData</span>> allTabs = []; <span class="code-comment">// Empty list</span>
|
||
|
|
|
||
|
|
<span class="code-comment">// Add tabs to the list</span>
|
||
|
|
allTabs.add(newTab);
|
||
|
|
|
||
|
|
<span class="code-comment">// Convert multiple JSON items to TabData list</span>
|
||
|
|
<span class="code-keyword">final</span> tabs = jsonArray.map((item) => <span class="code-class">TabData</span>.fromJson(item)).toList();
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<h3 style="margin: 30px 0 15px 0;">Modifying properties:</h3>
|
||
|
|
<div class="code-block">
|
||
|
|
tab.title = <span class="code-string">'New Title'</span>; <span class="code-comment">// Change the title</span>
|
||
|
|
tab.isPinned = <span class="code-keyword">true</span>; <span class="code-comment">// Pin the tab</span>
|
||
|
|
tab.lastAccessed = DateTime.now(); <span class="code-comment">// Update last access time</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- SECTION 12: Summary -->
|
||
|
|
<div class="section" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white;">
|
||
|
|
<h2 style="color: white; border-color: white;">📚 Summary</h2>
|
||
|
|
|
||
|
|
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px; margin-top: 20px;">
|
||
|
|
<h3 style="color: #ffd700; margin-bottom: 15px;">TabData is the foundation of the entire app!</h3>
|
||
|
|
|
||
|
|
<ul style="list-style: none; padding: 0; margin-top: 20px;">
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">🎯</span>
|
||
|
|
<strong>Blueprint:</strong> Defines how we store tab/bookmark/history information
|
||
|
|
</li>
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">📋</span>
|
||
|
|
<strong>9 Properties:</strong> id, title, url, favicon, lastAccessed, isPinned, type, visitCount, folder
|
||
|
|
</li>
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">🏗️</span>
|
||
|
|
<strong>Constructor:</strong> Creates new TabData objects with required and optional fields
|
||
|
|
</li>
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">🔄</span>
|
||
|
|
<strong>fromJson:</strong> Converts browser API JSON data into TabData objects
|
||
|
|
</li>
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">🛡️</span>
|
||
|
|
<strong>Error Handling:</strong> Uses ?? operator and multiple checks to handle missing data
|
||
|
|
</li>
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">📅</span>
|
||
|
|
<strong>Smart Dates:</strong> Handles 4 different date field names from different browsers
|
||
|
|
</li>
|
||
|
|
<li style="margin: 15px 0; padding-left: 25px; position: relative;">
|
||
|
|
<span style="position: absolute; left: 0;">✨</span>
|
||
|
|
<strong>Clean Code:</strong> Makes working with browser data easy and type-safe
|
||
|
|
</li>
|
||
|
|
</ul>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div style="background: rgba(255,255,255,0.1); padding: 20px; border-radius: 8px; margin-top: 20px; text-align: center;">
|
||
|
|
<h3 style="color: #ffd700; margin-bottom: 10px;">💡 Remember</h3>
|
||
|
|
<p style="font-size: 1.1em;">
|
||
|
|
Every tab, bookmark, and history item you see in the app is a <strong>TabData object</strong>.
|
||
|
|
<br/><br/>
|
||
|
|
Without TabData, we'd just have messy JSON strings everywhere!
|
||
|
|
</p>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</body>
|
||
|
|
</html>
|