images, gifs and messages added to frontend
This commit is contained in:
parent
acd46c0aef
commit
5beb927bf0
@ -1 +1 @@
|
||||
{"speed": "medium", "animation": "continuous", "title": true, "pause": "", "images": ["open.gif"]}
|
||||
{"speed": "medium", "animation": "continuous", "title": true, "pause": "", "images": ["open.gif", "close.gif"]}
|
@ -1 +1 @@
|
||||
["Custom Images"]
|
||||
["Custom Messages"]
|
1
csv/message_settings.json
Normal file
1
csv/message_settings.json
Normal file
@ -0,0 +1 @@
|
||||
{"feature": "Custom Messages", "speed": "Medium", "animation": "Continuous", "title": true, "messages": [{"name": "1", "text": "123", "text_colour": "White", "size": "Medium", "background_colour": "Black"}, {"name": "13", "text": "123", "text_colour": "Red", "size": "Medium", "background_colour": "Purple"}]}
|
Binary file not shown.
Binary file not shown.
17
log.txt
17
log.txt
@ -1,17 +0,0 @@
|
||||
[Errno 2] No such file or directory: '/home/pi/Desktop/stock_ticker/XOM.png'. file: stockTicker.py. line: 2230. type: <class 'FileNotFoundError'>
|
||||
Traceback (most recent call last):
|
||||
File "stockTicker.py", line 2230, in <module>
|
||||
stock_ticker.process_msg(msg)
|
||||
File "stockTicker.py", line 2194, in process_msg
|
||||
self.scrollFunctionsAnimated(userSettings, animation = 'down')
|
||||
File "stockTicker.py", line 309, in scrollFunctionsAnimated
|
||||
self.updateMultiple([options[0]])
|
||||
File "stockTicker.py", line 276, in updateMultiple
|
||||
img = self.functions[option]()
|
||||
File "stockTicker.py", line 1953, in getUserImage
|
||||
image = self.openImage(os.path.join(os.path.dirname(os.path.abspath(__file__)), image))
|
||||
File "stockTicker.py", line 76, in openImage
|
||||
image = Image.open(image_file)
|
||||
File "/usr/lib/python3/dist-packages/PIL/Image.py", line 2634, in open
|
||||
fp = builtins.open(filename, "rb")
|
||||
FileNotFoundError: [Errno 2] No such file or directory: '/home/pi/Desktop/stock_ticker/XOM.png'
|
@ -365,6 +365,9 @@ def feature_settings():
|
||||
elif feature in ['Custom GIFs', 'Custom Images']:
|
||||
save_image_settings(input_settings)
|
||||
|
||||
elif feature == 'Custom Messages':
|
||||
save_message_settings(input_settings)
|
||||
|
||||
return index()
|
||||
|
||||
# saves files uploaded to the webpage for images and GIFs
|
||||
@ -503,6 +506,9 @@ def save_image_settings(input_settings):
|
||||
del current_settings['feature']
|
||||
json.dump(current_settings, open('csv/' + filename, 'w+'))
|
||||
|
||||
def save_message_settings(input_settings):
|
||||
|
||||
json.dump(input_settings, open('csv/message_settings.json', 'w+'))
|
||||
|
||||
@app.route("/stop")
|
||||
def stop():
|
||||
|
@ -489,26 +489,7 @@ var allFeaturesFileAddBtn = [
|
||||
|
||||
|
||||
|
||||
var uploaded_images = [];
|
||||
var uploaded_GIFs = [];
|
||||
|
||||
allFeaturesFileAddBtn.map((value, index) => {
|
||||
if (value !== null) {
|
||||
value.addEventListener("click", () => {
|
||||
var tag = document.createElement("li");
|
||||
tag.innerHTML = allFeaturesFile[index].files[0].name;
|
||||
if (index == 10) {
|
||||
uploaded_images.push(allFeaturesFile[index].files[0]);
|
||||
} else if (index == 11) {
|
||||
uploaded_GIFs.push(allFeaturesFile[index].files[0]);
|
||||
}
|
||||
|
||||
allFeatures[index].appendChild(tag);
|
||||
changeVarValue();
|
||||
addEventOnFeaturesList();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// features input text
|
||||
var stocksText = document.getElementById("inputText3");
|
||||
@ -801,6 +782,29 @@ function getSportsSettings(page){
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
var uploaded_images = [];
|
||||
var uploaded_GIFs = [];
|
||||
|
||||
allFeaturesFileAddBtn.map((value, index) => {
|
||||
if (value !== null) {
|
||||
value.addEventListener("click", () => {
|
||||
var tag = document.createElement("li");
|
||||
tag.innerHTML = allFeaturesFile[index].files[0].name;
|
||||
if (index == 10) {
|
||||
uploaded_images.push(allFeaturesFile[index].files[0]);
|
||||
} else if (index == 11) {
|
||||
uploaded_GIFs.push(allFeaturesFile[index].files[0]);
|
||||
}
|
||||
|
||||
allFeatures[index].appendChild(tag);
|
||||
changeVarValue();
|
||||
addEventOnFeaturesList();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
//images and GIFs
|
||||
function getImageSettings(page){
|
||||
let pause = page.querySelectorAll(".pause-select")[0].value;
|
||||
@ -814,8 +818,48 @@ function getImageSettings(page){
|
||||
return settings;
|
||||
}
|
||||
|
||||
|
||||
var messages = [];
|
||||
|
||||
|
||||
messagesTextAddBtn.addEventListener("click", () => {
|
||||
let pageSelector = "Page13";
|
||||
|
||||
let page = document.getElementById(pageSelector);
|
||||
|
||||
|
||||
let msg_name = messagesText.value;
|
||||
//let speed = getSelected(page.querySelectorAll(".speed-select")[0]);
|
||||
//let animation = getSelected(page.querySelectorAll(".animation-select")[0]);
|
||||
|
||||
let message_text = page.querySelectorAll(".message-input")[0].value;
|
||||
let text_colour = getSelected(page.querySelectorAll(".text-colour")[0]);
|
||||
let text_size = getSelected(page.querySelectorAll(".text-size")[0]);
|
||||
let background_colour = getSelected(page.querySelectorAll(".back-colour")[0]);
|
||||
|
||||
let message = {'name':msg_name, 'text':message_text, 'text_colour':text_colour, 'size':text_size, 'background_colour':background_colour};
|
||||
messages.push(message);
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
function getMessageSettings(page) {
|
||||
|
||||
let messages_el = page.querySelectorAll(".message-list")[0];
|
||||
let message_names = getListItems(messages_el);
|
||||
|
||||
//remove any messages that arent in the list
|
||||
let new_messages = [];
|
||||
|
||||
for (let i = 0; i < messages.length; i++){
|
||||
if (message_names.includes(messages[i]['name'])) {
|
||||
new_messages.push(messages[i]);
|
||||
}
|
||||
}
|
||||
let title = page.querySelectorAll(".title-select")[0].checked;
|
||||
return {'title':title, 'messages':new_messages};
|
||||
}
|
||||
|
||||
|
||||
|
192
stockTicker.py
192
stockTicker.py
@ -45,7 +45,7 @@ class StockTicker():
|
||||
self.blank = Image.new('RGB', (10, 32))
|
||||
self.running = True
|
||||
self.brightness = 1.0
|
||||
self.delay = 0.02
|
||||
|
||||
|
||||
# Configuration for the matrix
|
||||
options = RGBMatrixOptions()
|
||||
@ -62,7 +62,7 @@ class StockTicker():
|
||||
'Daily Forecast':self.getDailyWeatherImage, 'Current Weather': self.getTodayWeatherImage,
|
||||
'Sports (Team Stats)':lambda : self.getLeagueTableImage('premier_league'), 'Sports (Past Games)': lambda:self.getLeagueImage('NBA', 'past'),
|
||||
'Sports (Upcoming Games)': lambda : self.getLeagueImage('NHL', 'future'), 'Sports (Live Games)': lambda: self.getLeagueImage('NBA', 'live'),
|
||||
'News':self.getNewsImage, 'Custom Messages': self.getUserText, 'Custom Images': self.getUserImage, 'Custom GIFs':self.getUserGIF,
|
||||
'News':self.getNewsImage, 'Custom Messages': self.getUserMessages,
|
||||
|
||||
|
||||
'Stocks Prof': self.getStockProfessional, 'Crypto Prof': self.getCryptoProfessional, 'Forex Prof': self.getForexProfessional,
|
||||
@ -74,6 +74,7 @@ class StockTicker():
|
||||
|
||||
def openImage(self, image_file):
|
||||
image = Image.open(image_file)
|
||||
image = image.convert('RGB')
|
||||
# Make image fit our screen.
|
||||
#image.thumbnail((self.matrix.width, self.matrix.height), Image.ANTIALIAS)
|
||||
|
||||
@ -273,9 +274,10 @@ class StockTicker():
|
||||
def updateMultiple(self, options):
|
||||
|
||||
for option in options:
|
||||
img = self.functions[option]()
|
||||
|
||||
if option not in ['Custom GIFs']: # aving the gif like this kills the animation
|
||||
|
||||
if option not in ['Custom GIFs', 'Custom Images', 'Custom Messages']: # these images are already saved in user uploads, dodnt need to update them
|
||||
img = self.functions[option]()
|
||||
img.save('./display_images/'+ option+ '.ppm')
|
||||
|
||||
|
||||
@ -317,69 +319,84 @@ class StockTicker():
|
||||
update_process = Process(target = self.updateMultiple, args = ([options[(i+1) % len(options)]],))
|
||||
update_process.start()
|
||||
|
||||
self.delay = 0.02
|
||||
if options[i % len(options)] == 'Custom Images':
|
||||
images = self.getUserImages()
|
||||
|
||||
if options[i % len(options)] != 'Custom GIFs':
|
||||
|
||||
elif options[i % len(options)] == 'Custom GIFs':
|
||||
images = self.getUserGIFs()
|
||||
|
||||
elif options[i % len(options)] == 'Custom Messages':
|
||||
images = self.getUserMessages()
|
||||
|
||||
|
||||
else: #these options just have a single ppm image
|
||||
image = self.openImage('./display_images/' + options[i % len(options)] +'.ppm')
|
||||
image = image.convert('RGB')
|
||||
else:
|
||||
image = self.openImage('./display_images/Custom GIFs.gif')
|
||||
|
||||
img_width, img_height = image.size
|
||||
images = [image]
|
||||
|
||||
|
||||
for image in images:
|
||||
img_width, img_height = image.size
|
||||
|
||||
offset_x = 0
|
||||
if animation == 'traditional':
|
||||
offset_x = 128
|
||||
elif animation == 'continuous':
|
||||
offset_x = 0
|
||||
elif animation in ['up', 'down']:
|
||||
offset_x = max(0, 128-img_width)
|
||||
if animation == 'traditional':
|
||||
offset_x = 128
|
||||
elif animation == 'continuous':
|
||||
offset_x = 0
|
||||
elif animation in ['up', 'down']:
|
||||
offset_x = max(0, 128-img_width)
|
||||
|
||||
offset_y = 0
|
||||
#first scroll image in from bottom
|
||||
|
||||
frame_skip = int((1/15)/self.delay) #controls how fast gifs run
|
||||
self.frame = 0
|
||||
|
||||
pause_frames = int(0.5/self.delay)
|
||||
if animation == 'up':
|
||||
offset_y = 33
|
||||
direction = -1
|
||||
kill = self.scrollImageY(image, direction = direction, offset_x = offset_x, offset_y = offset_y, frame_skip = frame_skip, gif = options[i % len(options)] == 'Custom GIFs')
|
||||
elif animation == 'down':
|
||||
direction = 1
|
||||
offset_y = -33
|
||||
kill = self.scrollImageY(image, direction = direction, offset_x = offset_x, offset_y = offset_y, frame_skip = frame_skip, gif = options[i % len(options)] == 'Custom GIFs')
|
||||
|
||||
if kill: break
|
||||
offset_y = 0
|
||||
offset_y = 0
|
||||
#first scroll image in from bottom
|
||||
|
||||
|
||||
if animation in ['up', 'down']:
|
||||
while pause_frames > 0:
|
||||
if pause_frames%frame_skip == 0:
|
||||
self.incrementGIF(image)
|
||||
|
||||
pause_frames -=1
|
||||
if options[i % len(options)] != 'Custom GIFs':
|
||||
self.double_buffer.SetImage(image, offset_x, offset_y)
|
||||
else:
|
||||
self.double_buffer.SetImage(image.convert('RGB'), offset_x, offset_y)
|
||||
frame_skip = int((1/15)/self.delay) #controls how fast gifs run
|
||||
self.frame = 0
|
||||
|
||||
pause_frames = int(0.5/self.delay)
|
||||
if animation == 'up':
|
||||
offset_y = 33
|
||||
direction = -1
|
||||
kill = self.scrollImageY(image, direction = direction, offset_x = offset_x, offset_y = offset_y, frame_skip = frame_skip, gif = options[i % len(options)] == 'Custom GIFs')
|
||||
elif animation == 'down':
|
||||
direction = 1
|
||||
offset_y = -33
|
||||
kill = self.scrollImageY(image, direction = direction, offset_x = offset_x, offset_y = offset_y, frame_skip = frame_skip, gif = options[i % len(options)] == 'Custom GIFs')
|
||||
|
||||
if kill: break
|
||||
offset_y = 0
|
||||
|
||||
|
||||
if animation in ['up', 'down']:
|
||||
while pause_frames > 0:
|
||||
if pause_frames%frame_skip == 0:
|
||||
self.incrementGIF(image)
|
||||
|
||||
pause_frames -=1
|
||||
if options[i % len(options)] != 'Custom GIFs':
|
||||
self.double_buffer.SetImage(image, offset_x, offset_y)
|
||||
else:
|
||||
self.double_buffer.SetImage(image.convert('RGB'), offset_x, offset_y)
|
||||
|
||||
self.double_buffer = self.matrix.SwapOnVSync(self.double_buffer)
|
||||
|
||||
time.sleep(self.delay)
|
||||
kill = self.checkKilled()
|
||||
if kill: break
|
||||
|
||||
self.double_buffer = self.matrix.SwapOnVSync(self.double_buffer)
|
||||
|
||||
time.sleep(self.delay)
|
||||
kill = self.checkKilled()
|
||||
if kill: break
|
||||
|
||||
|
||||
if kill: break
|
||||
|
||||
kill = self.scrollImage(image, offset_x = offset_x, offset_y = offset_y, frame_skip = frame_skip, gif = options[i % len(options)] == 'Custom GIFs')
|
||||
|
||||
if kill: break
|
||||
|
||||
kill = self.scrollImage(image, offset_x = offset_x, offset_y = offset_y, frame_skip = frame_skip, gif = options[i % len(options)] == 'Custom GIFs')
|
||||
|
||||
if kill: break
|
||||
if kill: break
|
||||
if kill:break
|
||||
|
||||
update_process.join()
|
||||
i+=1
|
||||
@ -458,7 +475,7 @@ class StockTicker():
|
||||
if kill: break
|
||||
i+=1
|
||||
|
||||
def textImage(self, text, font, r = 255, g = 255, b = 255, matrix_height = False, w_buff = 3, h_buff = 3):
|
||||
def textImage(self, text, font, r = 255, g = 255, b = 255, matrix_height = False, w_buff = 3, h_buff = 3, background = False):
|
||||
'''
|
||||
creates and returns a ppm image containing the text in the supplied font and colour
|
||||
'''
|
||||
@ -470,28 +487,54 @@ class StockTicker():
|
||||
img = Image.new('RGB', (width + w_buff, height + h_buff))
|
||||
d = ImageDraw.Draw(img)
|
||||
|
||||
if background:
|
||||
br, bg, bb = background
|
||||
d.rectangle(xy = (0, 0, width + w_buff, height + h_buff),
|
||||
fill = (br, bg, bb))
|
||||
#outline = (255, 255, 255),
|
||||
#width = 0) #use outline and width to add a border
|
||||
|
||||
d.text((0, 0), text, fill=(r, g, b), font=font)
|
||||
return img
|
||||
|
||||
|
||||
def getUserText(self):
|
||||
def getUserMessages(self):
|
||||
'''
|
||||
displays the text entered in the webpage by the user.
|
||||
'''
|
||||
|
||||
f = open('csv/scroll_text.csv', 'r')
|
||||
|
||||
CSV = csv.reader(f)
|
||||
text, r, g, b = next(CSV)
|
||||
|
||||
f = open('csv/message_settings.json', 'r')
|
||||
all_settings = json.load(f)
|
||||
f.close()
|
||||
|
||||
title_img = self.openImage('feature_titles/message.png')
|
||||
if all_settings['title']:
|
||||
title_img = self.openImage('feature_titles/message.png')
|
||||
imgs = [title_img]
|
||||
else:
|
||||
imgs = []
|
||||
|
||||
font = ImageFont.load("./fonts/texgyre-27.pil")
|
||||
colours = {'Black':(0,0,0),
|
||||
'White':(255,255,255),
|
||||
'Red':(255,0,0),
|
||||
'Green':(0,255,0),
|
||||
'Blue':(0,0,255),
|
||||
'Purple':(255,0,255),
|
||||
'Yellow':(255,255,0),
|
||||
'Cyan':(0,255,255)}
|
||||
|
||||
img = self.textImage(text, font, int(r), int(g), int(b), True, w_buff = 50)
|
||||
return self.stitchImage([title_img, img])
|
||||
for message in all_settings['messages']:
|
||||
font = ImageFont.load("./fonts/texgyre-27.pil")
|
||||
r,g,b = colours[message['text_colour']]
|
||||
|
||||
background = colours[message['background_colour']]
|
||||
img = self.textImage(message['text'], font, int(r), int(g), int(b), True, w_buff = 50, background = background)
|
||||
|
||||
imgs.append(img)
|
||||
|
||||
|
||||
return imgs
|
||||
|
||||
def displayGIF(self, gif):
|
||||
# To iterate through the entire gif
|
||||
@ -1937,7 +1980,7 @@ class StockTicker():
|
||||
|
||||
time.sleep(self.delay*1.1)
|
||||
|
||||
def getUserImage(self):
|
||||
def getUserImages(self):
|
||||
|
||||
title_img = self.openImage('feature_titles/images.png')
|
||||
|
||||
@ -1950,21 +1993,30 @@ class StockTicker():
|
||||
imgs = []
|
||||
|
||||
for image in all_settings['images']:
|
||||
image = self.openImage(os.path.join(os.path.dirname(os.path.abspath(__file__)), image))
|
||||
|
||||
img = self.openImage(os.path.join(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_uploads'), image))
|
||||
imgs.append(img)
|
||||
|
||||
|
||||
return imgs
|
||||
|
||||
return self.stitchImage(imgs)
|
||||
|
||||
def getUserGIF(self):
|
||||
title_img = self.openImage('feature_titles/gifs.png')
|
||||
|
||||
gif = Image.open(os.path.join(os.path.dirname(os.path.abspath(__file__)), all_settings['images'][0]))
|
||||
|
||||
def getUserGIFs(self):
|
||||
f = open('csv/GIF_settings.json', 'r')
|
||||
all_settings = json.load(f)
|
||||
f.close()
|
||||
|
||||
if all_settings['title']:
|
||||
title_img = self.openImage('feature_titles/gifs.png')
|
||||
GIFs = [title_img]
|
||||
else:
|
||||
GIFs = []
|
||||
|
||||
for f in all_settings['images']:
|
||||
GIF = Image.open(os.path.join(os.path.join(os.path.dirname(os.path.abspath(__file__)), 'user_uploads'), f))
|
||||
GIFs.append(GIF)
|
||||
|
||||
#below code stitches title and GIF together
|
||||
'''
|
||||
frames = []
|
||||
|
||||
for i, frame in enumerate(ImageSequence.Iterator(gif)):
|
||||
@ -1977,9 +2029,9 @@ class StockTicker():
|
||||
|
||||
|
||||
frames[0].save('./display_images/Custom GIFs.gif', save_all=True, append_images=frames[1:], loop=0, optimize = False)
|
||||
'''
|
||||
|
||||
|
||||
return None
|
||||
return GIFs
|
||||
|
||||
def displayStocks(self):
|
||||
|
||||
|
@ -2301,12 +2301,13 @@
|
||||
<input
|
||||
type="text"
|
||||
id="inputText14"
|
||||
class="form-control"
|
||||
class="form-control message-input"
|
||||
aria-describedby="TextHelpInline"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--
|
||||
<div class="row g-3 align-items-center mt-3">
|
||||
<div class="col-auto">
|
||||
<label for="inputText15" class="col-form-label"
|
||||
@ -2323,6 +2324,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-3 align-items-center mt-3">
|
||||
<div class="col-auto">
|
||||
<label for="inputScrollSpeed14" class="col-form-label"
|
||||
@ -2338,6 +2340,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="row g-3 align-items-center mt-3">
|
||||
<div class="col-auto">
|
||||
<label for="inputScrollSpeed15" class="col-form-label"
|
||||
@ -2352,6 +2355,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="row g-3 align-items-center mt-3">
|
||||
<div class="col-auto">
|
||||
@ -2360,10 +2364,15 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select id="inputScrollSpeed16" class="form-select">
|
||||
<option>White</option>
|
||||
<option></option>
|
||||
<option></option>
|
||||
<select id="inputScrollSpeed16" class="form-select text-colour">
|
||||
<option>White</option>
|
||||
<option>Black</option>
|
||||
<option>Red</option>
|
||||
<option>Green</option>
|
||||
<option>Blue</option>
|
||||
<option>Purple</option>
|
||||
<option>Yellow</option>
|
||||
<option>Cyan</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -2375,7 +2384,7 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select id="inputScrollSpeed17" class="form-select">
|
||||
<select id="inputScrollSpeed17" class="form-select text-size">
|
||||
<option>Medium</option>
|
||||
<option>Small</option>
|
||||
<option>Large</option>
|
||||
@ -2383,6 +2392,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<!--
|
||||
<div class="row g-3 align-items-center mt-3">
|
||||
<div class="col-auto">
|
||||
<label for="inputScrollSpeed18" class="col-form-label"
|
||||
@ -2397,6 +2408,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
-->
|
||||
|
||||
<div class="row g-3 align-items-center mt-3">
|
||||
<div class="col-auto">
|
||||
@ -2405,10 +2417,15 @@
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select id="inputScrollSpeed19" class="form-select">
|
||||
<select id="inputScrollSpeed19" class="form-select back-colour">
|
||||
<option>Black</option>
|
||||
<option>White</option>
|
||||
<option>Red</option>
|
||||
<option>Green</option>
|
||||
<option></option>
|
||||
<option></option>
|
||||
<option>Blue</option>
|
||||
<option>Purple</option>
|
||||
<option>Yellow</option>
|
||||
<option>Cyan</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@ -2421,7 +2438,7 @@
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<input
|
||||
class="form-check-input"
|
||||
class="form-check-input title-select"
|
||||
type="checkbox"
|
||||
value=""
|
||||
id="flexCheckChecked29"
|
||||
@ -2435,23 +2452,10 @@
|
||||
<div class="features-div-two">
|
||||
<ul
|
||||
id="messages-features"
|
||||
class="display-features-list text-dark"
|
||||
class="display-features-list text-dark message-list"
|
||||
>
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
<li>Custom Message 1</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
BIN
user_uploads/close.gif
Normal file
BIN
user_uploads/close.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 821 B |
Loading…
Reference in New Issue
Block a user