mirror of
https://github.com/netbox-community/netbox.git
synced 2024-05-10 07:54:54 +00:00
Merge pull request #6960 from candlerb/candlerb/6879-v3
Display device names in front of device front/rear images
This commit is contained in:
@ -112,6 +112,9 @@ class RackElevationSVG:
|
|||||||
)
|
)
|
||||||
image.fit(scale='slice')
|
image.fit(scale='slice')
|
||||||
link.add(image)
|
link.add(image)
|
||||||
|
link.add(drawing.text(str(name), insert=text, stroke='black',
|
||||||
|
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
|
||||||
|
link.add(drawing.text(str(name), insert=text, fill='white', class_='device-image-label'))
|
||||||
|
|
||||||
def _draw_device_rear(self, drawing, device, start, end, text):
|
def _draw_device_rear(self, drawing, device, start, end, text):
|
||||||
rect = drawing.rect(start, end, class_="slot blocked")
|
rect = drawing.rect(start, end, class_="slot blocked")
|
||||||
@ -129,6 +132,9 @@ class RackElevationSVG:
|
|||||||
)
|
)
|
||||||
image.fit(scale='slice')
|
image.fit(scale='slice')
|
||||||
drawing.add(image)
|
drawing.add(image)
|
||||||
|
drawing.add(drawing.text(str(device), insert=text, stroke='black',
|
||||||
|
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
|
||||||
|
drawing.add(drawing.text(str(device), insert=text, fill='white', class_='device-image-label'))
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
|
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
|
||||||
|
14
netbox/project-static/dist/netbox.js
vendored
14
netbox/project-static/dist/netbox.js
vendored
File diff suppressed because one or more lines are too long
2
netbox/project-static/dist/netbox.js.map
vendored
2
netbox/project-static/dist/netbox.js.map
vendored
File diff suppressed because one or more lines are too long
@ -1,92 +1,90 @@
|
|||||||
import { rackImagesState } from './stores';
|
import { rackImagesState, RackViewSelection } from './stores';
|
||||||
import { getElements } from './util';
|
import { getElements } from './util';
|
||||||
|
|
||||||
import type { StateManager } from './state';
|
import type { StateManager } from './state';
|
||||||
|
|
||||||
type RackToggleState = { hidden: boolean };
|
export type RackViewState = { view: RackViewSelection };
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the Rack Image button to reflect the current state. If the current state is hidden and
|
* Show or hide images and labels to build the desired rack view.
|
||||||
* the images are therefore hidden, the button should say "Show Images". Likewise, if the current
|
|
||||||
* state is *not* hidden, and therefore the images are shown, the button should say "Hide Images".
|
|
||||||
*
|
|
||||||
* @param hidden Current State - `true` if images are hidden, `false` otherwise.
|
|
||||||
* @param button Button element.
|
|
||||||
*/
|
*/
|
||||||
function toggleRackImagesButton(hidden: boolean, button: HTMLButtonElement): void {
|
function setRackView(
|
||||||
const text = hidden ? 'Show Images' : 'Hide Images';
|
view: RackViewSelection,
|
||||||
const selected = hidden ? '' : 'selected';
|
elevation: HTMLObjectElement,
|
||||||
button.setAttribute('selected', selected);
|
|
||||||
button.innerHTML = `<i class="mdi mdi-file-image-outline"></i> ${text}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show all rack images.
|
|
||||||
*/
|
|
||||||
function showRackImages(): void {
|
|
||||||
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
|
|
||||||
const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? [];
|
|
||||||
for (const image of images) {
|
|
||||||
image.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide all rack images.
|
|
||||||
*/
|
|
||||||
function hideRackImages(): void {
|
|
||||||
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
|
|
||||||
const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? [];
|
|
||||||
for (const image of images) {
|
|
||||||
image.classList.add('hidden');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle the visibility of device images and update the toggle button style.
|
|
||||||
*/
|
|
||||||
function handleRackImageToggle(
|
|
||||||
target: HTMLButtonElement,
|
|
||||||
state: StateManager<RackToggleState>,
|
|
||||||
): void {
|
): void {
|
||||||
const initiallyHidden = state.get('hidden');
|
switch(view) {
|
||||||
state.set('hidden', !initiallyHidden);
|
case 'images-and-labels': {
|
||||||
const hidden = state.get('hidden');
|
showRackElements('image.device-image', elevation);
|
||||||
|
showRackElements('text.device-image-label', elevation);
|
||||||
if (hidden) {
|
break;
|
||||||
hideRackImages();
|
}
|
||||||
} else {
|
case 'images-only': {
|
||||||
showRackImages();
|
showRackElements('image.device-image', elevation);
|
||||||
|
hideRackElements('text.device-image-label', elevation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'labels-only': {
|
||||||
|
hideRackElements('image.device-image', elevation);
|
||||||
|
hideRackElements('text.device-image-label', elevation);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showRackElements(
|
||||||
|
selector: string,
|
||||||
|
elevation: HTMLObjectElement,
|
||||||
|
): void {
|
||||||
|
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
|
||||||
|
for (const element of elements) {
|
||||||
|
element.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideRackElements(
|
||||||
|
selector: string,
|
||||||
|
elevation: HTMLObjectElement,
|
||||||
|
): void {
|
||||||
|
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
|
||||||
|
for (const element of elements) {
|
||||||
|
element.classList.add('hidden');
|
||||||
}
|
}
|
||||||
toggleRackImagesButton(hidden, target);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add onClick callback for toggling rack elevation images. Synchronize the image toggle button
|
* Change the visibility of all racks in response to selection.
|
||||||
* text and display state of images with the local state.
|
*/
|
||||||
|
function handleRackViewSelect(
|
||||||
|
newView: RackViewSelection,
|
||||||
|
state: StateManager<RackViewState>,
|
||||||
|
): void {
|
||||||
|
state.set('view', newView);
|
||||||
|
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
|
||||||
|
setRackView(newView, elevation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add change callback for selecting rack elevation images, and set
|
||||||
|
* initial state of select and the images themselves
|
||||||
*/
|
*/
|
||||||
export function initRackElevation(): void {
|
export function initRackElevation(): void {
|
||||||
const initiallyHidden = rackImagesState.get('hidden');
|
const initialView = rackImagesState.get('view');
|
||||||
for (const button of getElements<HTMLButtonElement>('button.toggle-images')) {
|
|
||||||
toggleRackImagesButton(initiallyHidden, button);
|
|
||||||
|
|
||||||
button.addEventListener(
|
for (const control of getElements<HTMLSelectElement>('select.rack-view')) {
|
||||||
'click',
|
control.selectedIndex = [...control.options].findIndex(o => o.value == initialView);
|
||||||
|
control.addEventListener(
|
||||||
|
'change',
|
||||||
event => {
|
event => {
|
||||||
handleRackImageToggle(event.currentTarget as HTMLButtonElement, rackImagesState);
|
handleRackViewSelect((event.currentTarget as any).value as RackViewSelection, rackImagesState);
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const element of getElements<HTMLObjectElement>('.rack_elevation')) {
|
for (const element of getElements<HTMLObjectElement>('.rack_elevation')) {
|
||||||
element.addEventListener('load', () => {
|
element.addEventListener('load', () => {
|
||||||
if (initiallyHidden) {
|
setRackView(initialView, element);
|
||||||
hideRackImages();
|
|
||||||
} else if (!initiallyHidden) {
|
|
||||||
showRackImages();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { createState } from '../state';
|
import { createState } from '../state';
|
||||||
|
|
||||||
export const rackImagesState = createState<{ hidden: boolean }>(
|
export type RackViewSelection = 'images-and-labels' | 'images-only' | 'labels-only';
|
||||||
{ hidden: false },
|
|
||||||
|
export const rackImagesState = createState<{ view: RackViewSelection }>(
|
||||||
|
{ view: 'images-and-labels' },
|
||||||
{ persist: true },
|
{ persist: true },
|
||||||
);
|
);
|
||||||
|
@ -18,10 +18,11 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block extra_controls %}
|
{% block extra_controls %}
|
||||||
<button class="btn btn-sm btn-outline-primary toggle-images" selected="selected">
|
<select class="btn btn-sm btn-outline-dark rack-view">
|
||||||
<i class="mdi mdi-file-image-outline"></i>
|
<option value="images-and-labels" selected="selected">Images and Labels</option>
|
||||||
Hide Images
|
<option value="images-only">Images only</option>
|
||||||
</button>
|
<option value="labels-only">Labels only</option>
|
||||||
|
</select>
|
||||||
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}{% endif %}" class="btn btn-sm btn-primary{% if not prev_rack %} disabled{% endif %}">
|
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}{% endif %}" class="btn btn-sm btn-primary{% if not prev_rack %} disabled{% endif %}">
|
||||||
<i class="mdi mdi-chevron-left" aria-hidden="true"></i> Previous
|
<i class="mdi mdi-chevron-left" aria-hidden="true"></i> Previous
|
||||||
</a>
|
</a>
|
||||||
|
@ -7,9 +7,13 @@
|
|||||||
{% block controls %}
|
{% block controls %}
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<button class="btn btn-sm btn-outline-dark toggle-images" selected="selected">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<span class="mdi mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
|
<select class="btn btn-sm btn-outline-secondary active rack-view">
|
||||||
</button>
|
<option value="images-and-labels" selected="selected">Images and Labels</option>
|
||||||
|
<option value="images-only">Images only</option>
|
||||||
|
<option value="labels-only">Labels only</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
<div class="btn-group btn-group-sm" role="group">
|
<div class="btn-group btn-group-sm" role="group">
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">Front</a>
|
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">Front</a>
|
||||||
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
|
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
|
||||||
|
Reference in New Issue
Block a user