Alonso Torres f993f203bd
🐛 Fix problems with plugins API (#10412)
* ✨ Adds static dispatch safe stubs in tests

* 🐛 Fix shapesColors metadata key to match ColorShapeInfo

* 🐛 Fix CommentThread.remove rejecting the owner's own threads

* 🐛 Fix page.removeCommentThread throwing on a spurious Promise

* ✨ Implement ShapeBase.swapComponent in the plugin API

* ✨ Expose File.revn in the plugin API

* 🐛 Fix FileVersion.createdAt calling Luxon method on a js/Date

* 🐛 Fix plugin font/typography application to text and ranges

* 🐛 Default plugin overlay interaction position for non-manual types

* 🐛 Fix plugin interaction setters passing an id-only shape

* 🐛 Fix grid addColumnAtIndex rejecting valid track types

* 🐛 Expose libraryId on library color/typography/component proxies

* ✨ Implement LibraryTypography.setFont in the plugin API

* 🐛 Fix typography.applyToTextRange reading unexposed range bounds

* 🐛 Fix utils.geometry.center argument mismatch

* 🐛 Fix localStorage.removeItem calling getItem

* 🐛 Fix shape backgroundBlur proxy key casing

* 🐛 Report boolean shape type as 'boolean' in the plugin API

* 🐛 Return the resulting paths from plugin flatten

* 🐛 Make plugin z-order methods act on the target shape

* 🐛 Make is-variant-container? return a boolean

* ✨ Implement Group.isMask in the plugin API

* 🐛 Return a shape proxy from TextRange.shape

* 🐛 Return the duplicated set from TokenSet.duplicate

* 🐛 Fix theme addSet/removeSet reading set name with a keyword

* 🐛 Accept string fontFamilies token value in the plugin API

* 🐛 Fix combineAsVariants ignoring the passed component ids

* 🐛 Fix board removeRulerGuide ignoring its argument

* 🐛 Fix board guides setter schema and parser

* 🐛 Avoid 0-byte allocation when syncing empty grid tracks

* 🐛 Validate grid track indices in the plugin API

* 🐛 Return null for empty input in group() and centerShapes()

* 🐛 Return TokenTypographyValue[] from a typography token's resolvedValue

* 🐛 Return TokenShadowValue[] from a shadow token's resolvedValue

* 🐛 Return string[] from a fontFamilies token's resolvedValue

* 🐛 Clear mutually-exclusive reps when setting LibraryColor gradient/image

* 🐛 Add readonly tags to types, deprecate Image type

* 📚 Update plugins changelog
2026-06-29 17:32:15 +02:00

272 lines
6.9 KiB
Rust

use macros::{wasm_error, ToJs};
use crate::get_render_state;
use crate::mem;
use crate::shapes::{GridCell, GridDirection, GridTrack, GridTrackType};
use crate::uuid::Uuid;
use crate::{uuid_from_u32_quartet, with_current_shape_mut, with_state};
use super::align;
#[allow(unused_imports)]
use crate::error::{Error, Result};
#[derive(Debug)]
#[repr(C, align(1))]
struct RawGridCell {
row: i32,
row_span: i32,
column: i32,
column_span: i32,
align_self: u8,
justify_self: u8,
_padding: u16,
shape_id_a: u32,
shape_id_b: u32,
shape_id_c: u32,
shape_id_d: u32,
}
impl From<[u8; size_of::<RawGridCell>()]> for RawGridCell {
fn from(bytes: [u8; size_of::<RawGridCell>()]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl From<RawGridCell> for GridCell {
fn from(raw: RawGridCell) -> Self {
let raw_justify_self = super::align::RawJustifySelf::from(raw.justify_self);
let raw_align_self = super::align::RawAlignSelf::from(raw.align_self);
let shape_id = uuid_from_u32_quartet(
raw.shape_id_a,
raw.shape_id_b,
raw.shape_id_c,
raw.shape_id_d,
);
Self {
row: raw.row,
row_span: raw.row_span,
column: raw.column,
column_span: raw.column_span,
align_self: raw_align_self.try_into().ok(),
justify_self: raw_justify_self.try_into().ok(),
shape: if shape_id != Uuid::nil() {
Some(shape_id)
} else {
None
},
}
}
}
#[derive(Debug, Clone, PartialEq, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
pub enum RawGridDirection {
Row = 0,
Column = 1,
}
impl From<u8> for RawGridDirection {
fn from(value: u8) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<RawGridDirection> for GridDirection {
fn from(value: RawGridDirection) -> Self {
match value {
RawGridDirection::Row => Self::Row,
RawGridDirection::Column => Self::Column,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, ToJs)]
#[repr(u8)]
#[allow(dead_code)]
pub enum RawGridTrackType {
Percent = 0,
Flex = 1,
Auto = 2,
Fixed = 3,
}
impl From<u8> for RawGridTrackType {
fn from(value: u8) -> Self {
unsafe { std::mem::transmute(value) }
}
}
impl From<RawGridTrackType> for GridTrackType {
fn from(value: RawGridTrackType) -> Self {
match value {
RawGridTrackType::Percent => Self::Percent,
RawGridTrackType::Flex => Self::Flex,
RawGridTrackType::Auto => Self::Auto,
RawGridTrackType::Fixed => Self::Fixed,
}
}
}
#[derive(Debug)]
#[repr(C, align(4))]
#[allow(dead_code)]
pub struct RawGridTrack {
track_type: RawGridTrackType,
_padding: [u8; 3],
value: f32,
}
impl From<[u8; size_of::<RawGridTrack>()]> for RawGridTrack {
fn from(bytes: [u8; size_of::<RawGridTrack>()]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl From<RawGridTrack> for GridTrack {
fn from(raw: RawGridTrack) -> Self {
Self {
track_type: raw.track_type.into(),
value: raw.value,
}
}
}
#[no_mangle]
pub extern "C" fn set_grid_layout_data(
dir: u8,
row_gap: f32,
column_gap: f32,
align_items: u8,
align_content: u8,
justify_items: u8,
justify_content: u8,
padding_top: f32,
padding_right: f32,
padding_bottom: f32,
padding_left: f32,
) {
let dir = RawGridDirection::from(dir);
let align_items = align::RawAlignItems::from(align_items);
let align_content = align::RawAlignContent::from(align_content);
let justify_items = align::RawJustifyItems::from(justify_items);
let justify_content = align::RawJustifyContent::from(justify_content);
with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_grid_layout_data(
dir.into(),
row_gap,
column_gap,
align_items.into(),
align_content.into(),
justify_items.into(),
justify_content.into(),
padding_top,
padding_right,
padding_bottom,
padding_left,
);
});
}
#[no_mangle]
#[wasm_error]
pub extern "C" fn set_grid_columns() -> Result<()> {
let bytes = mem::bytes_or_empty();
let entries: Vec<GridTrack> = bytes
.chunks(size_of::<RawGridTrack>())
.map(|data| {
let track_bytes: [u8; size_of::<RawGridTrack>()] = data
.try_into()
.map_err(|_| Error::CriticalError("Invalid bytes for grid track".to_string()))?;
Ok(RawGridTrack::from(track_bytes).into())
})
.collect::<Result<Vec<_>>>()?;
with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_grid_columns(entries);
});
mem::free_bytes()?;
Ok(())
}
#[no_mangle]
#[wasm_error]
pub extern "C" fn set_grid_rows() -> Result<()> {
let bytes = mem::bytes_or_empty();
let entries: Vec<GridTrack> = bytes
.chunks(size_of::<RawGridTrack>())
.map(|data| {
let track_bytes: [u8; size_of::<RawGridTrack>()] = data
.try_into()
.map_err(|_| Error::CriticalError("Invalid bytes for grid track".to_string()))?;
Ok(RawGridTrack::from(track_bytes).into())
})
.collect::<Result<Vec<_>>>()?;
with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_grid_rows(entries);
});
mem::free_bytes()?;
Ok(())
}
#[no_mangle]
#[wasm_error]
pub extern "C" fn set_grid_cells() -> Result<()> {
let bytes = mem::bytes_or_empty();
let cells: Vec<RawGridCell> = bytes
.chunks(size_of::<RawGridCell>())
.map(|data| {
let cell_bytes: [u8; size_of::<RawGridCell>()] = data
.try_into()
.map_err(|_| Error::CriticalError("Invalid bytes for grid cell".to_string()))?;
Ok(RawGridCell::from(cell_bytes))
})
.collect::<Result<Vec<_>>>()?;
with_current_shape_mut!(state, |shape: &mut Shape| {
shape.set_grid_cells(cells.into_iter().map(|raw| raw.into()).collect());
});
mem::free_bytes()?;
Ok(())
}
#[no_mangle]
pub extern "C" fn show_grid(a: u32, b: u32, c: u32, d: u32) {
let id = uuid_from_u32_quartet(a, b, c, d);
get_render_state().show_grid = Some(id);
}
#[no_mangle]
pub extern "C" fn hide_grid() {
get_render_state().show_grid = None;
}
#[no_mangle]
pub extern "C" fn get_grid_coords(pos_x: f32, pos_y: f32) -> *mut u8 {
let row: i32;
let col: i32;
with_state!(state, {
if let Some((r, c)) = state.get_grid_coords(pos_x, pos_y) {
row = r;
col = c;
} else {
row = -1;
col = -1;
};
});
let mut bytes = vec![0; 8];
bytes[0..4].clone_from_slice(&row.to_le_bytes());
bytes[4..8].clone_from_slice(&col.to_le_bytes());
mem::write_bytes(bytes)
}