PageSourceSearch

https://optimo.ch/assets/gray_matter/modules/image_annotation-69e6…e01f8bbe92537dcc0d908cbe2ed1d1f744518aa4.js

js optimo.ch collected 2026-10-02 03:24:27 UTC 5,807 bytes, 182 lines download raw bytes

1// Copyright (c) 2021-2025 GrayBits LLC
2
3import PopInInterface from 'gray_matter/utility/pop_in_interface'
4import * as Builder from 'gray_matter/utility/builder'
5import axios from 'axios'
6
7let current_annotation;
8let container;
9let image_container;
10let annotation_label;
11let annotation_details;
12let details_rich_text;
13let dragged_annotation;
14let dragged;
15let buffered_label;
16let buffered_details;
17let response;
18let popin;
19
20export async function openInterface(upload_id, options = {}){
21  let existing  = document.getElementById('annotations_interface');
22  if (existing) existing.remove();
23   
24  let params = {}
25  if (options.annotations) params['annotations'] = options.annotations;
26  popin = new PopInInterface(
27    document.body, 
28    { 
29      url: `/gray_matter/uploads/${upload_id}/annotations_interface`, 
30      method: 'put',
31      params: params,
32      setup: setup,
33      preserve_popins: options.preserve_popins
34    }
35  );
36  return () => {
37    return new Promise((resolve, reject) => {
38      response = resolve;
39    });
40  }
41}
42
43function setup(popin){
44  container = document.getElementById('upload_annotations');
45  image_container = document.getElementById('annotated_image_container');
46  annotation_label = document.querySelector('input[name="annotation_label"]');
47  annotation_details = document.querySelector('.rich_text_input');
48  details_rich_text = annotation_details.closest('.rich_text_editor').interface;
49  popin.element.querySelector('button.save_annotation').addEventListener('click', saveAnnotation);
50  popin.element.querySelector('button.save_annotations').addEventListener('click', saveAnnotations);
51  popin.element.querySelector('button.delete').addEventListener('click', deleteAnnotation);
52  popin.element.querySelector('#annotated_image_container').addEventListener('click', selectAnnotation);
53  annotation_label.addEventListener('input', syncLabel);
54  image_container.addEventListener('mousedown', startDrag);
55  document.body.addEventListener('cancelpopin', cancel);
56  image_container.querySelectorAll('.annotation').forEach(setupAnnotation);
57}
58
59function startDrag(e){
60  dragged_annotation = e.target.closest('.annotation');
61  if (!dragged_annotation) return;
62  
63  dragged = false;
64  image_container.addEventListener('mousemove', drag);
65  image_container.addEventListener('mouseup', endDrag);
66}
67
68function drag(e){
69  if (!dragged_annotation) return;
70  
71  dragged = true;
72  setAnnotationPosition(e, dragged_annotation);
73}
74
75function endDrag(e){
76  dragged_annotation = undefined;
77  image_container.removeEventListener('mousemove', drag);
78  image_container.removeEventListener('mouseup', endDrag);
79}
80
81function setAnnotationPosition(e, annotation){
82  let rect = image_container.getBoundingClientRect();
83  let x = (e.pageX - rect.x) / rect.width;
84  let y = (e.pageY - rect.y) / rect.height;
85
86  if (x < 0) x = 0;
87  if (y < 0) y = 0;
88  if (x > 1) x = 1;
89  if (y > 1) y = 1;
90  
91  annotation.annotation.x = x;
92  annotation.annotation.y = y;
93  annotation.style.left = `${100 * x}%`;
94  annotation.style.top = `${100 * y}%`;
95}
96
97function selectAnnotation(e){
98  if (dragged){
99    dragged = false;
100    return;
101  }
102  if (current_annotation){ 
103    clearAnnotation();
104    current_annotation = undefined;
105  }
106
107  let annotation = e.target.closest('.annotation');
108
109  if (annotation && !e.target.classList.contains('annotation_label_positioner')){
110    current_annotation = annotation;
111  } else {
112    current_annotation = Builder.createFromHTML('<div class="annotation"><div class="annotation_label_positioner"><div class="annotation_label">New</div></div></div>');
113    current_annotation.annotation = { x: 0, y: 0, label: 'New', details: ''};
114    current_annotation.label = current_annotation.querySelector('.annotation_label');
115    setAnnotationPosition(e, current_annotation);
116    image_container.append(current_annotation);
117    current_annotation.offsetHeight;
118    current_annotation.classList.add('added');
119  }
120  container.classList.add('selected');
121  current_annotation.classList.add('active'); 
122  setInputValues(current_annotation.annotation.label, current_annotation.annotation.details);
123  buffered_label = current_annotation.annotation.label;
124  buffered_details = current_annotation.annotation.details;
125}
126
127function setupAnnotation(annotation){
128  annotation.annotation = { 
129    x: parseFloat(annotation.dataset.x), 
130    y: parseFloat(annotation.dataset.y), 
131    label: annotation.dataset.label, 
132    details: annotation.dataset.details
133  };
134  annotation.style.left = `${100 * parseFloat(annotation.dataset.x)}%`;
135  annotation.style.top = `${100 * parseFloat(annotation.dataset.y)}%`;
136  annotation.label = annotation.querySelector('.annotation_label');
137  annotation.classList.add('added');
138}
139
140function saveAnnotation(e){
141  if (current_annotation){
142    details_rich_text.syncInput();
143    current_annotation.annotation.label = annotation_label.value;
144    current_annotation.annotation.details = annotation_details.value;
145    clearAnnotation();
146  }
147}
148
149function saveAnnotations(){
150  let annotations = [...image_container.querySelectorAll('.annotation')].map((a) => a.annotation);
151  document.body.removeEventListener('cancelpopin', cancel);
152  popin.cancel();
153  response(annotations);
154}
155
156function cancel(){
157  response(false);
158  document.body.removeEventListener('cancelpopin', cancel);
159}
160
161function clearAnnotation(){
162  container.classList.remove('selected');
163  current_annotation.classList.remove('active');
164  current_annotation = undefined;
165}
166
167function deleteAnnotation(e){
168  current_annotation.remove();
169  clearAnnotation();
170}
171
172function setInputValues(label, details){
173  annotation_label.value = label;
174  annotation_details.value = details;
175  details_rich_text.syncFromInput();
176}
177
178function syncLabel(){
179  if (!current_annotation) return;
180
181  current_annotation.label.innerHTML = annotation_label.value;
182};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.