Added solar system selector.

This commit is contained in:
Krafpy
2022-07-21 21:30:52 +02:00
parent 9a8cba06e6
commit a88aea6472
16 changed files with 248 additions and 165 deletions

View File

@@ -10,7 +10,7 @@ solarSystem:
planetFarSize: 0.05 # size of planet sprites
satFarSize: 0.04 # size of satellites sprites
satDispRadii: 10 # minimum display distance of satellites (in radii of the scaled semi major axis)
spriteDispSOIMul: 18 # minimum display distance of sprites (in multiple of the SOI of the body to which they are attached)
spriteDispSOIMul: 18 # minimum display distance of sprites (in multiple of the SOI of the body to which they are attached)
mouseFocusDst: 25 # minimum distance to between body on screen and mouse to set focus (in pixels)
soiOpacity: 0.3 # the opacity of SOI spheres

8
data/systems.yml Normal file
View File

@@ -0,0 +1,8 @@
# File keeping track of the list of solar systems in this data folder
- name: Stock
folderName: stock
# Template:
# - name: New Solar System
# folderName: new-solar-system

View File

@@ -1,3 +1,5 @@
import { SolarSystem } from "../objects/system.js";
import { CameraController } from "../objects/camera.js";
import { FlybySequenceGenerator } from "../solvers/sequence-solver.js";
import { TimeSelector } from "./time-selector.js";
import { ErrorMessage } from "./error-msg.js";
@@ -12,7 +14,32 @@ import { FlybySequence } from "../solvers/sequence.js";
import { Trajectory } from "../solvers/trajectory.js";
import { Selector } from "./selector.js";
import { DiscreteRange } from "./range.js";
export function initEditor(controls, system, config, canvas, stopLoop) {
import { loadBodiesData, loadConfig } from "../utilities/data.js";
export async function initEditorWithSystem(systems, systemIndex) {
const canvas = document.getElementById("three-canvas");
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const config = await loadConfig(systems[systemIndex].folderName);
const camera = new THREE.PerspectiveCamera(config.rendering.fov, width / height, config.rendering.nearPlane, config.rendering.farPlane);
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvas });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
const bodiesData = await loadBodiesData(systems[systemIndex].folderName);
const system = new SolarSystem(bodiesData.sun, bodiesData.bodies, config);
system.fillSceneObjects(scene, canvas);
const controls = new CameraController(camera, canvas, system, config);
controls.targetBody = system.sun;
let stop = false;
let stopLoop = () => stop = true;
const loop = () => {
if (!stop)
requestAnimationFrame(loop);
controls.update();
system.update(controls);
renderer.render(scene, camera);
};
requestAnimationFrame(loop);
const systemTime = new TimeSelector("system", config);
const updateSystemTime = () => {
systemTime.validate();
@@ -30,6 +57,10 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
originSelector.select(config.editor.defaultOrigin);
const destSelector = new BodySelector("destination-selector", system);
destSelector.select(config.editor.defaultDest);
const systemSelector = new Selector("system-selector");
const optionNames = systems.map(s => s.name);
systemSelector.fill(optionNames);
systemSelector.select(systemIndex);
{
const maxSwingBys = new IntegerInput("max-swingbys");
const maxResonant = new IntegerInput("max-resonant-swingbys");
@@ -40,9 +71,11 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
maxSwingBys.assertValidity();
maxResonant.assertValidity();
maxBackLegs.assertValidity();
if (originSelector.body.attractor.id != destSelector.body.attractor.id)
const depBody = originSelector.body;
const destBody = destSelector.body;
if (depBody.attractor.id != destBody.attractor.id)
throw new Error("Origin and destination bodies must orbit the same body.");
if (originSelector.body.id == destSelector.body.id)
if (depBody.id == destBody.id)
throw new Error("Same origin and destination bodies.");
};
const generator = new FlybySequenceGenerator(system, config);
@@ -66,6 +99,7 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
};
const generateSequences = async () => {
paramsErr.hide();
systemSelector.disable();
try {
sequenceSelector.disable();
sequenceSelector.clear();
@@ -81,6 +115,7 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
}
finally {
progressMsg.hide();
systemSelector.enable();
}
};
new SubmitButton("sequence-btn").click(() => generateSequences());
@@ -108,6 +143,7 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
detailsSelector.disable();
stepSlider.disable();
const getSpan = (id) => document.getElementById(id);
const getDiv = (id) => document.getElementById(id);
const resultItems = {
dateSpan: getSpan("maneuvre-date"),
progradeDVSpan: getSpan("prograde-delta-v"),
@@ -123,43 +159,41 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
inclinationSpan: getSpan("flyby-inclination"),
detailsSelector: detailsSelector,
stepSlider: stepSlider,
maneuverDiv: document.getElementById("maneuvre-details"),
flybyDiv: document.getElementById("flyby-details")
maneuverDiv: getDiv("maneuvre-details"),
flybyDiv: getDiv("flyby-details")
};
const resetFoundTrajectory = () => {
systemTime.input(updateSystemTime);
deltaVPlot.reveal();
detailsSelector.clear();
detailsSelector.disable();
stepSlider.disable();
if (trajectory) {
if (trajectory)
trajectory.remove();
}
systemTime.input(updateSystemTime);
};
const displayFoundTrajectory = () => {
trajectory = new Trajectory(solver.bestSteps, system, config);
trajectory.draw(canvas);
trajectory.fillResultControls(resultItems, systemTime, controls);
detailsSelector.select(0);
detailsSelector.enable();
stepSlider.enable();
systemTime.input(() => {
updateSystemTime();
trajectory.updatePodPosition(systemTime);
});
detailsSelector.select(0);
detailsSelector.enable();
stepSlider.enable();
trajectory.updatePodPosition(systemTime);
console.log(solver.bestDeltaV);
};
const findTrajectory = async () => {
paramsErr.hide();
systemSelector.disable();
try {
let sequence;
if (customSequence.value == "") {
sequence = sequenceSelector.sequence;
}
else {
if (customSequence.value != "")
sequence = FlybySequence.fromString(customSequence.value, system);
}
else
sequence = sequenceSelector.sequence;
updateAltitudeRange(depAltitude, sequence.bodies[0]);
const seqLen = sequence.length;
updateAltitudeRange(destAltitude, sequence.bodies[seqLen - 1]);
@@ -180,8 +214,33 @@ export function initEditor(controls, system, config, canvas, stopLoop) {
paramsErr.show(err);
console.error(err);
}
finally {
systemSelector.enable();
}
};
new SubmitButton("search-btn").click(() => findTrajectory());
new StopButton("search-stop-btn").click(() => solver.cancel());
systemSelector.change((_, index) => {
stopLoop();
deltaVPlot.destroy();
detailsSelector.clear();
resultItems.maneuvreNumber.innerHTML = "--";
resultItems.endDateSpan.innerHTML = "--";
resultItems.dateSpan.innerHTML = "--";
resultItems.normalDVSpan.innerHTML = "--";
resultItems.radialDVSpan.innerHTML = "--";
resultItems.depDateSpan.innerHTML = "--";
resultItems.totalDVSpan.innerHTML = "--";
resultItems.periAltitudeSpan.innerHTML = "--";
resultItems.inclinationSpan.innerHTML = "--";
for (let i = scene.children.length - 1; i >= 0; i--) {
scene.remove(scene.children[i]);
}
camera.remove();
scene.remove();
renderer.dispose();
controls.dispose();
initEditorWithSystem(systems, index);
});
}
}

View File

@@ -58,6 +58,9 @@ export class EvolutionPlot {
this._chart.data.datasets[0].data = [];
this._chart.data.datasets[1].data = [];
}
destroy() {
this._chart.destroy();
}
reveal() {
this._container.hidden = false;
}

6
dist/main/main.js vendored
View File

@@ -1,12 +1,14 @@
import { initPageWithSystem } from "./page.js";
import { initEditorWithSystem } from "./editor/editor.js";
import { loadSystemsList } from "./utilities/data.js";
import { SpriteManager } from "./utilities/sprites.js";
import { WorkerManager } from "./utilities/worker.js";
window.onload = main;
async function main() {
const systems = await loadSystemsList();
await SpriteManager.loadSpriteMaterials();
const path = "dist/dedicated-workers/";
WorkerManager.createPool(path + "trajectory-optimizer.js", "trajectory-optimizer");
WorkerManager.createPool(path + "sequence-evaluator.js", "sequence-evaluator");
WorkerManager.createWorker(path + "sequence-generator.js", "sequence-generator");
initPageWithSystem();
initEditorWithSystem(systems, 0);
}

38
dist/main/page.js vendored
View File

@@ -1,38 +0,0 @@
import { initEditor } from "./editor/editor.js";
import { SolarSystem } from "./objects/system.js";
import { CameraController } from "./objects/camera.js";
import { loadConfig, loadBodiesData } from "./utilities/data.js";
export async function initPageWithSystem() {
const canvas = document.getElementById("three-canvas");
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const config = await loadConfig();
const camera = new THREE.PerspectiveCamera(config.rendering.fov, width / height, config.rendering.nearPlane, config.rendering.farPlane);
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({ antialias: true, canvas: canvas });
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
const bodiesData = await loadBodiesData();
const system = new SolarSystem(bodiesData.sun, bodiesData.bodies, config);
system.fillSceneObjects(scene, canvas);
const controls = new CameraController(camera, canvas, system, config);
controls.targetBody = system.sun;
let stop = false;
let stopLoop = () => stop = false;
initEditor(controls, system, config, canvas, stopLoop);
const loop = () => {
controls.update();
system.update(controls);
renderer.render(scene, camera);
if (!stop) {
requestAnimationFrame(loop);
}
};
requestAnimationFrame(loop);
}
export function clearWindow(scene) {
for (let i = scene.children.length - 1; i >= 0; i--) {
const obj = scene.children[i];
scene.remove(obj);
}
}

View File

@@ -3,16 +3,21 @@ async function readTextFile(path) {
const text = await response.text();
return text;
}
export async function loadBodiesData() {
const content = await readTextFile("./data/kspbodies.yml");
export async function loadBodiesData(systemFolder) {
const content = await readTextFile(`./data/${systemFolder}/bodies.yml`);
const data = jsyaml.load(content);
return {
sun: data[0],
bodies: data.slice(1)
};
}
export async function loadConfig() {
const content = await readTextFile("./data/config.yml");
export async function loadConfig(systemFolder) {
const content = await readTextFile(`./data/${systemFolder}/config.yml`);
const config = jsyaml.load(content);
return config;
}
export async function loadSystemsList() {
const content = await readTextFile("./data/systems.yml");
const systems = jsyaml.load(content);
return systems;
}

View File

@@ -204,9 +204,9 @@
<input type="number" value="0" name="system-hour" id="system-hour">
</div>
</div>
<p id="data-update">
Uses data displayed on <a href="https://wiki.kerbalspaceprogram.com/wiki/Main_Page">KSP's wiki</a> on summer 2021
</p>
<select id="system-selector" name="system-selector">
<!-- filled by JS -->
</select>
</div>
<canvas id="three-canvas" width="750" height="550"></canvas>

View File

@@ -15,8 +15,47 @@ import { Trajectory } from "../solvers/trajectory.js";
import { Selector } from "./selector.js";
import { DiscreteRange } from "./range.js";
import { OrbitingBody } from "../objects/body.js";
import { loadBodiesData, loadConfig } from "../utilities/data.js";
export function initEditor(controls: CameraController, system: SolarSystem, config: Config, canvas: HTMLCanvasElement, stopLoop: () => void){
export async function initEditorWithSystem(systems: SolarSystemData[], systemIndex: number){
const canvas = document.getElementById("three-canvas") as HTMLCanvasElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const config = await loadConfig(systems[systemIndex].folderName);
const camera = new THREE.PerspectiveCamera(
config.rendering.fov,
width / height,
config.rendering.nearPlane,
config.rendering.farPlane
);
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({antialias: true, canvas: canvas});
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
const bodiesData = await loadBodiesData(systems[systemIndex].folderName);
const system = new SolarSystem(bodiesData.sun, bodiesData.bodies, config);
system.fillSceneObjects(scene, canvas);
const controls = new CameraController(camera, canvas, system, config);
controls.targetBody = system.sun;
let stop = false;
let stopLoop = () => stop = true;
const loop = () => {
if(!stop) requestAnimationFrame(loop);
controls.update();
system.update(controls);
renderer.render(scene, camera);
}
requestAnimationFrame(loop);
// Setting up solar system time control
const systemTime = new TimeSelector("system", config);
const updateSystemTime = () => {
systemTime.validate();
@@ -40,6 +79,13 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
originSelector.select(config.editor.defaultOrigin);
const destSelector = new BodySelector("destination-selector", system);
destSelector.select(config.editor.defaultDest);
// Init the solar system selector
const systemSelector = new Selector("system-selector");
const optionNames = systems.map(s => s.name);
systemSelector.fill(optionNames);
systemSelector.select(systemIndex);
// callback is configured later
{
// Sequence generation parameters
@@ -53,11 +99,14 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
maxSwingBys.assertValidity();
maxResonant.assertValidity();
maxBackLegs.assertValidity();
if(originSelector.body.attractor.id != destSelector.body.attractor.id)
const depBody = originSelector.body;
const destBody = destSelector.body;
if(depBody.attractor.id != destBody.attractor.id)
throw new Error("Origin and destination bodies must orbit the same body.");
if(originSelector.body.id == destSelector.body.id)
if(depBody.id == destBody.id)
throw new Error("Same origin and destination bodies.");
}
@@ -73,7 +122,6 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
const percent = Math.floor(100 * generator.progression / generator.totalFeasible);
progressMsg.setMessage(`Evaluation sequences : ${percent}%`);
};
const params = {
departureId: originSelector.body.id,
destinationId: destSelector.body.id,
@@ -82,20 +130,19 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
maxResonant: maxResonant.value,
maxBackLegs: maxBackLegs.value,
};
const sequences = await generator.generateFlybySequences(params, onProgress);
sequenceSelector.fillFrom(sequences);
}
const generateSequences = async () => {
paramsErr.hide();
systemSelector.disable();
try {
sequenceSelector.disable();
sequenceSelector.clear();
progressMsg.enable(1000);
assertSequenceInputs();
assertSequenceInputs();
await runSequenceGeneration();
sequenceSelector.enable();
@@ -107,6 +154,7 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
} finally {
progressMsg.hide();
systemSelector.enable();
}
}
@@ -152,6 +200,7 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
stepSlider.disable();
const getSpan = (id: string) => document.getElementById(id) as HTMLSpanElement;
const getDiv = (id: string) => document.getElementById(id) as HTMLDivElement;
const resultItems = {
dateSpan: getSpan("maneuvre-date"),
@@ -161,29 +210,24 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
depDateSpan: getSpan("result-departure-date") ,
totalDVSpan: getSpan("result-total-delta-v"),
maneuvreNumber: getSpan("maneuvre-number"),
flybyNumberSpan: getSpan("flyby-number"),
startDateSpan: getSpan("flyby-start-date"),
endDateSpan: getSpan("flyby-end-date"),
periAltitudeSpan: getSpan("flyby-periapsis-altitude"),
inclinationSpan: getSpan("flyby-inclination"),
detailsSelector: detailsSelector,
stepSlider: stepSlider,
maneuverDiv: document.getElementById("maneuvre-details") as HTMLDivElement,
flybyDiv: document.getElementById("flyby-details") as HTMLDivElement
maneuverDiv: getDiv("maneuvre-details"),
flybyDiv: getDiv("flyby-details")
};
const resetFoundTrajectory = () => {
systemTime.input(updateSystemTime);
deltaVPlot.reveal();
detailsSelector.clear();
detailsSelector.disable();
stepSlider.disable();
if(trajectory){
trajectory.remove();
}
systemTime.input(updateSystemTime);
if(trajectory) trajectory.remove();
}
const displayFoundTrajectory = () => {
@@ -191,15 +235,14 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
trajectory.draw(canvas);
trajectory.fillResultControls(resultItems, systemTime, controls);
detailsSelector.select(0);
detailsSelector.enable();
stepSlider.enable();
systemTime.input(() => {
updateSystemTime();
//@ts-ignore
trajectory.updatePodPosition(systemTime);
});
detailsSelector.select(0);
detailsSelector.enable();
stepSlider.enable();
trajectory.updatePodPosition(systemTime);
console.log(solver.bestDeltaV);
@@ -207,13 +250,13 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
const findTrajectory = async () => {
paramsErr.hide();
systemSelector.disable();
try {
let sequence: FlybySequence;
if(customSequence.value == ""){
sequence = sequenceSelector.sequence;
} else {
if(customSequence.value != "")
sequence = FlybySequence.fromString(customSequence.value, system);
}
else
sequence = sequenceSelector.sequence;
updateAltitudeRange(depAltitude, sequence.bodies[0]);
const seqLen = sequence.length;
@@ -241,11 +284,41 @@ export function initEditor(controls: CameraController, system: SolarSystem, conf
if(err instanceof Error && err.message != "TRAJECTORY FINDER CANCELLED")
paramsErr.show(err);
console.error(err);
} finally {
systemSelector.enable();
}
};
// Trajectory solver buttons
new SubmitButton("search-btn").click(() => findTrajectory());
new StopButton("search-stop-btn").click(() => solver.cancel());
// Configure the system selector callback
systemSelector.change((_, index) => {
stopLoop();
deltaVPlot.destroy();
detailsSelector.clear();
resultItems.maneuvreNumber.innerHTML = "--";
resultItems.endDateSpan.innerHTML = "--";
resultItems.dateSpan.innerHTML = "--";
resultItems.normalDVSpan.innerHTML = "--";
resultItems.radialDVSpan.innerHTML = "--";
resultItems.depDateSpan.innerHTML = "--";
resultItems.totalDVSpan.innerHTML = "--";
resultItems.periAltitudeSpan.innerHTML = "--";
resultItems.inclinationSpan.innerHTML = "--";
for(let i = scene.children.length - 1; i >= 0; i--){
scene.remove(scene.children[i]);
}
camera.remove();
scene.remove();
renderer.dispose();
controls.dispose();
initEditorWithSystem(systems, index);
});
}
}
}

View File

@@ -1,6 +1,8 @@
export class EvolutionPlot {
private readonly _chart!: Chart;
private readonly _container!: HTMLDivElement;
private readonly _chart!: Chart;
private readonly _container!: HTMLDivElement;
private static _instance: EvolutionPlot;
constructor(canvasId: string){
const data = {
@@ -75,6 +77,10 @@ export class EvolutionPlot {
this._chart.data.datasets[1].data = [];
}
public destroy(){
this._chart.destroy();
}
public reveal(){
this._container.hidden = false;
}

View File

@@ -1,15 +1,14 @@
/*import { SolarSystem } from "./objects/system.js";
import { FlybySequenceGenerator } from "./solvers/sequence-solver.js";
import { TrajectorySolver } from "./solvers/trajectory-solver.js";
import { Trajectory } from "./solvers/trajectory.js";*/
import { initPageWithSystem } from "./page.js";
import { initEditorWithSystem } from "./editor/editor.js";
import { loadSystemsList } from "./utilities/data.js";
import { SpriteManager } from "./utilities/sprites.js";
import { WorkerManager } from "./utilities/worker.js";
window.onload = main;
async function main(){
const systems = await loadSystemsList();
await SpriteManager.loadSpriteMaterials();
const path = "dist/dedicated-workers/";
@@ -17,5 +16,5 @@ async function main(){
WorkerManager.createPool(path + "sequence-evaluator.js", "sequence-evaluator");
WorkerManager.createWorker(path + "sequence-generator.js", "sequence-generator");
initPageWithSystem();
initEditorWithSystem(systems, 0);
}

View File

@@ -1,54 +0,0 @@
import { initEditor } from "./editor/editor.js";
import { SolarSystem } from "./objects/system.js";
import { CameraController } from "./objects/camera.js";
import { loadConfig, loadBodiesData } from "./utilities/data.js";
export async function initPageWithSystem(){
const canvas = document.getElementById("three-canvas") as HTMLCanvasElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const config = await loadConfig();
const camera = new THREE.PerspectiveCamera(
config.rendering.fov,
width / height,
config.rendering.nearPlane,
config.rendering.farPlane
);
const scene = new THREE.Scene();
const renderer = new THREE.WebGLRenderer({antialias: true, canvas: canvas});
renderer.setSize(canvas.clientWidth, canvas.clientHeight);
renderer.setPixelRatio(window.devicePixelRatio);
const bodiesData = await loadBodiesData();
const system = new SolarSystem(bodiesData.sun, bodiesData.bodies, config);
system.fillSceneObjects(scene, canvas);
const controls = new CameraController(camera, canvas, system, config);
controls.targetBody = system.sun;
let stop = false;
let stopLoop = () => stop = false;
initEditor(controls, system, config, canvas, stopLoop);
const loop = () => {
controls.update();
system.update(controls);
renderer.render(scene, camera);
if(!stop){
requestAnimationFrame(loop);
}
}
requestAnimationFrame(loop);
}
export function clearWindow(scene: THREE.Scene){
for(let i = scene.children.length - 1; i >= 0; i--) {
const obj = scene.children[i];
scene.remove(obj);
}
}

View File

@@ -4,8 +4,8 @@ async function readTextFile(path: string){
return text;
}
export async function loadBodiesData(){
const content = await readTextFile("./data/kspbodies.yml");
export async function loadBodiesData(systemFolder: string){
const content = await readTextFile(`./data/${systemFolder}/bodies.yml`);
const data = jsyaml.load(content) as object[];
return {
sun: data[0] as ICelestialBody,
@@ -13,8 +13,14 @@ export async function loadBodiesData(){
};
}
export async function loadConfig(){
const content = await readTextFile("./data/config.yml");
export async function loadConfig(systemFolder: string){
const content = await readTextFile(`./data/${systemFolder}/config.yml`);
const config = jsyaml.load(content) as Config;
return config;
}
export async function loadSystemsList(){
const content = await readTextFile("./data/systems.yml");
const systems = jsyaml.load(content) as SolarSystemData[];
return systems;
}

14
src/types.d.ts vendored
View File

@@ -322,4 +322,18 @@ type DetailsSelectorOption = {
index: number,
type: "maneuver" | "flyby",
origin: number
};
type EditorParameters = {
controls: CameraController,
system: SolarSystem,
config: Config,
canvas: HTMLCanvasElement,
stopLoop: () => void,
systems: SolarSystemData[]
};
type SolarSystemData = {
name: string;
folderName: string;
};

View File

@@ -60,18 +60,18 @@ strong
margin-top: 30px;
}
#data-update
{
font-weight: bold;
color: rgba(255, 255, 255, 0.459);
}
#canvas-top-bar
{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-end;
margin-bottom: 10px;
}
#canvas-top-bar .time-selector
{
margin-bottom: 0;
}
/* Editor forms styles */
@@ -236,10 +236,10 @@ input[type="text"]::placeholder
margin-top: 25px;
}
#custom-sequence-group
/*#custom-sequence-group
{
margin-top: 25px;
}
}*/
/* Editor forms' action buttons */