Add icon size options to mapping icons

This commit is contained in:
pietervdvn 2022-02-17 23:54:14 +01:00
parent 29ad3be280
commit 2e6069b95b
10 changed files with 159 additions and 44 deletions

View file

@ -130,7 +130,7 @@ export class Fuse<T> extends DesugaringStep<T> {
Utils.Dedup([].concat(...steps.map(step => step.modifiedAttributes))),
"Fuse of "+steps.map(s => s.name).join(", ")
);
this.steps = steps;
this.steps = Utils.NoNull(steps);
}
convert(json: T, context: string): { result: T; errors: string[]; warnings: string[], information: string[] } {

View file

@ -32,7 +32,7 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
for (const foundImage of found) {
if (typeof foundImage === "string") {
allFoundImages.push(foundImage)
} else {
} else{
// This is a tagRendering where every rendered value might be an icon!
for (const trpath of trpaths) {
if (trpath.typeHint !== "rendered") {
@ -54,7 +54,9 @@ export class ExtractImages extends Conversion<LayoutConfigJson, string[]> {
}
}
const splitParts = [].concat(...Utils.NoNull(allFoundImages).map(img => img.split(";")))
const splitParts = [].concat(...Utils.NoNull(allFoundImages)
.map(img => img["path"] ?? img)
.map(img => img.split(";")))
.map(img => img.split(":")[0])
return {result: Utils.Dedup(splitParts), errors, warnings};
}

View file

@ -16,7 +16,7 @@ class SubstituteLayer extends Conversion<(string | LayerConfigJson), LayerConfig
constructor(
state: DesugaringContext,
) {
super("Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form", [],"SubstuteLayers");
super("Converts the identifier of a builtin layer into the actual layer, or converts a 'builtin' syntax with override in the fully expanded form", [],"SubstituteLayer");
this._state = state;
}

View file

@ -11,6 +11,7 @@ import {TagUtils} from "../../../Logic/Tags/TagUtils";
import {ExtractImages} from "./FixImages";
import ScriptUtils from "../../../scripts/ScriptUtils";
import {And} from "../../../Logic/Tags/And";
import Translations from "../../../UI/i18n/Translations";
class ValidateLanguageCompleteness extends DesugaringStep<any> {
@ -51,7 +52,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
private readonly _isBuiltin: boolean;
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) {
super("Doesn't change anything, but emits warnings and errors", [],"ValidateTheme");
super("Doesn't change anything, but emits warnings and errors", [], "ValidateTheme");
this.knownImagePaths = knownImagePaths;
this._path = path;
this._isBuiltin = isBuiltin;
@ -61,6 +62,9 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
const errors = []
const warnings = []
const information = []
const theme = new LayoutConfig(json, true, "test")
{
// Legacy format checks
if (this._isBuiltin) {
@ -105,7 +109,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
const width: string = svg.$.width;
const height: string = svg.$.height;
if (width !== height) {
const e = `the icon for theme ${json.id} is not square. Please square the icon at ${json.icon}` +
const e = `the icon for theme ${json.id} is not square. Please square the icon at ${json.icon}` +
` Width = ${width} height = ${height}`;
(json.hideFromOverview ? warnings : errors).push(e)
}
@ -116,8 +120,8 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
}
}
try {
const theme = new LayoutConfig(json, true, "test")
if (theme.id !== theme.id.toLowerCase()) {
errors.push("Theme ids should be in lowercase, but it is " + theme.id)
}
@ -138,7 +142,7 @@ class ValidateTheme extends DesugaringStep<LayoutConfigJson> {
.convert(theme, theme.id)
errors.push(...checked.errors)
}
if(!json.hideFromOverview && theme.id !== "personal"){
if (!json.hideFromOverview && theme.id !== "personal") {
// Official, public themes must have a full english translation
const checked = new ValidateLanguageCompleteness("en")
.convert(theme, theme.id)
@ -171,7 +175,7 @@ export class ValidateThemeAndLayers extends Fuse<LayoutConfigJson> {
class OverrideShadowingCheck extends DesugaringStep<LayoutConfigJson> {
constructor() {
super("Checks that an 'overrideAll' does not override a single override",[],"OverrideShadowingCheck");
super("Checks that an 'overrideAll' does not override a single override", [], "OverrideShadowingCheck");
}
convert(json: LayoutConfigJson, context: string): { result: LayoutConfigJson; errors?: string[]; warnings?: string[] } {
@ -211,11 +215,12 @@ export class PrevalidateTheme extends Fuse<LayoutConfigJson> {
export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJson> {
constructor() {
super("Checks that the mappings don't shadow each other",[],"DetectShadowedMappings");
super("Checks that the mappings don't shadow each other", [], "DetectShadowedMappings");
}
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
const errors = []
const warnings = []
if (json.mappings === undefined || json.mappings.length === 0) {
return {result: json}
}
@ -230,10 +235,13 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
properties[k] = v
})
for (let j = 0; j < i; j++) {
if(json.mappings[j].hideInAnswer === true){
continue
}
const doesMatch = parsedConditions[j].matchesProperties(properties)
if (doesMatch) {
// The current mapping is shadowed!
errors.push(`Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
warnings.push(`At ${context}: Mapping ${i} is shadowed by mapping ${j} and will thus never be shown:
The mapping ${parsedConditions[i].asHumanString(false, false, {})} is fully matched by a previous mapping, which matches:
${parsedConditions[j].asHumanString(false, false, {})}.
@ -246,11 +254,48 @@ export class DetectShadowedMappings extends DesugaringStep<TagRenderingConfigJso
return {
errors,
warnings,
result: json
};
}
}
export class DetectMappingsWithImages extends DesugaringStep<TagRenderingConfigJson> {
constructor() {
super("Checks that 'then'clauses in mappings don't have images, but use 'icon' instead", [], "DetectMappingsWithImages");
}
convert(json: TagRenderingConfigJson, context: string): { result: TagRenderingConfigJson; errors?: string[]; warnings?: string[] } {
const warnings = []
if (json.mappings === undefined || json.mappings.length === 0) {
return {result: json}
}
for (let i = 0; i < json.mappings.length; i++) {
const mapping = json.mappings[i]
const images = Utils.Dedup(Translations.T(mapping.then).ExtractImages())
if (images.length > 0) {
warnings.push(context + ".mappings[" + i + "]: A mapping has an image in the 'then'-clause. Remove the image there and use `\"icon\": <your-image>` instead. The images found are "+images.join(", "))
}
}
return {
warnings,
result: json
};
}
}
export class ValidateTagRenderings extends Fuse<TagRenderingConfigJson> {
constructor() {
super("Various validation on tagRenderingConfigs",
// TODO enable these checks again
// new DetectShadowedMappings(),
// new DetectMappingsWithImages() e
);
}
}
export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
/**
* The paths where this layer is originally saved. Triggers some extra checks
@ -261,16 +306,16 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
private readonly _isBuiltin: boolean;
constructor(knownImagePaths: Set<string>, path: string, isBuiltin: boolean) {
super("Doesn't change anything, but emits warnings and errors", [],"ValidateLayer");
super("Doesn't change anything, but emits warnings and errors", [], "ValidateLayer");
this.knownImagePaths = knownImagePaths;
this._path = path;
this._isBuiltin = isBuiltin;
}
convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[] } {
convert(json: LayerConfigJson, context: string): { result: LayerConfigJson; errors: string[]; warnings?: string[], information?: string[] } {
const errors = []
const warnings = []
const information = []
if (typeof json === "string") {
errors.push(context + ": This layer hasn't been expanded: " + json)
return {
@ -343,23 +388,26 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
}
}
if (json.tagRenderings !== undefined) {
new DetectShadowedMappings().convertAll(<TagRenderingConfigJson[]>json.tagRenderings, context + ".tagRenderings")
const r = new OnEvery("tagRenderings", new ValidateTagRenderings()).convert(json, context)
warnings.push(...(r.warnings??[]))
errors.push(...(r.errors??[]))
information.push(...(r.information??[]))
}
if(json.presets !== undefined){
if (json.presets !== undefined) {
// Check that a preset will be picked up by the layer itself
const baseTags = TagUtils.Tag( json.source.osmTags)
for (let i = 0; i < json.presets.length; i++){
const baseTags = TagUtils.Tag(json.source.osmTags)
for (let i = 0; i < json.presets.length; i++) {
const preset = json.presets[i];
const tags : {k: string,v: string}[]= new And(preset.tags.map(t => TagUtils.Tag(t))).asChange({id:"node/-1"})
const tags: { k: string, v: string }[] = new And(preset.tags.map(t => TagUtils.Tag(t))).asChange({id: "node/-1"})
const properties = {}
for (const tag of tags) {
properties[tag.k] = tag.v
}
const doMatch = baseTags.matchesProperties(properties)
if(!doMatch){
errors.push(context+".presets["+i+"]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: "+JSON.stringify(properties)+"\n The required tags are: "+baseTags.asHumanString(false, false, {}))
if (!doMatch) {
errors.push(context + ".presets[" + i + "]: This preset does not match the required tags of this layer. This implies that a newly added point will not show up.\n A newly created point will have properties: " + JSON.stringify(properties) + "\n The required tags are: " + baseTags.asHumanString(false, false, {}))
}
}
}
@ -372,7 +420,8 @@ export class ValidateLayer extends DesugaringStep<LayerConfigJson> {
return {
result: json,
errors,
warnings
warnings,
information
};
}
}

View file

@ -122,7 +122,18 @@ export interface TagRenderingConfigJson {
* An icon supporting this mapping; typically shown pretty small
* Type: icon
*/
icon?: string
icon?: string | {
/**
* The path to the icon
* Type: icon
*/
path: string,
/**
* A hint to mapcomplete on how to render this icon within the mapping.
* This is translated to 'mapping-icon-<classtype>', so defining your own in combination with a custom CSS is possible (but discouraged)
*/
class: "small" | "medium" | "large" | string
}
/**
* In some cases, multiple taggings exist (e.g. a default assumption, or a commonly mapped abbreviation and a fully written variation).
*

View file

@ -44,6 +44,7 @@ export default class TagRenderingConfig {
readonly ifnot?: TagsFilter,
readonly then: Translation,
readonly icon: string,
readonly iconClass: string
readonly hideInAnswer: boolean | TagsFilter
readonly addExtraTags: Tag[]
}[]
@ -180,8 +181,14 @@ export default class TagRenderingConfig {
hideInAnswer = TagUtils.Tag(mapping.hideInAnswer, `${context}.mapping[${i}].hideInAnswer`);
}
let icon = undefined;
if (mapping.icon !== "") {
icon = mapping.icon
let iconClass = "small"
if(mapping.icon !== undefined){
if (typeof mapping.icon === "string" && mapping.icon !== "") {
icon = mapping.icon
}else{
icon = mapping.icon["path"]
iconClass = mapping.icon["class"] ?? iconClass
}
}
const mp = {
if: TagUtils.Tag(mapping.if, `${ctx}.if`),
@ -189,6 +196,7 @@ export default class TagRenderingConfig {
then: Translations.T(mapping.then, `${ctx}.then`),
hideInAnswer,
icon,
iconClass,
addExtraTags: (mapping.addExtraTags ?? []).map((str, j) => TagUtils.SimpleTag(str, `${ctx}.addExtraTags[${j}]`))
};
if (this.question) {
@ -350,7 +358,7 @@ export default class TagRenderingConfig {
* @param tags
* @constructor
*/
public GetRenderValues(tags: any): { then: Translation, icon?: string }[] {
public GetRenderValues(tags: any): { then: Translation, icon?: string, iconClass?: string }[] {
if (!this.multiAnswer) {
return [this.GetRenderValueWithImage(tags)]
}
@ -399,9 +407,6 @@ export default class TagRenderingConfig {
return mapping;
}
if (mapping.if.matchesProperties(tags)) {
if (this.id === "uk_addresses_placename") {
console.log("Matched", mapping.if, "with ", tags["addr:place"])
}
return mapping;
}
}

View file

@ -45,7 +45,7 @@ export default class TagRenderingAnswer extends VariableUiElement {
if(tr.icon === undefined){
return text
}
return new Combine([new Img(tr.icon).SetClass("w-6 max-h-6 pr-2"), text]).SetClass("flex")
return new Combine([new Img(tr.icon).SetClass("mapping-icon-"+(tr.iconClass ?? "small")), text]).SetClass("flex items-center")
})
if (valuesToRender.length === 1) {
return valuesToRender[0];

View file

@ -355,7 +355,8 @@ export default class TagRenderingQuestion extends Combine {
if: TagsFilter,
then: Translation,
addExtraTags: Tag[],
img?: string
icon?: string,
iconClass?: string
}, ifNot?: TagsFilter[]): InputElement<TagsFilter> {
let tagging: TagsFilter = mapping.if;
@ -375,13 +376,14 @@ export default class TagRenderingQuestion extends Combine {
private static GenerateMappingContent(mapping: {
then: Translation,
icon?: string
icon?: string,
iconClass?: string
}, tagsSource: UIEventSource<any>, state: FeaturePipelineState): BaseUIElement {
const text = new SubstitutedTranslation(mapping.then, tagsSource, state)
if (mapping.icon === undefined) {
return text;
}
return new Combine([new Img(mapping.icon).SetClass("w-6 max-h-6 pr-2"), text]).SetClass("flex")
return new Combine([new Img(mapping.icon).SetClass("mapping-icon-"+(mapping.iconClass ?? "small")), text]).SetClass("flex")
}
private static GenerateFreeform(state, configuration: TagRenderingConfig, applicableUnit: Unit, tags: UIEventSource<any>, feedback: UIEventSource<Translation>)

View file

@ -851,11 +851,6 @@ video {
margin-bottom: 0.75rem;
}
.mx-4 {
margin-left: 1rem;
margin-right: 1rem;
}
.ml-3 {
margin-left: 0.75rem;
}
@ -1178,10 +1173,6 @@ video {
min-width: min-content;
}
.min-w-\[20em\] {
min-width: 20em;
}
.max-w-full {
max-width: 100%;
}
@ -1781,6 +1772,10 @@ video {
filter: var(--tw-filter);
}
.\!filter {
filter: var(--tw-filter) !important;
}
.transition {
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
transition-property: background-color, border-color, color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
@ -2405,6 +2400,31 @@ input {
/* Additional class on the first layer filter */
}
.mapping-icon-small {
/* A mapping icon type */
width: 1.5rem;
max-height: 1.5rem;
margin-right: 0.5rem;
}
.mapping-icon-medium {
/* A mapping icon type */
width: 3rem;
max-height: 3rem;
margin-right: 1rem;
margin-left: 1rem;
}
.mapping-icon-large{
/* A mapping icon type */
width: 6rem;
max-height: 5rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
margin-right: 1.5rem;
margin-left: 1.5rem;
}
.hover\:bg-indigo-200:hover {
--tw-bg-opacity: 1;
background-color: rgba(199, 210, 254, var(--tw-bg-opacity));
@ -2696,4 +2716,3 @@ input {
display: inline;
}
}

View file

@ -588,3 +588,30 @@ input {
/* Additional class on the first layer filter */
}
.mapping-icon-small {
/* A mapping icon type */
width: 1.5rem;
max-height: 1.5rem;
margin-right: 0.5rem;
}
.mapping-icon-medium {
/* A mapping icon type */
width: 3rem;
max-height: 3rem;
margin-right: 1rem;
margin-left: 1rem;
}
.mapping-icon-large{
/* A mapping icon type */
width: 6rem;
max-height: 5rem;
margin-top: 0.5rem;
margin-bottom: 0.5rem;
margin-right: 1.5rem;
margin-left: 1.5rem;
}