Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions lib/css/shell/main.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
@import url("./docs.css");
@import url("./type-stage.css");

.hidden {
display: none !important;
}

/******************************************************************************
material symbols
*******************************************************************************/
Expand Down Expand Up @@ -1958,11 +1962,22 @@ prosemirror menu
}
}

.ui_prose_mirror_menu-simple {
/******************************************************************************
styles floating menu
*******************************************************************************/

.ui-styles-floating-menu {
background-color: #eee;
border: 1px solid #ddd;
border-radius: 4px;
box-shadow: rgb(0 0 0 / 10%) 0 0.25em 0.5em;
display: flex;
gap: 0.25em;
height: auto;
margin-bottom: 1em;
padding: 0.25em;
position: fixed;
z-index: 2;

.ui_prose_mirror_menu-container-blocks,
.typeroof-ui-label {
Expand All @@ -1974,9 +1989,8 @@ prosemirror menu
padding: 0.25rem;

&.active {
color: white;
background: var(--button-active-color);
border-color: var(--button-active-color);
outline: 1px solid var(--button-active-color);
}
}

Expand Down
6 changes: 6 additions & 0 deletions lib/js/components/layouts/ramp/index.typeroof.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import {
PathModelOrEmpty,
Path,
BooleanModel,
StringModel,
CoherenceFunction,
} from "../../../metamodel.mjs";
import {
TypeSpecModel,
StylePatchesMapModel,
} from "../../type-spec-models.mjs";
import { createRootFontStylePatchesCoherenceFunction } from "./root-font-style-patches.mjs";
import {
ProseMirrorSchemaModel,
NodeSpecToTypeSpecMapModel,
Expand Down Expand Up @@ -63,7 +65,11 @@ const RampModel = _BaseLayoutModel.createClass(
// the root of all typeSpecs
["document", NodeModel],
["showParameters", BooleanModel],
// The font identity for which the style patches in "stylePatchesSource"
// were generated, see createRootFontStylePatchesCoherenceFunction.
["rootFontStylePatchesKey", StringModel],
initTypeSpecCoherenceFn(DEFAULT_STATE),
createRootFontStylePatchesCoherenceFunction(),
);

class TypeSpecSelect extends GenericSelect {
Expand Down
140 changes: 140 additions & 0 deletions lib/js/components/layouts/ramp/root-font-style-patches.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import {
CoherenceFunction,
deserializeSync,
SERIALIZE_OPTIONS,
SERIALIZE_FORMAT_OBJECT,
} from "../../../metamodel.mjs";
import {
StylePatchesMapModel,
StylePatchLinksMapModel,
} from "../../type-spec-models.mjs";

const _SERIALIZE_OPTIONS = Object.assign({}, SERIALIZE_OPTIONS, {
format: SERIALIZE_FORMAT_OBJECT,
});

/**
* Depth first iteration over the root TypeSpec and all of its (recursive)
* children.
*/
function* _iterTypeSpecs(typeSpec) {
yield typeSpec;
for (const [, child] of typeSpec.get("children").entries())
yield* _iterTypeSpecs(child);
}

/**
* Replace the complete contents of an ordered map (`target`) with the
* entries of `newMap`.
*/
function _replaceOrderedMap(target, newMap) {
// NOTE: the public "splice" addresses entries by key, "arraySplice"
// by index — here we want to drop all existing entries by index.
if (target.size) target.arraySplice(0, target.size);
for (const [key, entry] of newMap.entries()) target.set(key, entry);
}

/**
* An "observer" implemented as a CoherenceFunction, reacting to changes
* of the root font (the "font" dependency).
*
* Whenever the root font changes it (re)generates all "style patches" —
* one SimpleStylePatch per named instance of the font — into
* `stylePatchesSource`, and associates those patches with every existing
* TypeSpec (the root TypeSpec and all of its children).
*
* NOTE: a CoherenceFunction is the idiomatic way to react to model
* changes here, as changeState (and hence writing the model from a
* component's update phase) is locked while the UI updates.
*
* The font identity for which the patches were generated is stored in
* `rootFontStylePatchesKey`. As coherence functions run on (almost)
* every metamorphose, this lets us rebuild only when the font actually
* changed — and otherwise leave the user's edits to the patches and
* links untouched.
*/
export function createRootFontStylePatchesCoherenceFunction() {
return CoherenceFunction.create(
[
// Ensures this runs after the default-state initialization,
// as both write "typeSpec" and "stylePatchesSource".
"initTypeSpec",
"font",
"typeSpec",
"stylePatchesSource",
"rootFontStylePatchesKey",
],
function generateRootFontStylePatches({
font,
typeSpec,
stylePatchesSource,
rootFontStylePatchesKey,
}) {
// "font" is a ForeignKey/ValueLink that can be null and its
// value is the actual (VideoProof-)font.
const fontObject = font ? font.value : null;
if (!fontObject) return;

const signature = fontObject.fullName;
if (rootFontStylePatchesKey.value === signature)
// The style patches already correspond to this font, don't
// regenerate (and don't clobber the user's edits).
return;

// [[name, coordinates {axisTag: numericValue}], ...]
const instances = fontObject.instances;

// (Re)generate the style patches, one SimpleStylePatch per
// named instance of the font. The instance name is normalized to
// lower case so the keys match the (lower case) style names used
// e.g. in the editor/document (e.g. "bold", "bold italic").
const stylePatchesData = instances.map(([name, coordinates]) => [
name.toLowerCase(),
{
stylePatchTypeKey: "SimpleStylePatch",
instance: {
axesLocations: Object.entries(coordinates).map(
([axisTag, numericValue]) => [
axisTag,
{ logicalValue: "number", numericValue },
],
),
},
},
]);
_replaceOrderedMap(
stylePatchesSource,
deserializeSync(
StylePatchesMapModel,
stylePatchesSource.dependencies,
stylePatchesData,
_SERIALIZE_OPTIONS,
),
);

// Associate the generated style patches with every TypeSpec.
// Keys are the local names in the TypeSpec, values are the keys
// into stylePatchesSource; here both are the instance name.
const stylePatchLinksData = instances.map(([name]) => {
const key = name.toLowerCase();
return [key, key];
});
// Materialize the traversal before mutating, to keep traversal
// and (nested draft) mutation cleanly separated.
for (const typeSpecItem of [..._iterTypeSpecs(typeSpec)]) {
const stylePatches = typeSpecItem.get("stylePatches");
_replaceOrderedMap(
stylePatches,
deserializeSync(
StylePatchLinksMapModel,
stylePatches.dependencies,
stylePatchLinksData,
_SERIALIZE_OPTIONS,
),
);
}

rootFontStylePatchesKey.value = signature;
},
);
}
Loading
Loading