Put the top bar back to words
The icon run in the top bar read worse than what it replaced. Restore the earlier top bar — pane words, new, open ▾, undo ▾ redo, the edit menu, snapshots ▾, title, status, export… — and its CSS. The stage bar's overlay switches, the palette chooser and `menu/popover` stay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f238ff4f62
commit
208dddee07
7 changed files with 168 additions and 207 deletions
|
|
@ -121,26 +121,21 @@
|
|||
:on-pointer-cancel (fn [_] (reset! sizing nil))}]))
|
||||
|
||||
(defn panes
|
||||
"Which panes are open, as three switches at the left of the top bar, each a
|
||||
picture of the window with its pane filled in. In the top bar because a shut
|
||||
pane has no head left to carry its own handle, and because on a narrow screen
|
||||
this is the whole of the navigation."
|
||||
"Which panes are open, as a segmented control in the top bar. In the top bar
|
||||
because a shut pane has no head left to carry its own handle, and because on a
|
||||
narrow screen this is the whole of the navigation."
|
||||
[]
|
||||
(let [{:keys [shut]} @(rf/subscribe [::state])]
|
||||
[:div.icons.panes {:aria-label "panes"}
|
||||
[:div.seg.panes {:aria-label "panes"}
|
||||
(doall
|
||||
(for [[k label strip] [[:pool "media pool" {:x 1.5 :y 2.5 :width 4 :height 11}]
|
||||
[:time "timeline" {:x 1.5 :y 9.5 :width 13 :height 4}]
|
||||
[:params "inspector" {:x 10.5 :y 2.5 :width 4 :height 11}]]]
|
||||
(for [[k label] [[:pool "pool"] [:params "inspector"] [:time "timeline"]]]
|
||||
(let [open? (not (contains? shut k))]
|
||||
^{:key k}
|
||||
[:button.ico {:class (when open? "on")
|
||||
:aria-pressed open? :aria-label label
|
||||
:title (str (if open? "hide the " "show the ") label)
|
||||
:on-click #(rf/dispatch [::toggle k])}
|
||||
[:svg {:view-box "0 0 16 16"}
|
||||
[:rect.frame {:x 1.5 :y 2.5 :width 13 :height 11 :rx 1}]
|
||||
[:rect (assoc strip :rx 0.5)]]])))]))
|
||||
[:button {:class (when open? "on")
|
||||
:aria-pressed open?
|
||||
:title (str (if open? "hide the " "show the ") label)
|
||||
:on-click #(rf/dispatch [::toggle k])}
|
||||
label])))]))
|
||||
|
||||
(defn- stepped [k z out?]
|
||||
;; Geometric steps work below 100% as well as above it.
|
||||
|
|
|
|||
|
|
@ -14,7 +14,9 @@
|
|||
(:require [arthur.db :as db]
|
||||
[arthur.events.playback :as pb]
|
||||
[arthur.events.project :as project]
|
||||
[re-frame.core :as rf]))
|
||||
[arthur.subs.playback :as playback]
|
||||
[re-frame.core :as rf]
|
||||
[reagent.core :as r]))
|
||||
|
||||
(defn- when-said
|
||||
"The ISO stamp the server sends, as a date. Truncated rather than formatted:
|
||||
|
|
@ -33,30 +35,44 @@
|
|||
label
|
||||
(when sub [:span.sub sub])])
|
||||
|
||||
(defn panel
|
||||
"The list, under the top bar's file menu. `close!` shuts it."
|
||||
[close!]
|
||||
(let [{:keys [items loading?]} @(rf/subscribe [::project/listing])
|
||||
choose! (fn [event] (close!) (rf/dispatch event))]
|
||||
[:div.menu.menu-left
|
||||
[:h2 "projects"]
|
||||
(cond
|
||||
loading? [:div.dim "…"]
|
||||
(empty? items) [:div.dim "none saved yet"]
|
||||
:else
|
||||
(doall
|
||||
(for [{:keys [id name seq updated]} items]
|
||||
^{:key id}
|
||||
[item {:label (or name "untitled")
|
||||
:sub (str "r" seq " · " (when-said updated))
|
||||
:on-click #(choose! [::project/open id])}])))
|
||||
[:h2 "built-in examples"]
|
||||
[:div.dim "compiled in, not saved documents"]
|
||||
(doall
|
||||
(for [[id {:keys [label]}] db/clips]
|
||||
^{:key id}
|
||||
[item {:label label :example? true
|
||||
:on-click #(choose! [::pb/select-clip id])}]))
|
||||
[item {:label "8625 stage study" :example? true
|
||||
:sub "composed from a locally saved project"
|
||||
:on-click #(choose! [::project/load-stage])}]]))
|
||||
(defn view []
|
||||
(r/with-let [open? (r/atom false)]
|
||||
(let [{:keys [items loading?]} @(rf/subscribe [::project/listing])
|
||||
busy? (:busy? @(rf/subscribe [::playback/project]))
|
||||
choose! (fn [event] (reset! open? false) (rf/dispatch event))]
|
||||
[:div.menu-wrap
|
||||
[:button {:disabled busy?
|
||||
:class (when @open? "on")
|
||||
:on-click (fn []
|
||||
(when-not @open? (rf/dispatch [::project/list]))
|
||||
(swap! open? not))}
|
||||
"open ▾"]
|
||||
(when @open?
|
||||
[:<>
|
||||
;; A full-page catcher behind the panel, so clicking anywhere else
|
||||
;; dismisses it. Cheaper and more predictable than a document-level
|
||||
;; listener that has to be added, removed and told to ignore the click
|
||||
;; that opened the menu.
|
||||
[:div.menu-scrim {:on-click #(reset! open? false)}]
|
||||
[:div.menu
|
||||
[:h2 "projects"]
|
||||
(cond
|
||||
loading? [:div.dim "…"]
|
||||
(empty? items) [:div.dim "none saved yet"]
|
||||
:else
|
||||
(doall
|
||||
(for [{:keys [id name seq updated]} items]
|
||||
^{:key id}
|
||||
[item {:label (or name "untitled")
|
||||
:sub (str "r" seq " · " (when-said updated))
|
||||
:on-click #(choose! [::project/open id])}])))
|
||||
[:h2 "built-in examples"]
|
||||
[:div.dim "compiled in, not saved documents"]
|
||||
(doall
|
||||
(for [[id {:keys [label]}] db/clips]
|
||||
^{:key id}
|
||||
[item {:label label :example? true
|
||||
:on-click #(choose! [::pb/select-clip id])}]))
|
||||
[item {:label "8625 stage study" :example? true
|
||||
:sub "composed from a locally saved project"
|
||||
:on-click #(choose! [::project/load-stage])}]]])])))
|
||||
|
|
|
|||
|
|
@ -7,32 +7,38 @@
|
|||
[re-frame.core :as rf]
|
||||
[reagent.core :as r]))
|
||||
|
||||
(defn panel
|
||||
"Named versions, under the top bar's file menu. `close!` shuts it."
|
||||
[close!]
|
||||
(r/with-let [draft (r/atom "")]
|
||||
(defn view []
|
||||
(r/with-let [open? (r/atom false)
|
||||
draft (r/atom "")]
|
||||
(let [{:keys [can-edit?]} @(rf/subscribe [::playback/project])
|
||||
rows @(rf/subscribe [::collab/snapshot-list])
|
||||
editable? (not (false? can-edit?))]
|
||||
[:div.menu.menu-left
|
||||
(when editable?
|
||||
[:form.row {:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
(rf/dispatch [::collab/snapshot
|
||||
(or (not-empty (str/trim @draft)) "snapshot")])
|
||||
(reset! draft ""))}
|
||||
[:input {:placeholder "name this version" :value @draft :auto-focus true
|
||||
:on-change #(reset! draft (.. % -target -value))}]
|
||||
[:button {:type "submit"} "take snapshot"]])
|
||||
[:h2 "snapshots"]
|
||||
(if (empty? rows)
|
||||
[:div.dim "none yet"]
|
||||
(doall
|
||||
(for [{:keys [id name author created] :as row} rows]
|
||||
^{:key id}
|
||||
[:div.menu-item.static
|
||||
[:span name [:span.sub (str author " · " (subs (str created) 0 16))]]
|
||||
(when editable?
|
||||
[:button.link {:on-click (fn [] (close!)
|
||||
(rf/dispatch [::collab/restore row]))}
|
||||
"restore"])])))])))
|
||||
[:div.menu-wrap
|
||||
[:button {:class (when @open? "on")
|
||||
:on-click (fn [] (when-not @open? (rf/dispatch [::collab/snapshots]))
|
||||
(swap! open? not))}
|
||||
"snapshots ▾"]
|
||||
(when @open?
|
||||
[:<> [:div.menu-scrim {:on-click #(reset! open? false)}]
|
||||
[:div.menu.menu-left
|
||||
(when editable?
|
||||
[:form.row {:on-submit (fn [e]
|
||||
(.preventDefault e)
|
||||
(rf/dispatch [::collab/snapshot
|
||||
(or (not-empty (str/trim @draft)) "snapshot")])
|
||||
(reset! draft ""))}
|
||||
[:input {:placeholder "name this version" :value @draft :auto-focus true
|
||||
:on-change #(reset! draft (.. % -target -value))}]
|
||||
[:button {:type "submit"} "take snapshot"]])
|
||||
[:h2 "snapshots"]
|
||||
(if (empty? rows)
|
||||
[:div.dim "none yet"]
|
||||
(doall
|
||||
(for [{:keys [id name author created] :as row} rows]
|
||||
^{:key id}
|
||||
[:div.menu-item.static
|
||||
[:span name [:span.sub (str author " · " (subs (str created) 0 16))]]
|
||||
(when editable?
|
||||
[:button.link {:on-click (fn [] (reset! open? false)
|
||||
(rf/dispatch [::collab/restore row]))}
|
||||
"restore"])])))]])])))
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
(ns arthur.ui.topbar
|
||||
"The strip across the top: everything you do to the whole document, on the
|
||||
surface rather than in menus, and as pictures rather than words so there is
|
||||
room for all of it. Each group is a run of the same plain icon button — panes,
|
||||
file, history, clipboard, versions — then the document's name in the middle,
|
||||
then export and the people on the right. Every icon says what it is on hover.
|
||||
"The strip across the top: what document this is, and the three things you can
|
||||
do to the whole of it.
|
||||
|
||||
Open, snapshots and undo history are lists, so they drop from their button;
|
||||
only one of them is open at a time."
|
||||
Save, open and export are here rather than in a pane because none of them is a
|
||||
property of a selection — they act on the document, and the document is the
|
||||
window."
|
||||
(:require [arthur.events.collab :as collab]
|
||||
[arthur.events.export :as export]
|
||||
[arthur.events.history :as history]
|
||||
[arthur.events.project :as project]
|
||||
[arthur.events.ui :as ui]
|
||||
[arthur.subs.playback :as playback]
|
||||
[arthur.subs.ui :as sub]
|
||||
[arthur.ui.layout :as layout]
|
||||
[arthur.ui.menu :as menu]
|
||||
[arthur.ui.openmenu :as openmenu]
|
||||
[arthur.ui.share :as share]
|
||||
[arthur.ui.snapshots :as snapshots]
|
||||
|
|
@ -26,6 +24,7 @@
|
|||
(let [{:keys [target zoom busy? done total]} @(rf/subscribe [::export/state])
|
||||
targets @(rf/subscribe [::export/targets])]
|
||||
[:<>
|
||||
[:button {:disabled busy? :on-click #(reset! open? true)} "export…"]
|
||||
(when @open?
|
||||
[:<> [:div.export-scrim {:on-click #(when-not busy? (reset! open? false))}]
|
||||
[:section.export-dialog {:role "dialog" :aria-modal true :aria-label "Export"}
|
||||
|
|
@ -49,53 +48,16 @@
|
|||
(if busy? (str "rendering " done "/" total) "start export")]
|
||||
[:button {:disabled busy? :on-click #(reset! open? false)} "close"]]]])]))
|
||||
|
||||
(defn- icon
|
||||
"A 16-unit glyph, stroked in the button's own colour."
|
||||
[& paths]
|
||||
(into [:svg {:view-box "0 0 16 16"}] paths))
|
||||
|
||||
(def ^:private glyphs
|
||||
{:new (icon [:path {:d "M4 1.5h5.5L12.5 4.5V14.5H4Z"}] [:path {:d "M8.25 7v5M5.75 9.5h5"}])
|
||||
:open (icon [:path {:d "M1.5 3.5h4.5l1.5 1.5h7v8.5h-13Z"}])
|
||||
:undo (icon [:path {:d "M5.5 3 2.5 6l3 3"}] [:path {:d "M2.5 6h7a4 4 0 0 1 0 8H6"}])
|
||||
:redo (icon [:path {:d "M10.5 3l3 3-3 3"}] [:path {:d "M13.5 6h-7a4 4 0 0 0 0 8H10"}])
|
||||
:history (icon [:path {:d "M4.5 6.5 8 10l3.5-3.5"}])
|
||||
:cut (icon [:circle {:cx 4.5 :cy 12 :r 2}] [:circle {:cx 11.5 :cy 12 :r 2}]
|
||||
[:path {:d "M5.8 10.5 11.5 2M10.2 10.5 4.5 2"}])
|
||||
:copy (icon [:rect {:x 2 :y 2 :width 8 :height 8 :rx 1}] [:rect {:x 6 :y 6 :width 8 :height 8 :rx 1}])
|
||||
:paste (icon [:path {:d "M5.5 2.5h-2v12h9v-12h-2"}] [:rect {:x 5.5 :y 1.5 :width 5 :height 2.5 :rx 0.5}])
|
||||
:duplicate (icon [:rect.fill {:x 2 :y 2 :width 8 :height 8 :rx 1}] [:rect {:x 6 :y 6 :width 8 :height 8 :rx 1}])
|
||||
:unique (icon [:rect.fill {:x 2 :y 2 :width 8 :height 8 :rx 1}]
|
||||
[:rect {:x 6 :y 6 :width 8 :height 8 :rx 1 :stroke-dasharray "2 1.5"}])
|
||||
:snapshots (icon [:path {:d "M4 1.5h8v13l-4-3-4 3Z"}])
|
||||
:export (icon [:path {:d "M8 10V1.5M4.5 5 8 1.5 11.5 5"}] [:path {:d "M2 9.5v5h12v-5"}])})
|
||||
|
||||
(defn- btn [glyph title attrs]
|
||||
[:button.ico (merge {:title title :aria-label title} attrs) (glyphs glyph)])
|
||||
|
||||
(defn- dropper
|
||||
"An icon button with a list hanging from it: projects, snapshots, undo steps."
|
||||
[panel k glyph title on-open body & [disabled?]]
|
||||
[:div.menu-wrap
|
||||
[btn glyph title {:class (when (= k @panel) "on") :disabled disabled?
|
||||
:on-click (fn [] (if (= k @panel)
|
||||
(reset! panel nil)
|
||||
(do (on-open) (reset! panel k))))}]
|
||||
(when (= k @panel)
|
||||
[:<> [:div.menu-scrim {:on-click #(reset! panel nil)}]
|
||||
[body #(reset! panel nil)]])])
|
||||
|
||||
(defn view []
|
||||
(r/with-let [renaming? (r/atom false)
|
||||
draft (r/atom "")
|
||||
panel (r/atom nil)
|
||||
export-open? (r/atom false)]
|
||||
(let [{project-name :name project-seq :seq :keys [busy? status]} @(rf/subscribe [::playback/project])
|
||||
{footage-status :status} @(rf/subscribe [::playback/footage])
|
||||
{export-status :status export-busy? :busy?} @(rf/subscribe [::export/state])
|
||||
{:keys [done undone]} @(rf/subscribe [::history/steps])
|
||||
selected? (boolean (seq @(rf/subscribe [::sub/selections])))
|
||||
{export-status :status} @(rf/subscribe [::export/state])
|
||||
selections @(rf/subscribe [::sub/selections])
|
||||
clipboard @(rf/subscribe [::sub/clipboard])
|
||||
selected? (boolean (seq selections))
|
||||
title (or project-name "untitled")
|
||||
commit! (fn []
|
||||
(rf/dispatch [::project/rename @draft])
|
||||
|
|
@ -107,46 +69,37 @@
|
|||
;; this strip scrolls, and which panes are open is the one control that
|
||||
;; must not be the thing off the end of it.
|
||||
[layout/panes]
|
||||
[:span.sep]
|
||||
[:div.icons
|
||||
[btn :new "new project" {:disabled busy? :on-click #(rf/dispatch [::collab/create])}]
|
||||
[dropper panel :open :open "open a project" #(rf/dispatch [::project/list]) openmenu/panel]]
|
||||
[:span.sep]
|
||||
[:div.icons
|
||||
[btn :undo (if (seq done) (str "undo " (first done) " (⌘Z)") "nothing to undo")
|
||||
{:disabled (empty? done) :on-click #(rf/dispatch [::history/undo])}]
|
||||
[dropper panel :history :history "undo history" identity undo/panel (empty? done)]
|
||||
[btn :redo (if (seq undone) (str "redo " (first undone) " (⇧⌘Z)") "nothing to redo")
|
||||
{:disabled (empty? undone) :on-click #(rf/dispatch [::history/redo])}]]
|
||||
[:span.sep]
|
||||
[:div.icons
|
||||
[btn :cut "cut selection (⌘X)" {:disabled (not selected?) :on-click #(rf/dispatch [::ui/cut])}]
|
||||
[btn :copy "copy selection (⌘C)" {:disabled (not selected?) :on-click #(rf/dispatch [::ui/copy])}]
|
||||
[btn :paste "paste at the playhead into the active row (⌘V)"
|
||||
{:disabled (nil? clipboard) :on-click #(rf/dispatch [::ui/paste])}]
|
||||
[btn :duplicate "duplicate beside the selection; lanes repeat forward (⌘D)"
|
||||
{:disabled (not selected?) :on-click #(rf/dispatch [::ui/duplicate])}]
|
||||
[btn :unique "duplicate unique — with a private copy of its nested symbols (⇧⌘D)"
|
||||
{:disabled (not selected?) :on-click #(rf/dispatch [::ui/duplicate-unique])}]]
|
||||
[:span.sep]
|
||||
[dropper panel :snapshots :snapshots "snapshots — name this version, or go back to one"
|
||||
#(rf/dispatch [::collab/snapshots]) snapshots/panel]
|
||||
[:div.doc
|
||||
(if @renaming?
|
||||
[:input.project-name {:auto-focus true :value @draft
|
||||
:on-change #(reset! draft (.. % -target -value))
|
||||
:on-blur (fn [_] (commit!))
|
||||
:on-key-down (fn [e]
|
||||
(case (.-key e)
|
||||
"Enter" (do (.preventDefault e) (commit!))
|
||||
"Escape" (reset! renaming? false)
|
||||
nil))}]
|
||||
[:button.project-title {:disabled busy? :title "click to rename project"
|
||||
:on-click (fn [] (reset! draft title)
|
||||
(reset! renaming? true))}
|
||||
title (when project-seq [:span.dim (str " r" project-seq)])])
|
||||
[:span.status (or export-status status footage-status)]]
|
||||
[btn :export "export a PNG sequence and audio"
|
||||
{:disabled export-busy? :on-click #(reset! export-open? true)}]
|
||||
[:button {:disabled busy? :on-click #(rf/dispatch [::collab/create])} "new"]
|
||||
[openmenu/view]
|
||||
[undo/view]
|
||||
[menu/view
|
||||
{:label "edit" :title "selection and clipboard"
|
||||
:items [{:label "copy" :sub "Copy selection · ⌘/Ctrl C"
|
||||
:disabled? (not selected?) :on-click #(rf/dispatch [::ui/copy])}
|
||||
{:label "cut" :sub "Cut selection · ⌘/Ctrl X"
|
||||
:disabled? (not selected?) :on-click #(rf/dispatch [::ui/cut])}
|
||||
{:label "paste" :sub "Paste at playhead into the active row · ⌘/Ctrl V"
|
||||
:disabled? (nil? clipboard) :on-click #(rf/dispatch [::ui/paste])}
|
||||
{:label "duplicate" :sub "Duplicate beside selection; lanes repeat forward · ⌘/Ctrl D"
|
||||
:disabled? (not selected?) :on-click #(rf/dispatch [::ui/duplicate])}
|
||||
{:label "duplicate unique"
|
||||
:sub "Duplicate with a private nested symbol graph · ⇧⌘/Ctrl D"
|
||||
:disabled? (not selected?)
|
||||
:on-click #(rf/dispatch [::ui/duplicate-unique])}]}]
|
||||
[snapshots/view]
|
||||
(if @renaming?
|
||||
[:input.project-name {:auto-focus true :value @draft
|
||||
:on-change #(reset! draft (.. % -target -value))
|
||||
:on-blur (fn [_] (commit!))
|
||||
:on-key-down (fn [e]
|
||||
(case (.-key e)
|
||||
"Enter" (do (.preventDefault e) (commit!))
|
||||
"Escape" (reset! renaming? false)
|
||||
nil))}]
|
||||
[:button.project-title {:disabled busy? :title "click to rename project"
|
||||
:on-click (fn [] (reset! draft title)
|
||||
(reset! renaming? true))}
|
||||
title (when project-seq (str " r" project-seq))])
|
||||
[:span.status (or export-status status footage-status)]
|
||||
[exporter export-open?]
|
||||
[share/view]])))
|
||||
|
|
|
|||
|
|
@ -1,22 +1,32 @@
|
|||
(ns arthur.ui.undo
|
||||
"The list of what undo would take off — newest first, so choosing the third
|
||||
row undoes three steps. Undo and redo themselves are the top bar's."
|
||||
"Undo, redo, and the list of what undo would take off — newest first, so
|
||||
choosing the third row undoes three steps."
|
||||
(:require [arthur.events.history :as history]
|
||||
[re-frame.core :as rf]))
|
||||
[re-frame.core :as rf]
|
||||
[reagent.core :as r]))
|
||||
|
||||
(defn panel
|
||||
"What undo would take off, newest first, under the edit menu. `close!` shuts it."
|
||||
[close!]
|
||||
(let [{:keys [done]} @(rf/subscribe [::history/steps])]
|
||||
[:div.menu.menu-left
|
||||
[:h2 "undo history"]
|
||||
(if (empty? done)
|
||||
[:div.dim "nothing to undo"]
|
||||
(doall
|
||||
(map-indexed
|
||||
(fn [i label]
|
||||
^{:key i}
|
||||
[:button.menu-item {:on-click (fn [] (close!)
|
||||
(rf/dispatch [::history/undo (inc i)]))}
|
||||
label (when (pos? i) [:span.sub (str (inc i) " steps")])])
|
||||
(take 30 done))))]))
|
||||
(defn view []
|
||||
(r/with-let [open? (r/atom false)]
|
||||
(let [{:keys [done undone]} @(rf/subscribe [::history/steps])]
|
||||
[:div.menu-wrap.undo
|
||||
[:button {:disabled (empty? done) :title (if (seq done) (str "undo " (first done) " (⌘Z)") "nothing to undo")
|
||||
:on-click #(rf/dispatch [::history/undo])}
|
||||
"undo"]
|
||||
[:button.undo-list {:disabled (empty? done) :class (when @open? "on")
|
||||
:title "undo history" :on-click #(swap! open? not)}
|
||||
"▾"]
|
||||
[:button {:disabled (empty? undone) :title (if (seq undone) (str "redo " (first undone) " (⇧⌘Z)") "nothing to redo")
|
||||
:on-click #(rf/dispatch [::history/redo])}
|
||||
"redo"]
|
||||
(when (and @open? (seq done))
|
||||
[:<> [:div.menu-scrim {:on-click #(reset! open? false)}]
|
||||
[:div.menu.menu-left
|
||||
[:h2 "undo"]
|
||||
(doall
|
||||
(map-indexed
|
||||
(fn [i label]
|
||||
^{:key i}
|
||||
[:button.menu-item {:on-click (fn [] (reset! open? false)
|
||||
(rf/dispatch [::history/undo (inc i)]))}
|
||||
label (when (pos? i) [:span.sub (str (inc i) " steps")])])
|
||||
(take 30 done)))]])])))
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ const CLICK = (label) => `(() => {
|
|||
// turn of the event loop between them, as it is for anybody using the app.
|
||||
const MENU_OPEN = `(() => {
|
||||
if (document.querySelector('.menu')) return true;
|
||||
const b = document.querySelector('.top button[aria-label="open a project"]');
|
||||
const b = [...document.querySelectorAll('button')]
|
||||
.find((b) => b.textContent.trim() === 'open \u25be');
|
||||
if (!b || b.disabled) return false;
|
||||
b.click();
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue