Image annotator – Pin notes to images

Image annotator is a tweaked structure field, allowing you to add notes to images by pinning them to specific coordinates.

A quick overview :

  • Add a pin by clicking anywhere on the image. It acts as a trigger for adding a structure entry, and therefore opens a modal.
  • Pins can be dragged once added (Important : if a new pin has just been added, you’ll need to save the page in order to be able to drag the pin).
  • Pins are deleted when its associated structure entry is.
  • Pins index is updated when structure entries are sorted.
  • Pins background color can easily be changed

Basic usage :

  imagefieldname:
    label: The image that the annotator field will use
    type: image
  fieldname:
    label: Field label
    type: imageannotator
    src: imagefieldname
    fields: 
      markerid:
        type: hidden
      x:
        type: hidden
      y:
        type: hidden
      customfield:
        label: Note
        type: text

I initially did it for a client project in order to locate archive photos on street views. I just took a few time to clean it a little, but I haven’t tested every default structure options yet, so any help or feedback from your eventual uses of it are very welcome :slight_smile:

More informations on GitHub :

https://github.com/sylvainjule/kirby-image-annotator

12 Likes

The link ends on 404?

Oops, it was still private. Fixed, thanks ! :slight_smile:

Very cool and useful, gonna test tomorrow.

1 Like

This!!! I needed something like this like a year ago for two sites. How i wish time travel was possible. I ended up using the stand alone version of this but it was a lot of manual work to get it into Kirby.

Good work @sylvainjule.

What a great idea! Very useful indeed. :slight_smile:

@aoimedia added it to https://github.com/jenstornell/kirby-plugins/issues/545 (I was too slow, as always)

I hope you intend to keep it updated to work with Kirby 3 when ever it will be released? This a kind of plugin that, at least I could think of purchase for a license, 10 EUR or so. But I don’t mind the MIT license. :slight_smile:

Really glad you find it useful !

Yes I’m currently diving into Vue for some other projects, hopefully by the time it launches I’ll be able to keep the few fields I have updated a quickly as possible :slight_smile:

Great! :slight_smile: I will try the same thing… with the few fields I have… :grinning:

What I’d really :heart_eyes: to see as an extension: Using this field in the images view, so the information can be added in file meta data, similar to what @flokosiol did with his crop position plugin…

Thats a great idea. This data should really belong to the image, not a page.

It very much depends on the use case, for mine it was much more interesting to have it in a page as the whole page content revolved around it and I used it to link to subpages.

But you’re definitely right it would be a great option to add ! Added it on my to-do list :slight_smile:

2 Likes

@sylvainjule Well, is it something that can be toggled from a config setting? Some flag that says use file meta or a page field to store the data?

Yep, I agree it depends on the use case, but for myself, can think of more settings were I would like to add the data to the image rather than the page.

Glad to hear you are considering it!

@sylvainjule I haven’t yet tried the plugin, but how does it work out with a lot of pins? My use case is that i build a lot of sites for a particular industry which involves using a geographical map and plotting about 30 locations on it. For my use case, i would like to put a logo or a photo inside the info for a pin, as well as text and a button. Is that possible?

You should see it as a structure field, only the Add button has been replaced by a click somewhere on the image. So you should be able to put anything you could have put within a structure field as field options, photo, text, etc.

No problem to deal with many entries, aside from the messy display you’ll likely end up with on smaller screens. :thinking:
I could add a config option to choose the display size of the pin, but for now you could tweak this directly in the CSS.

@texnixe I have put something together on the branch called filesfield. I keep it there while I do some more testing but you can give it a shot already. To keep it short, if you now use it as a file field, and don’t add any src option, it should work fine !

@sylvainjule Oh, great, that was quick :clap: , I’ll give it a go as soon as I can.

This plugin is amazing.

I managed to implement it on the file page itself and I can save markers just fine. I would like to create a custom image tag (overwrite the default kirbytext tag) so all images that have saved markers will output the image tag and all saved markers within the .

I kinda managed to create a custom tag, but due to my limited PHP knowledge I’m not really sure how I would retrieve that data for the markers here and print them one after one in the $html.

<?php

Kirby::plugin('your/plugin', [
    'tags' => [
        'image' => [
            'attr' => [
                'alt',
                'caption',
                'class',
                'height',
                'imgclass',
                'link',
                'linkclass',
                'rel',
                'target',
                'title',
                'width'
            ],
            'html' => function($tag) {
                if ($tag->file = $tag->file($tag->value)) {

                    $tag->src     = $tag->file->url();
                    $tag->alt     = $tag->alt     ?? $tag->file->alt()->or(' ')->value();
                    $tag->title   = $tag->title   ?? $tag->file->title()->value();
                    $tag->caption = $tag->caption ?? $tag->file->caption()->value();
                    $tag->ratio   = $tag->file->ratio();
                        
                    $html = 
                    '<figure class="markers">
                        <img src="' . $tag->src() . '" alt="' . $tag->alt . '">
                    </figure>';

                    $html = str_replace(array("\r", "\n"), '', $html);

                    return $html;
                } 
            }
        ]
    ]
]);

Something along those lines should do the trick:

if ($tag->file = $tag->file($tag->value)) {
    // (... your current code without returning $html)

    if($tag->parent()->markers()->exists()) {
        $markers     = $tag->parent()->markers()->toStructure();
        $markersHtml = '';
        foreach($markers as $marker) {
            $markersHtml .= '<div class="marker" style="'. markerStyle($marker) .'"></div>';
        }
        $markersHtml = $markersHtml !== '' ? '<div class="markers">'. $markersHtml .'</div>' : $markersHtml;
        $html       .= $markersHtml;
    }
   
    return $html;
} 

@sylvainjule thanks a lot for your help!

I tried your snippet, but getting the following error:
“Call to a member function markers() on null”

<?php

Kirby::plugin('tags/image', [
    'tags' => [
        'image' => [
            'attr' => [
                // list of attributes
            ],
            'html' => function($tag) {
                if ($tag->file = $tag->file($tag->value)) {
                    $tag->src = $tag->file->url();

                    $html = 
                        '<figure class="markers">
                            <img src="' . $tag->src() . '">
                        </figure>';
                    
                    if($tag->parent->markers()->exists()) {
                        $markers     = $tag->parent->markers()->toStructure();
                        $markersHtml = '';
                        foreach($markers as $marker) {
                            $markersHtml .= '<div class="marker" style="'. markerStyle($marker) .'"></div>';
                        }
                        $markersHtml = $markersHtml !== '' ? '<div class="markers">'. $markersHtml .'</div>' : $markersHtml;
                        $html       .= $markersHtml;
                    }

                    $html = str_replace(array("\r", "\n"), '', $html);
                    return $html;
                }
            }
        ]
    ]
]);

Apologies, use ->parent() instead of ->parent. I have edited the code above.