Направите свој изглед

Направите своје изгледе мерача у HTML-у, CSS-у и JavaScript-у. Сваки изглед ради са сваким извором, од батерије до бројева које сами шаљете.

Брзи почетак

  1. Направите изглед: У Подешавањима кликните Направи изглед… (Option-Command-N). Multimeter прави фасциклу са почетним датотекама које већ користе податке верзије 2.
  2. Измените датотеке: Multimeter приказује фасциклу у Finder-у. Уређујте датотеке у било ком уређивачу кода.
  3. Гледајте измене уживо: Multimeter поново учитава ваш изглед сваки пут кад сачувате. Испробајте га са различитим изворима у Додај мерач… или Прилагоди овај мерач….
  4. Поделите га: Компримујте фасциклу у Finder-у, преименујте .zip у .multimeter и пошаљите га у галерију. Свако може да га дода преко Увези… (Command-O) или двоструким кликом на датотеку.

Како мерач ради

Мерач на траци менија је изглед плус извор плус подешавања корисника. Изглед је ваша фасцикла: мала веб страница коју Multimeter учитава у провидни приказ висок 22 тачке и широк колико width у вашем манифесту. Извор је оно што мерач мери, а бира га корисник када додаје мерач.

Кад год се нешто промени, Multimeter поставља window.meterData и шаље догађај meterUpdate. Ваш изглед не мора да зна одакле бројеви долазе: нацртајте level (од 0 до 1) и прикажите formatted, и радиће подједнако за процесор, мрежу, тајмер или батерију.

Структура датотека

Сваки изглед је фасцикла која садржи:

my-meter/
├── manifest.json    # name, size, which sources it's made for
├── index.html       # the page Multimeter loads
├── styles.css
└── meter.js         # listens for meterUpdate and draws

manifest.json

Манифест описује ваш изглед и говори апликацији Multimeter колико је широк и за шта је намењен:

{
  "name": "My Meter",
  "description": "What it shows, in one line.",
  "version": "1.0.0",
  "author": "Your Name",
  "apiVersion": 2,
  "sources": ["any"],
  "defaultSource": "cpu",
  "width": 60,
  "minWidth": 40,
  "maxWidth": 100,
  "supportsLightMode": true,
  "supportsDarkMode": true
}
КључТипЗначење
namestringПриказује се на листи изгледа и у галерији.
descriptionstringЈедан ред о томе шта изглед приказује.
versionstringВаш број верзије, на пример 1.0.0.
authorstringВаше име, приказано у галерији.
apiVersion2Поставите на 2 за податке описане на овој страници.
sourcesstring[]["any"] за изглед који може да црта било који извор, или листа ID-ова извора као што је ["battery"] или ["battery", "countdown", "timer"]. Multimeter нуди ваш изглед само за те изворе.
defaultSourcestringИзвор који се бира када неко први пут дода ваш изглед, на пример "cpu".
widthnumberШирина у тачкама. Висина је увек 22.
minWidth / maxWidthnumberНајмања и највећа ширина коју корисник може да подеси у Прилагоди.
supportsLightMode / supportsDarkModebooleanДа ли је изглед прављен за светлу и тамну траку менија.

Манифест без apiVersion и sources сматра се изгледом прављеним првенствено за батерију који и даље ради са сваким извором, тако да мерачи прављени за раније верзије настављају да раде.

Подаци мерача

Multimeter ставља тренутне податке у window.meterData и шаље их поново при свакој промени. Ево свега што садрже (API верзија 2):

window.meterData = {
  apiVersion: 2,
  source: "cpu",            // what this meter measures
  sourceName: "CPU",        // that source's name, translated
  label: "CPU",             // the meter's label ("" if the person cleared it)
  value: 23.4,              // raw value in `unit` (null if unavailable)
  min: 0, max: 100,         // the range `level` is measured against
  level: 0.234,             // value placed in the range, 0 to 1
  formatted: "23%",         // ready to show, unit included, in the person's language
  unit: "%",                // "%", "W", "B/s", "rpm", "s", or your own
  state: "normal",          // "normal", "warning" or "critical"
  tint: null,               // "#30D158" if the person picked a color
  history: [0.21, 0.25],    // up to 60 recent levels, oldest first
  warningLevel: 0.7,        // alert levels on the same 0 to 1 scale (or null)
  criticalLevel: 0.9,
  alertDirection: "above",  // "above" or "below" (or null)
  available: true,          // false when the source can't be read right now
  colorScheme: "dark",      // the menu bar: "light" or "dark"
  reduceMotion: false,      // System Settings > Accessibility > Reduce motion
  strings: { timeLeft: "left" },
  battery: {                // always the Mac's battery, whatever the source
    level: 0.82, percentage: 82, isCharging: false, isPluggedIn: false,
    hasBattery: true, timeRemaining: 312, timeToFullCharge: null,
    formattedTime: "5:12", cycleCount: 212, health: 0.94, temperature: 30.5
  },
  // Version 1 fields, so battery meters keep working with any source:
  percentage: 23, isCharging: false, isPluggedIn: false,
  timeRemaining: null, timeToFullCharge: null, formattedTime: null,
  cycleCount: 212, health: 0.94, temperature: 30.5, hasBattery: true
}
ПољеЗначење
sourceID извора (погледајте табелу испод). sourceName је његов преведени назив.
labelОзнака мерача, на пример CPU или DOWN. Корисник може да је преименује или обрише, па може бити празан стринг.
valueСирово очитавање у unit, или null. Уз min и max добијате целу слику.
levelВредност смештена у опсег од 0 до 1, ограничена. Већини изгледа треба само ово.
formattedОчитавање као текст, са јединицом, форматирано за језик корисника: 23%, 12.4 W, 1.2 MB/s, 4:12.
unit%, W, B/s, rpm, s (тајмери и одбројавања), празан стринг или јединица коју је корисник сам задао.
statenormal, warning или critical, према нивоима упозорења које је корисник подесио. Бојите изглед према томе.
tintБоја као што је #30D158 коју је корисник изабрао, или null да бисте користили сопствене боје изгледа.
historyДо 60 недавних нивоа (од 0 до 1), најстарији први, за графиконе.
warningLevelwarningLevel и criticalLevel смештају нивое упозорења на исту скалу од 0 до 1 као level, а alertDirection говори да ли се упозорења активирају above или below њих. Сва три су null када мерач нема упозорења.
availablefalse када извор тренутно не може да се очита: нема батерије, додатак је искључен, веб адреса не одговара. Прикажите цртицу.
colorSchemeИзглед траке менија, light или dark. Прати позадину, а не само системско подешавање.
reduceMotiontrue када је укључено Смањи покрете. Прескочите анимације.
batteryБатерија Mac-а, увек присутна шта год мерач мерио: напуњеност, пуњење, прикључено на струју, преостало време, време до пуне, циклуси, стање и температура.

Праћење ажурирања

Нацртајте једном са оним што већ постоји у window.meterData, а затим поново при сваком meterUpdate. Поставите window.meterReady = true када сте спремни за податке.

function render(data) {
  // draw the meter from data.level, data.formatted, data.state ...
}

window.addEventListener('meterUpdate', (e) => render(e.detail));
render(window.meterData || { level: 0.5, formatted: '50%', state: 'normal' });
window.meterReady = true;

Извори

Ово су извори које мерач може да има. Ваш изглед види ID у source и добија већ написан formatted, па ретко морате различито да их третирате.

Изворsourceunitformatted изгледа овако
Батеријаbattery"%"82%
Bluetooth додатакbluetooth"%"64%
Потрошња струјеpower"W"12.4 W
Процесорcpu"%"23%
Меморијаmemory"%"71%
Простор на дискуdisk"%"58%
Термално оптерећењеthermal""Нормално, Умерено, Озбиљно, Критично
Брзина вентилатора*fan"rpm"2,400 rpm
Мрежаnetwork"B/s"1.2 MB/s, 340 KB/s
Тајмерtimer"s"24:51
Одбројавањеcountdown"s"3d 4h, 4:12, 12:05
Ток данаprogress"%"63%
Послата вредностpush""42
Веб адресаurl""1,284
Датотекаfile""12
Скрипта*script""412

* Брзина вентилатора и скрипте захтевају верзију апликације Multimeter за директно преузимање. Верзија из Mac App Store-а не може да чита сензоре вентилатора нити да покреће ваше скрипте; тамо користите послату вредност или датотеку.

Проценти увек иду од 0 до 100. Потрошња струје, мрежа и брзина вентилатора прилагођавају скалу свом недавном врхунцу, осим ако корисник не подеси опсег. Термално оптерећење има четири степена, па је level 0, 0.33, 0.67 или 1. За тајмере и одбројавања, level је удео преосталог времена, а value преостале секунде.

Упозорења и боје

Сваки мерач може да има ниво упозорења и критични ниво које подешава корисник (подразумевано за батерију испод 20% и 10%, за процесор изнад 70% и 90%, за диск изнад 85% и 95%). Multimeter израчунава state уместо вас. Изглед треба да делује мирно када је normal и да постаје упадљивији како прелази у warning и critical, и да користи tint када је корисник изабрао боју:

document.body.classList.remove('warning', 'critical');
if (data.state !== 'normal') document.body.classList.add(data.state);

// The person's color wins over the face's own
if (data.tint) document.body.style.setProperty('--accent', data.tint);
else document.body.style.removeProperty('--accent');
body.warning  { --accent: #ff9f0a; }
body.critical { --accent: #ff453a; }

Желите да нацртате зоне упозорења на скали, као бројчаник са црвеним крајем? Користите warningLevel, criticalLevel и alertDirection.

Историја

history садржи до 60 недавних нивоа, најстарији први, већ на скали од 0 до 1. Додајте тренутни level на крај и имате графикон. Пример Графикон историје испод црта један.

Детаљи о батерији

Батерија је и даље посебна. data.battery је увек батерија Mac-а, шта год мерач мерио, па сваки изглед може да прикаже муњу пуњења. Изглед прављен само за батерију треба у манифесту да наведе "sources": ["battery"]. timeRemaining и timeToFullCharge су у минутима и имају вредност null док macOS још процењује.

// A battery-only face: "sources": ["battery"] in manifest.json
const b = data.battery;
bolt.hidden = !b.isCharging;
time.textContent = b.isCharging
  ? (b.timeToFullCharge ? `${b.timeToFullCharge} min to full` : '')
  : (b.formattedTime ?? '');

Изгледи писани за верзију 1 читају percentage, isCharging и formattedTime на највишем нивоу. Они се и даље шаљу: за извор батерије то су вредности саме батерије, а за друге изворе isCharging је false, а formattedTime је преостало време тајмера или одбројавања (h:mm), иначе null.

Ваши сопствени подаци

Четири извора приказују бројеве које ви обезбеђујете: послата вредност, веб адреса, датотека или скрипта. Ваш изглед их третира као сваки други извор.

Пошаљите вредност линком

Отворите multimeter://set линк одакле год желите: Terminal, скрипта љуске, Пречице (Отвори URL-ове), Raycast, cron задатак, скрипта за изградњу. Додајте мерач са извором Послата вредност и истим кључем, и он се ажурира чим се линк отвори. open -g држи Multimeter у позадини.

# Set a value (the meter shows the "deploys" key)
open -g "multimeter://set?key=deploys&value=42"

# Range, label, unit and your own text ride along
open -g "multimeter://set?key=coffee&value=3&min=0&max=5&label=Coffee&unit=cups"
open -g "multimeter://set?key=build&value=0.8&max=1&formatted=80%25%20done"
ПараметарЗначење
keyЗа који мерач је. Кључеви не разликују велика и мала слова.
valueБрој. Обавезно.
min / maxОпсег, да би level био тачан. Подразумевано од 0 до 100.
labelТекст за ознаку мерача.
unitЈединица која се додаје после броја, на пример cups. Користите % за проценат.
formattedВаш сопствени текст за formatted, који се користи уместо броја.

Послате вредности раде и у верзији из Mac App Store-а. Multimeter памти последњу вредност за сваки кључ.

Веб адресе, датотеке и скрипте

Сва три читају исти излаз: обичан број или JSON са value и било којим додацима. Све осим value је опционо.

42
42.5
42%

{"value": 42, "min": 0, "max": 100, "label": "Deploys", "unit": "", "formatted": "42 left"}

Користите већи JSON, као одговор неког API-ја? Дајте мерачу путању кључа и он ће издвојити број:

{"data": {"count": 17}}                → key path  data.count
{"items": [{"value": 9.5}]}             → key path  items.0.value
  • Веб адреса: преузима се у интервалу који изаберете, најчешће на сваких 30 секунди (на сваких 5 минута ако то не промените).
  • Датотека: поново се чита кад год се промени. Згодно за све што може да упише датотеку.
  • Скрипта: команда љуске која се покреће у zsh у интервалу који изаберете (на сваких 30 секунди ако то не промените). Multimeter приказује број који она испише. Homebrew фасцикле су у PATH, а команда која траје дуже од 10 секунди се зауставља.
# Open pull requests waiting on you (direct download version)
gh pr list --search "review-requested:@me" --json number --jq length

# Free memory pressure as a percentage
memory_pressure | awk '/percentage/ {print 100 - $5}'

Верзија из Mac App Store-а може да користи веб адресе и датотеке, али не и скрипте. За скрипте тамо, нека ваша скрипта пошаље линк или упише датотеку.

Светла и тамна трака менија

Трака менија је светла или тамна у зависности од позадине. Multimeter додаје light-mode или dark-mode на body, поставља colorScheme у подацима, а и prefers-color-scheme прати траку менија. Користите шта вам одговара:

/* Dark menu bar (the default) */
body { color: white; }

/* Light menu bar */
body.light-mode { color: black; }

/* Same thing with the media query, which follows the menu bar too */
@media (prefers-color-scheme: light) {
  body { color: black; }
}

Смањени покрети

Када је укључено Смањи покрете, reduceMotion је true, а prefers-reduced-motion се поклапа. Нека мерач остане читљив, а избаците одскакивања, пулсирања и замахе:

body.reduce-motion * { transition: none !important; animation: none !important; }

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; animation: none !important; }
}

Потпуни примери

Два потпуна изгледа, која раде уживо на овој страници преко истих података које шаље Multimeter. Копирајте датотеке у фасциклу и увезите је.

Почетни изглед из Направи изглед

Тачно оно што пише Направи изглед…: ознака, трака и форматирана вредност, са описом API-ја у коментарима. Боји се према state, поштује tint и прати изглед траке менија.

starter/manifest.json

{
  "name": "My Face",
  "description": "A label, a bar and the value. Works with any source.",
  "version": "1.0.0",
  "author": "Your Name",
  "apiVersion": 2,
  "sources": ["any"],
  "defaultSource": "cpu",
  "width": 70,
  "minWidth": 50,
  "maxWidth": 120,
  "supportsLightMode": true,
  "supportsDarkMode": true
}

starter/index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="styles.css">
</head>
<body>
    <div id="meter">
        <span id="label"></span>
        <span id="track"><span id="fill"></span></span>
        <span id="value">--</span>
    </div>
    <script src="meter.js"></script>
</body>
</html>

starter/styles.css

* { margin: 0; padding: 0; box-sizing: border-box; }

/* The meter is 22 points tall and as wide as manifest.json says. */
body {
    font: 600 11px/1 -apple-system, BlinkMacSystemFont, sans-serif;
    font-variant-numeric: tabular-nums;
    color: white;                 /* dark menu bar */
    background: transparent;
    height: 22px;
    overflow: hidden;
    display: flex;
    align-items: center;
    justify-content: center;
    --accent: #30d158;            /* replaced by the meter's color, if one is set */
}

/* Light menu bar: the system setting, or the preview's light mode. */
#track { background: rgba(255, 255, 255, 0.25); }
@media (prefers-color-scheme: light) { body { color: black; } #track { background: rgba(0, 0, 0, 0.18); } }
body.light-mode { color: black; }
body.light-mode #track { background: rgba(0, 0, 0, 0.18); }
body.dark-mode { color: white; }
body.dark-mode #track { background: rgba(255, 255, 255, 0.25); }

#meter { display: flex; align-items: center; gap: 4px; }
#label { font-size: 8px; letter-spacing: 0.04em; opacity: 0.7; }
#track { width: 18px; height: 6px; border-radius: 3px; position: relative; overflow: hidden; }
#fill { position: absolute; inset: 0 auto 0 0; width: 0; background: var(--accent); border-radius: 3px; transition: width 0.4s ease; }

/* state is "normal", "warning" or "critical" (from the meter's alert levels). */
body.warning { --accent: #ffd60a; }
body.critical { --accent: #ff453a; }

@media (prefers-reduced-motion: reduce) { #fill { transition: none; } }

starter/meter.js

// Multimeter meter API, version 2. The app sets window.meterData and fires
// a 'meterUpdate' event whenever anything changes. The fields you'll use most:
//
//   level        0.0 to 1.0, the value within the meter's range
//   formatted    ready-to-show text with its unit: "23%", "1.2 MB/s", "4:12"
//   label        short label ("CPU"), may be "" if hidden
//   value, unit, min, max   the raw number and its range
//   state        "normal", "warning" or "critical"
//   tint         the color picked in Customize ("#30D158"), or null
//   history      up to 60 recent levels (0 to 1), oldest first, for graphs
//   source       what it measures: "battery", "cpu", "memory", "network", "timer"...
//   available    false when there's nothing to read (no battery, URL failing)
//   battery      the Mac's battery, always: level, isCharging, isPluggedIn,
//                hasBattery, timeRemaining, timeToFullCharge, health...
//   isCharging   true only when this meter shows the battery and it's charging
//   colorScheme  "light" or "dark"; reduceMotion  true if animations should stop
//
// Full reference: https://multimeterapp.com/creating-meters

class Meter {
    constructor() {
        this.label = document.getElementById('label');
        this.fill = document.getElementById('fill');
        this.value = document.getElementById('value');
        window.addEventListener('meterUpdate', (e) => this.render(e.detail));
        if (window.meterData) this.render(window.meterData);
        window.meterReady = true;
    }

    render(data) {
        const level = Math.max(0, Math.min(1, data.level || 0));
        this.label.textContent = data.label || '';
        this.label.style.display = data.label ? '' : 'none';
        this.value.textContent = data.available === false ? '\u2013' : (data.formatted || Math.round(level * 100) + '%');
        this.fill.style.width = (level * 100) + '%';

        document.body.classList.toggle('warning', data.state === 'warning');
        document.body.classList.toggle('critical', data.state === 'critical');
        if (data.tint) document.body.style.setProperty('--accent', data.tint);
        else document.body.style.removeProperty('--accent');
    }
}

if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => new Meter());
} else {
    new Meter();
}

Графикон историје

Линијски графикон последњег минута из history, нацртан на canvas-у у правој резолуцији екрана.

history-graph/manifest.json

{
  "name": "History Graph",
  "description": "The last minute as a little line graph.",
  "version": "1.0.0",
  "author": "Your Name",
  "apiVersion": 2,
  "sources": ["any"],
  "defaultSource": "network",
  "width": 44,
  "minWidth": 30,
  "maxWidth": 80,
  "supportsLightMode": true,
  "supportsDarkMode": true
}

history-graph/index.html

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <style>
    html, body { margin: 0; height: 22px; background: transparent; overflow: hidden; }
    canvas { display: block; width: 100%; height: 22px; }
  </style>
</head>
<body>
  <canvas id="graph"></canvas>
  <script src="meter.js"></script>
</body>
</html>

history-graph/meter.js

const canvas = document.getElementById('graph');
const ctx = canvas.getContext('2d');

function render(data) {
  // Draw at the screen's real resolution so the line stays sharp
  const scale = window.devicePixelRatio || 1;
  const w = canvas.clientWidth, h = 22;
  canvas.width = w * scale;
  canvas.height = h * scale;
  ctx.setTransform(scale, 0, 0, scale, 0, 0);
  ctx.clearRect(0, 0, w, h);

  const points = (data.history || []).concat(data.level ?? 0);
  if (points.length < 2) return;

  const dark = data.colorScheme !== 'light';
  const color = data.tint
    || (data.state === 'critical' ? '#ff453a' : data.state === 'warning' ? '#ff9f0a' : dark ? '#fff' : '#1d1d1f');
  const x = (i) => (i / (points.length - 1)) * (w - 2) + 1;
  const y = (v) => h - 3 - v * (h - 6);

  // Soft fill under the line, then the line
  ctx.beginPath();
  points.forEach((v, i) => (i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v))));
  ctx.lineTo(x(points.length - 1), h);
  ctx.lineTo(x(0), h);
  ctx.closePath();
  ctx.globalAlpha = 0.22;
  ctx.fillStyle = color;
  ctx.fill();

  ctx.globalAlpha = 1;
  ctx.beginPath();
  points.forEach((v, i) => (i ? ctx.lineTo(x(i), y(v)) : ctx.moveTo(x(i), y(v))));
  ctx.strokeStyle = color;
  ctx.lineWidth = 1.5;
  ctx.lineJoin = 'round';
  ctx.stroke();
}

window.addEventListener('meterUpdate', (e) => render(e.detail));
render(window.meterData || { history: [0.2, 0.4, 0.3, 0.6, 0.5], level: 0.7 });
window.meterReady = true;

Савети за тестирање

  • Држите све унутар висине од 22 тачке и проверите изглед на minWidth и maxWidth.
  • Користите системски фонт (-apple-system) и font-variant-numeric: tabular-nums да бројеви не би подрхтавали.
  • Испробајте свој изглед са више извора у Додај мерач… и Прилагоди овај мерач…: проценат, брзина мреже, тајмер, недоступан извор.
  • Проверите светлу и тамну траку менија, изабрану боју и стања упозорења и критичног нивоа.
  • Грешке из вашег JavaScript-а појављују се у Console.app. Можете и да отворите index.html у Safari-ју и шаљете му податке из конзоле:
  • Нека датотеке буду мале и избегавајте мрежне захтеве, да би се изглед одмах исцртао.
// Paste in Safari's console with your index.html open to try a value
window.meterData = { level: 0.92, formatted: '92%', label: 'CPU', state: 'critical',
  available: true, colorScheme: 'dark', history: [] };
window.dispatchEvent(new CustomEvent('meterUpdate', { detail: window.meterData }));

Спремни да правите?

$19.99 једнократно. Без претплате. Повраћај новца у року од 30 дана.

Купите за $19.99