Skip to content

Commit

Permalink
refactor(web): plugin playground presets - camera position (#1415)
Browse files Browse the repository at this point in the history
  • Loading branch information
OpeDada authored Feb 10, 2025
1 parent c7b5acd commit 98a374b
Showing 1 changed file with 194 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ extensions:
type: widget
name: Camera Position Widget
description: Widget for controlling camera position
widgetLayout:
defaultLocation:
zone: outer
section: center
area: top
`,
disableEdit: true,
disableDelete: true
Expand All @@ -36,25 +31,33 @@ const widgetFile: FileType = {
reearth.ui.show(\`
${PRESET_PLUGIN_COMMON_STYLE}
<style>
#info-section {
position: sticky;
top: 0;
background: #eee;
text-align: left;
}
.camera-controls {
background-color: white;
border-radius: 5px;
padding: 24px;
padding: 18px;
display: flex;
flex-direction: column;
gap: 8px;
}
.input-group {
margin-bottom: 4px;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: 600;
}
.info-expanded .camera-controls {
max-height: 250px;
overflow-y: auto;
}
.input-group input {
width: 100%;
padding: 8px;
Expand All @@ -66,7 +69,6 @@ const widgetFile: FileType = {
.button-group {
display: flex;
justify-content: center;
gap: 10px;
padding-top: 12px;
margin: 0;
}
Expand All @@ -80,123 +82,193 @@ const widgetFile: FileType = {
font-weight: bold;
}
#refresh-btn {
background-color: #4CAF50;
}
#apply-btn {
background-color: #2196F3;
opacity: 0.5;
cursor: not-allowed;
}
#refresh-btn:active {
background-color: #45a049;
#apply-btn:not(:disabled) {
opacity: 1;
cursor: pointer;
}
#apply-btn:active {
#apply-btn:not(:disabled):active {
background-color: #1976D2;
}
.status-message {
text-align: center;
color: #666;
height: 4px;
padding-top: 4px;
padding: 4px;
}
#info-section {
margin: 8px 0;
text-align: left;
}
#info-toggle {
padding: 6px 8px;
background: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 12px;
}
#info-content {
background: #f9f9f9;
padding: 10px;
border-radius: 5px;
margin-top: 8px;
font-size: 12px;
line-height: 1.3;
}
</style>
<div id="wrapper">
<div class="camera-controls">
<div class="input-group">
<label for="lat">Latitude</label>
<input type="number" id="lat" step="0.0001">
</div>
<div class="input-group">
<label for="lng">Longitude</label>
<input type="number" id="lng" step="0.0001">
</div>
<div class="input-group">
<label for="height">Height (meters)</label>
<input type="number" id="height" step="1">
</div>
<div class="input-group">
<label for="heading">Heading (radians)</label>
<input type="number" id="heading" step="0.1">
</div>
<div class="input-group">
<label for="pitch">Pitch (radians)</label>
<input type="number" id="pitch" step="0.1">
</div>
<div class="input-group">
<label for="roll">Roll (radians)</label>
<input type="number" id="roll" step="0.1">
<div class="scrollable-content">
<div id="info-section">
<button id="info-toggle">Show Info</button>
<div id="info-content" style="display: none;">
<strong>How to Use the Plugin</strong><br><br>
1. <strong>Automatic Tracking</strong><br>
• Move the camera around the globe<br>
• Input fields will automatically update with current camera position<br><br>
2. <strong>Manual Positioning</strong><br>
• Enter desired values in any of the input fields<br>
• Click "Apply Position"<br>
• Camera will instantly move to the specified location.
</div>
</div>
<div class="button-group">
<button id="apply-btn">Apply Position</button>
<div class="camera-controls">
<div class="input-group">
<label for="lat">Latitude</label>
<input type="number" id="lat" step="0.0001">
</div>
<div class="input-group">
<label for="lng">Longitude</label>
<input type="number" id="lng" step="0.0001">
</div>
<div class="input-group">
<label for="height">Height (meters)</label>
<input type="number" id="height" step="1">
</div>
<div class="input-group">
<label for="heading">Heading (radians)</label>
<input type="number" id="heading" step="0.1">
</div>
<div class="input-group">
<label for="pitch">Pitch (radians)</label>
<input type="number" id="pitch" step="0.1">
</div>
<div class="input-group">
<label for="roll">Roll (radians)</label>
<input type="number" id="roll" step="0.1">
</div>
<div class="button-group">
<button id="apply-btn" disabled>Apply Position</button>
</div>
<div id="status-message" class="status-message"></div>
</div>
<div id="status-message" class="status-message"></div>
</div>
</div>
<script>
let preserveManualInput = null;
const inputs = {
lat: document.getElementById('lat'),
lng: document.getElementById('lng'),
height: document.getElementById('height'),
heading: document.getElementById('heading'),
pitch: document.getElementById('pitch'),
roll: document.getElementById('roll')
};
const statusMessage = document.getElementById('status-message');
// Apply camera position
document.getElementById('apply-btn').addEventListener('click', () => {
preserveManualInput = true;
const cameraParams = {};
// Collect ALL input values
Object.keys(inputs).forEach(key => {
const value = inputs[key].value.trim();
if (value !== '') {
cameraParams[key] = Number(value);
}
});
let preserveManualInput = null;
// Send message to apply camera position
parent.postMessage({
type: 'applyCameraPosition',
position: cameraParams
}, '*');
});
const inputs = {
lat: document.getElementById('lat'),
lng: document.getElementById('lng'),
height: document.getElementById('height'),
heading: document.getElementById('heading'),
pitch: document.getElementById('pitch'),
roll: document.getElementById('roll')
};
// Handle messages from extension
window.addEventListener('message', (e) => {
if (e.data.type === 'currentPosition') {
// Only update if not preserving manual input
if (!preserveManualInput) {
const position = e.data.position;
// Update input fields
Object.keys(inputs).forEach(key => {
if (position[key] !== undefined) {
inputs[key].value = Number(position[key]).toFixed(4);
} else {
inputs[key].value = '';
}
});
} else {
// Reset the flag after one update
preserveManualInput = false;
}
} else if (e.data.type === 'positionApplied') {
// Show status message for successful application
statusMessage.textContent = 'Camera position applied!';
const applyBtn = document.getElementById('apply-btn');
const statusMessage = document.getElementById('status-message');
// Function to check if any input has a value
function checkInputs() {
const hasInput = Object.values(inputs).some(input => input.value.trim() !== '');
applyBtn.disabled = !hasInput;
applyBtn.style.opacity = hasInput ? '1' : '0.5';
applyBtn.style.cursor = hasInput ? 'pointer' : 'not-allowed';
}
// Add event listeners to all inputs
Object.values(inputs).forEach(input => {
input.addEventListener('input', checkInputs);
});
// Initial check
checkInputs();
// Toggle info section
document.getElementById('info-toggle').addEventListener('click', () => {
const infoContent = document.getElementById('info-content');
const wrapper = document.getElementById('wrapper');
const isHidden = infoContent.style.display === 'none';
infoContent.style.display = isHidden ? 'block' : 'none';
wrapper.classList.toggle('info-expanded', isHidden);
document.getElementById('info-toggle').textContent = isHidden ? 'Hide Info' : 'Show Info';
});
// Apply camera position
document.getElementById('apply-btn').addEventListener('click', () => {
preserveManualInput = true;
const cameraParams = {};
// Collect ALL input values
Object.keys(inputs).forEach(key => {
const value = inputs[key].value.trim();
if (value !== '') {
cameraParams[key] = Number(value);
}
});
</script>
// Send message to apply camera position
parent.postMessage({
type: 'applyCameraPosition',
position: cameraParams
}, '*');
});
// Handle messages from extension
window.addEventListener('message', (e) => {
if (e.data.type === 'currentPosition') {
// Only update if not preserving manual input
if (!preserveManualInput) {
const position = e.data.position;
// Update input fields
Object.keys(inputs).forEach(key => {
if (position[key] !== undefined) {
inputs[key].value = Number(position[key]).toFixed(4);
} else {
inputs[key].value = '';
}
});
// Recheck inputs after automatic update
checkInputs();
} else {
// Reset the flag after one update
preserveManualInput = false;
}
} else if (e.data.type === 'positionApplied') {
// Show status message for successful application
statusMessage.textContent = 'Camera position applied!';
}
});
</script>
\`);
reearth.camera.on("move", (camera) => {
Expand All @@ -218,21 +290,31 @@ reearth.extension.on('message', (msg) => {
if (msg.type === 'applyCameraPosition') {
const params = msg.position;
reearth.camera.flyTo(
params,
{
duration: 0,
easing: (t) => t * t
}
// Get current camera position before flying
const currentPosition = reearth.camera.position || {};
// Check if the new position is actually different
const isPositionChanged = Object.keys(params).some(key =>
//Use a tolerance threshold to compare floating-point values
Math.abs((currentPosition[key] || 0) - (params[key] || 0)) > 0.0001
);
// Send confirmation message
reearth.ui.postMessage({
type: 'positionApplied'
});
if (isPositionChanged) {
reearth.camera.flyTo(
params,
{
duration: 0,
easing: (t) => t * t
}
);
// Send confirmation message
reearth.ui.postMessage({
type: 'positionApplied'
});
}
}
});
`
});`
};

export const cameraPosition: PluginType = {
Expand Down

0 comments on commit 98a374b

Please sign in to comment.