Buckets:
| import { useState } from 'react'; | |
| import { poissonApi } from '../../api'; | |
| const DEFAULT_PARAMS = { | |
| name: 'Simulation Poisson', | |
| domain_x_min: 0, | |
| domain_x_max: 2, | |
| domain_y_min: 0, | |
| domain_y_max: 1, | |
| mesh_nx: 64, | |
| mesh_ny: 32, | |
| source_type: 'gaussian', | |
| source_amplitude: 10, | |
| source_center_x: 0.5, | |
| source_center_y: 0.5, | |
| source_width: 0.02, | |
| source_frequency: 1, | |
| dirichlet_enabled: true, | |
| dirichlet_boundaries: 'left,right', | |
| dirichlet_value: 0, | |
| neumann_enabled: true, | |
| neumann_boundaries: 'top,bottom', | |
| neumann_type: 'sinusoidal', | |
| neumann_amplitude: 1, | |
| neumann_frequency: 5, | |
| }; | |
| export default function PoissonForm({ onPreviewMesh, onSimulationCreated, loading, setLoading }) { | |
| const [params, setParams] = useState(DEFAULT_PARAMS); | |
| const [activeSection, setActiveSection] = useState('domain'); | |
| const handleChange = (field, value) => { | |
| setParams(prev => ({ ...prev, [field]: value })); | |
| }; | |
| const handlePreview = async () => { | |
| setLoading(true); | |
| try { | |
| await onPreviewMesh(params); | |
| } catch (err) { | |
| console.error('Erreur preview:', err); | |
| } | |
| setLoading(false); | |
| }; | |
| const handleSubmit = async (e) => { | |
| e.preventDefault(); | |
| setLoading(true); | |
| try { | |
| const simulation = await poissonApi.createSimulation(params); | |
| onSimulationCreated(simulation); | |
| } catch (err) { | |
| console.error('Erreur création:', err); | |
| alert('Erreur: ' + err.message); | |
| } | |
| setLoading(false); | |
| }; | |
| const toggleBoundary = (field, boundary) => { | |
| const current = params[field].split(',').filter(b => b); | |
| const index = current.indexOf(boundary); | |
| if (index >= 0) { | |
| current.splice(index, 1); | |
| } else { | |
| current.push(boundary); | |
| } | |
| handleChange(field, current.join(',')); | |
| }; | |
| const isBoundarySelected = (field, boundary) => { | |
| return params[field].split(',').includes(boundary); | |
| }; | |
| return ( | |
| <form className="poisson-form" onSubmit={handleSubmit}> | |
| <h3>Paramètres de simulation</h3> | |
| {/* Section Domaine */} | |
| <div className={`form-section ${activeSection === 'domain' ? 'expanded' : ''}`}> | |
| <button | |
| type="button" | |
| className="section-header" | |
| onClick={() => setActiveSection(activeSection === 'domain' ? '' : 'domain')} | |
| > | |
| <span>Domaine & Maillage</span> | |
| <span className="toggle">{activeSection === 'domain' ? '−' : '+'}</span> | |
| </button> | |
| {activeSection === 'domain' && ( | |
| <div className="section-content"> | |
| <div className="form-group"> | |
| <label>Nom de la simulation</label> | |
| <input | |
| type="text" | |
| value={params.name} | |
| onChange={(e) => handleChange('name', e.target.value)} | |
| /> | |
| </div> | |
| <div className="form-row"> | |
| <div className="form-group"> | |
| <label>X min</label> | |
| <input | |
| type="number" | |
| step="0.1" | |
| value={params.domain_x_min} | |
| onChange={(e) => handleChange('domain_x_min', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| <div className="form-group"> | |
| <label>X max</label> | |
| <input | |
| type="number" | |
| step="0.1" | |
| value={params.domain_x_max} | |
| onChange={(e) => handleChange('domain_x_max', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| </div> | |
| <div className="form-row"> | |
| <div className="form-group"> | |
| <label>Y min</label> | |
| <input | |
| type="number" | |
| step="0.1" | |
| value={params.domain_y_min} | |
| onChange={(e) => handleChange('domain_y_min', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| <div className="form-group"> | |
| <label>Y max</label> | |
| <input | |
| type="number" | |
| step="0.1" | |
| value={params.domain_y_max} | |
| onChange={(e) => handleChange('domain_y_max', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| </div> | |
| <div className="form-row"> | |
| <div className="form-group"> | |
| <label>Éléments X: {params.mesh_nx}</label> | |
| <input | |
| type="range" | |
| min="8" | |
| max="128" | |
| step="8" | |
| value={params.mesh_nx} | |
| onChange={(e) => handleChange('mesh_nx', parseInt(e.target.value))} | |
| /> | |
| </div> | |
| <div className="form-group"> | |
| <label>Éléments Y: {params.mesh_ny}</label> | |
| <input | |
| type="range" | |
| min="8" | |
| max="128" | |
| step="8" | |
| value={params.mesh_ny} | |
| onChange={(e) => handleChange('mesh_ny', parseInt(e.target.value))} | |
| /> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| {/* Section Source */} | |
| <div className={`form-section ${activeSection === 'source' ? 'expanded' : ''}`}> | |
| <button | |
| type="button" | |
| className="section-header" | |
| onClick={() => setActiveSection(activeSection === 'source' ? '' : 'source')} | |
| > | |
| <span>Terme Source (f)</span> | |
| <span className="toggle">{activeSection === 'source' ? '−' : '+'}</span> | |
| </button> | |
| {activeSection === 'source' && ( | |
| <div className="section-content"> | |
| <div className="form-group"> | |
| <label>Type de source</label> | |
| <select | |
| value={params.source_type} | |
| onChange={(e) => handleChange('source_type', e.target.value)} | |
| > | |
| <option value="gaussian">Gaussienne</option> | |
| <option value="constant">Constante</option> | |
| <option value="sinusoidal">Sinusoïdale</option> | |
| </select> | |
| </div> | |
| <div className="form-group"> | |
| <label>Amplitude: {params.source_amplitude}</label> | |
| <input | |
| type="range" | |
| min="-50" | |
| max="50" | |
| step="1" | |
| value={params.source_amplitude} | |
| onChange={(e) => handleChange('source_amplitude', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| {params.source_type === 'gaussian' && ( | |
| <> | |
| <div className="form-row"> | |
| <div className="form-group"> | |
| <label>Centre X: {params.source_center_x}</label> | |
| <input | |
| type="range" | |
| min="0" | |
| max="2" | |
| step="0.1" | |
| value={params.source_center_x} | |
| onChange={(e) => handleChange('source_center_x', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| <div className="form-group"> | |
| <label>Centre Y: {params.source_center_y}</label> | |
| <input | |
| type="range" | |
| min="0" | |
| max="1" | |
| step="0.1" | |
| value={params.source_center_y} | |
| onChange={(e) => handleChange('source_center_y', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| </div> | |
| <div className="form-group"> | |
| <label>Largeur (σ²): {params.source_width}</label> | |
| <input | |
| type="range" | |
| min="0.01" | |
| max="0.5" | |
| step="0.01" | |
| value={params.source_width} | |
| onChange={(e) => handleChange('source_width', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| </> | |
| )} | |
| {params.source_type === 'sinusoidal' && ( | |
| <div className="form-group"> | |
| <label>Fréquence: {params.source_frequency}</label> | |
| <input | |
| type="range" | |
| min="0.5" | |
| max="5" | |
| step="0.5" | |
| value={params.source_frequency} | |
| onChange={(e) => handleChange('source_frequency', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| {/* Section Dirichlet */} | |
| <div className={`form-section ${activeSection === 'dirichlet' ? 'expanded' : ''}`}> | |
| <button | |
| type="button" | |
| className="section-header" | |
| onClick={() => setActiveSection(activeSection === 'dirichlet' ? '' : 'dirichlet')} | |
| > | |
| <span>Condition Dirichlet</span> | |
| <span className="toggle">{activeSection === 'dirichlet' ? '−' : '+'}</span> | |
| </button> | |
| {activeSection === 'dirichlet' && ( | |
| <div className="section-content"> | |
| <div className="form-group checkbox"> | |
| <label> | |
| <input | |
| type="checkbox" | |
| checked={params.dirichlet_enabled} | |
| onChange={(e) => handleChange('dirichlet_enabled', e.target.checked)} | |
| /> | |
| Activer Dirichlet (u = valeur) | |
| </label> | |
| </div> | |
| {params.dirichlet_enabled && ( | |
| <> | |
| <div className="form-group"> | |
| <label>Frontières</label> | |
| <div className="boundary-selector"> | |
| {['left', 'right', 'top', 'bottom'].map(b => ( | |
| <button | |
| key={b} | |
| type="button" | |
| className={`boundary-btn ${isBoundarySelected('dirichlet_boundaries', b) ? 'selected' : ''}`} | |
| onClick={() => toggleBoundary('dirichlet_boundaries', b)} | |
| > | |
| {b === 'left' ? 'Gauche' : | |
| b === 'right' ? 'Droite' : | |
| b === 'top' ? 'Haut' : 'Bas'} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="form-group"> | |
| <label>Valeur: {params.dirichlet_value}</label> | |
| <input | |
| type="range" | |
| min="-10" | |
| max="10" | |
| step="0.5" | |
| value={params.dirichlet_value} | |
| onChange={(e) => handleChange('dirichlet_value', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| </> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| {/* Section Neumann */} | |
| <div className={`form-section ${activeSection === 'neumann' ? 'expanded' : ''}`}> | |
| <button | |
| type="button" | |
| className="section-header" | |
| onClick={() => setActiveSection(activeSection === 'neumann' ? '' : 'neumann')} | |
| > | |
| <span>Condition Neumann</span> | |
| <span className="toggle">{activeSection === 'neumann' ? '−' : '+'}</span> | |
| </button> | |
| {activeSection === 'neumann' && ( | |
| <div className="section-content"> | |
| <div className="form-group checkbox"> | |
| <label> | |
| <input | |
| type="checkbox" | |
| checked={params.neumann_enabled} | |
| onChange={(e) => handleChange('neumann_enabled', e.target.checked)} | |
| /> | |
| Activer Neumann (∂u/∂n = g) | |
| </label> | |
| </div> | |
| {params.neumann_enabled && ( | |
| <> | |
| <div className="form-group"> | |
| <label>Frontières</label> | |
| <div className="boundary-selector"> | |
| {['left', 'right', 'top', 'bottom'].map(b => ( | |
| <button | |
| key={b} | |
| type="button" | |
| className={`boundary-btn ${isBoundarySelected('neumann_boundaries', b) ? 'selected' : ''}`} | |
| onClick={() => toggleBoundary('neumann_boundaries', b)} | |
| > | |
| {b === 'left' ? 'Gauche' : | |
| b === 'right' ? 'Droite' : | |
| b === 'top' ? 'Haut' : 'Bas'} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="form-group"> | |
| <label>Type de flux</label> | |
| <select | |
| value={params.neumann_type} | |
| onChange={(e) => handleChange('neumann_type', e.target.value)} | |
| > | |
| <option value="constant">Constant</option> | |
| <option value="sinusoidal">Sinusoïdal</option> | |
| <option value="linear">Linéaire</option> | |
| </select> | |
| </div> | |
| <div className="form-group"> | |
| <label>Amplitude: {params.neumann_amplitude}</label> | |
| <input | |
| type="range" | |
| min="-10" | |
| max="10" | |
| step="0.5" | |
| value={params.neumann_amplitude} | |
| onChange={(e) => handleChange('neumann_amplitude', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| {params.neumann_type === 'sinusoidal' && ( | |
| <div className="form-group"> | |
| <label>Fréquence: {params.neumann_frequency}</label> | |
| <input | |
| type="range" | |
| min="1" | |
| max="20" | |
| step="1" | |
| value={params.neumann_frequency} | |
| onChange={(e) => handleChange('neumann_frequency', parseFloat(e.target.value))} | |
| /> | |
| </div> | |
| )} | |
| </> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| {/* Boutons d'action */} | |
| <div className="form-actions"> | |
| <button | |
| type="button" | |
| className="btn-preview" | |
| onClick={handlePreview} | |
| disabled={loading} | |
| > | |
| Aperçu du maillage | |
| </button> | |
| <button | |
| type="submit" | |
| className="btn-submit" | |
| disabled={loading} | |
| > | |
| {loading ? 'Calcul...' : 'Lancer la simulation'} | |
| </button> | |
| </div> | |
| </form> | |
| ); | |
| } | |
Xet Storage Details
- Size:
- 20.7 kB
- Xet hash:
- 65ae624d707a98e95ad90e11b03092bd823f846382b47e873df3a22e33858c13
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.